Repository: SlotSun/dart_simple_live Branch: master Commit: 23a09a35b30e Files: 561 Total size: 2.7 MB Directory structure: gitextract_7g62ejnc/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug.yml │ │ ├── config.yml │ │ └── feature.yml │ └── workflows/ │ ├── docs-deploy.yaml │ ├── pr.yaml │ ├── publish_app_dev.yaml │ ├── publish_app_release.yml │ └── publish_tv_app_release.yaml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets/ │ ├── app_version.json │ ├── tv_app_version.json │ └── update-linux-metainfo.dart ├── docs/ │ ├── .gitignore │ ├── .vitepress/ │ │ └── config.mts │ ├── README.md │ ├── contributing.md │ ├── index.md │ ├── package.json │ └── user.md ├── flatpak/ │ ├── flatpak-manifest.yaml │ └── libplacebo-python-3.14.patch ├── simple_live_app/ │ ├── .fvmrc │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── analysis_options.yaml │ ├── android/ │ │ ├── .gitignore │ │ ├── app/ │ │ │ ├── build.gradle.kts │ │ │ ├── google-services.json │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ ├── debug/ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── kotlin/ │ │ │ │ │ └── com/ │ │ │ │ │ └── slotsun/ │ │ │ │ │ └── slive/ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── res/ │ │ │ │ ├── drawable/ │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ ├── ic_launcher_foreground.xml │ │ │ │ │ ├── ic_launcher_monochrome.xml │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21/ │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-anydpi-v26/ │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ ├── values/ │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── values-night/ │ │ │ │ │ └── styles.xml │ │ │ │ └── xml/ │ │ │ │ └── network_security_config.xml │ │ │ └── profile/ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle.kts │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ └── settings.gradle.kts │ ├── assets/ │ │ ├── fonts/ │ │ │ └── fonts-manifest.json │ │ ├── io.github.SlotSun.Slive.desktop │ │ ├── io.github.SlotSun.Slive.metainfo.xml │ │ ├── io.github.SlotSun.dart_simple_live.desktop │ │ ├── io.github.SlotSun.dart_simple_live.metainfo.xml │ │ ├── lotties/ │ │ │ ├── empty.json │ │ │ ├── error.json │ │ │ └── loadding.json │ │ └── statement.txt │ ├── distribute_options.yaml │ ├── flutter_rust_bridge.yaml │ ├── integration_test/ │ │ └── simple_test.dart │ ├── ios/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── Podfile │ │ ├── Runner/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ ├── Contents.json │ │ │ │ └── README.md │ │ │ ├── Base.lproj/ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ └── Runner-Bridging-Header.h │ │ ├── Runner.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata/ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── Runner.xcscheme │ │ └── Runner.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ ├── lib/ │ │ ├── app/ │ │ │ ├── app_style.dart │ │ │ ├── constant.dart │ │ │ ├── controller/ │ │ │ │ ├── app_settings_controller.dart │ │ │ │ └── base_controller.dart │ │ │ ├── custom_throttle.dart │ │ │ ├── event_bus.dart │ │ │ ├── log.dart │ │ │ ├── sites.dart │ │ │ ├── utils/ │ │ │ │ ├── archive.dart │ │ │ │ ├── document.dart │ │ │ │ ├── duration_2_str_utils.dart │ │ │ │ ├── dynamic_filter.dart │ │ │ │ ├── dynamic_sort.dart │ │ │ │ ├── listen_fourth_button.dart │ │ │ │ ├── permission_handler.dart │ │ │ │ ├── sandbox.dart │ │ │ │ ├── string_normalizer.dart │ │ │ │ └── url_parse.dart │ │ │ └── utils.dart │ │ ├── firebase_options.dart │ │ ├── icons/ │ │ │ └── live_icons.dart │ │ ├── main.dart │ │ ├── models/ │ │ │ ├── account/ │ │ │ │ ├── bilibili_user_info_page.dart │ │ │ │ └── douyin_user_info.dart │ │ │ ├── db/ │ │ │ │ ├── follow_user.dart │ │ │ │ ├── follow_user.g.dart │ │ │ │ ├── follow_user_tag.dart │ │ │ │ ├── follow_user_tag.g.dart │ │ │ │ ├── history.dart │ │ │ │ └── history.g.dart │ │ │ ├── font_model.dart │ │ │ ├── fonts_model.dart │ │ │ ├── sync_client_info_model.dart │ │ │ └── version_model.dart │ │ ├── modules/ │ │ │ ├── category/ │ │ │ │ ├── category_controller.dart │ │ │ │ ├── category_list_controller.dart │ │ │ │ ├── category_list_view.dart │ │ │ │ ├── category_page.dart │ │ │ │ └── detail/ │ │ │ │ ├── category_detail_controller.dart │ │ │ │ └── category_detail_page.dart │ │ │ ├── follow_user/ │ │ │ │ ├── follow_app_setting/ │ │ │ │ │ ├── follow_app_settings_controller.dart │ │ │ │ │ └── follow_app_settings_page.dart │ │ │ │ ├── follow_info_setting/ │ │ │ │ │ ├── follow_info_controller.dart │ │ │ │ │ ├── follow_info_page.dart │ │ │ │ │ └── readme.md │ │ │ │ ├── follow_user_controller.dart │ │ │ │ └── follow_user_page.dart │ │ │ ├── home/ │ │ │ │ ├── home_controller.dart │ │ │ │ ├── home_list_controller.dart │ │ │ │ ├── home_list_view.dart │ │ │ │ └── home_page.dart │ │ │ ├── indexed/ │ │ │ │ ├── indexed_controller.dart │ │ │ │ └── indexed_page.dart │ │ │ ├── live_room/ │ │ │ │ ├── danmu/ │ │ │ │ │ └── danmaku_mask.dart │ │ │ │ ├── live_room_controller.dart │ │ │ │ ├── live_room_page.dart │ │ │ │ └── player/ │ │ │ │ ├── player_controller.dart │ │ │ │ └── player_controls.dart │ │ │ ├── mine/ │ │ │ │ ├── account/ │ │ │ │ │ ├── account_controller.dart │ │ │ │ │ ├── account_page.dart │ │ │ │ │ └── bilibili/ │ │ │ │ │ ├── qr_login_controller.dart │ │ │ │ │ ├── qr_login_page.dart │ │ │ │ │ ├── web_login_controller.dart │ │ │ │ │ └── web_login_page.dart │ │ │ │ ├── history/ │ │ │ │ │ ├── history_controller.dart │ │ │ │ │ └── history_page.dart │ │ │ │ ├── mine_page.dart │ │ │ │ └── parse/ │ │ │ │ ├── parse_controller.dart │ │ │ │ └── parse_page.dart │ │ │ ├── other/ │ │ │ │ └── debug_log_page.dart │ │ │ ├── search/ │ │ │ │ ├── douyin/ │ │ │ │ │ ├── douyin_search_controller.dart │ │ │ │ │ └── douyin_search_view.dart │ │ │ │ ├── search_controller.dart │ │ │ │ ├── search_list_controller.dart │ │ │ │ ├── search_list_view.dart │ │ │ │ └── search_page.dart │ │ │ ├── settings/ │ │ │ │ ├── appstyle_settings/ │ │ │ │ │ ├── appstyle_setting_contorller.dart │ │ │ │ │ └── appstyle_setting_page.dart │ │ │ │ ├── auto_exit_settings_page.dart │ │ │ │ ├── danmu_settings_page.dart │ │ │ │ ├── danmu_shield/ │ │ │ │ │ ├── danmu_shield_controller.dart │ │ │ │ │ └── danmu_shield_page.dart │ │ │ │ ├── indexed_settings/ │ │ │ │ │ ├── indexed_settings_controller.dart │ │ │ │ │ └── indexed_settings_page.dart │ │ │ │ ├── other/ │ │ │ │ │ ├── other_settings_controller.dart │ │ │ │ │ └── other_settings_page.dart │ │ │ │ └── play_settings_page.dart │ │ │ └── sync/ │ │ │ ├── local_sync/ │ │ │ │ ├── device/ │ │ │ │ │ ├── sync_device_controller.dart │ │ │ │ │ └── sync_device_page.dart │ │ │ │ ├── local_sync_controller.dart │ │ │ │ ├── local_sync_page.dart │ │ │ │ └── scan_qr/ │ │ │ │ ├── sync_scan_qr_controller.dart │ │ │ │ └── sync_scan_qr_page.dart │ │ │ ├── remote_sync/ │ │ │ │ ├── room/ │ │ │ │ │ ├── remote_sync_room_controller.dart │ │ │ │ │ └── remote_sync_room_page.dart │ │ │ │ └── webdav/ │ │ │ │ ├── remote_sync_webdav_config_page.dart │ │ │ │ ├── remote_sync_webdav_controller.dart │ │ │ │ └── remote_sync_webdav_page.dart │ │ │ └── sync_page.dart │ │ ├── requests/ │ │ │ ├── common_request.dart │ │ │ ├── custom_log_interceptor.dart │ │ │ ├── http_client.dart │ │ │ ├── http_error.dart │ │ │ ├── sync_client_request.dart │ │ │ └── webdav_client.dart │ │ ├── routes/ │ │ │ ├── app_analytics_observer.dart │ │ │ ├── app_navigation.dart │ │ │ ├── app_pages.dart │ │ │ └── route_path.dart │ │ ├── services/ │ │ │ ├── bilibili_account_service.dart │ │ │ ├── core_api_service.dart │ │ │ ├── db_service.dart │ │ │ ├── douyin_account_service.dart │ │ │ ├── firebase_service.dart │ │ │ ├── follow_service.dart │ │ │ ├── history_service.dart │ │ │ ├── local_storage_service.dart │ │ │ ├── migration_service.dart │ │ │ ├── signalr_service.dart │ │ │ ├── sync_service.dart │ │ │ └── window_service.dart │ │ ├── src/ │ │ │ └── rust/ │ │ │ ├── api/ │ │ │ │ ├── danmaku_mask.dart │ │ │ │ └── simple.dart │ │ │ ├── frb_generated.dart │ │ │ ├── frb_generated.io.dart │ │ │ └── frb_generated.web.dart │ │ └── widgets/ │ │ ├── desktop_refresh_button.dart │ │ ├── filter_button.dart │ │ ├── follow_user_item.dart │ │ ├── keep_alive_wrapper.dart │ │ ├── live_room_card.dart │ │ ├── net_image.dart │ │ ├── none_border_circular_textfield.dart │ │ ├── page_grid_view.dart │ │ ├── page_list_view.dart │ │ ├── rectangular_indicator.dart │ │ ├── settings/ │ │ │ ├── settings_action.dart │ │ │ ├── settings_card.dart │ │ │ ├── settings_menu.dart │ │ │ ├── settings_menu_check.dart │ │ │ ├── settings_number.dart │ │ │ └── settings_switch.dart │ │ ├── shadow_card.dart │ │ ├── status/ │ │ │ ├── app_empty_widget.dart │ │ │ ├── app_error_widget.dart │ │ │ └── app_loadding_widget.dart │ │ ├── superchat_card.dart │ │ └── ui/ │ │ └── after_post_frame.dart │ ├── linux/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── flutter/ │ │ │ ├── CMakeLists.txt │ │ │ ├── generated_plugin_registrant.cc │ │ │ ├── generated_plugin_registrant.h │ │ │ └── generated_plugins.cmake │ │ ├── packaging/ │ │ │ ├── aur/ │ │ │ │ └── slive.desktop │ │ │ └── deb/ │ │ │ └── make_config.yaml │ │ └── runner/ │ │ ├── CMakeLists.txt │ │ ├── main.cc │ │ ├── my_application.cc │ │ └── my_application.h │ ├── macos/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ ├── Flutter-Release.xcconfig │ │ │ └── GeneratedPluginRegistrant.swift │ │ ├── Podfile │ │ ├── Runner/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj/ │ │ │ │ └── MainMenu.xib │ │ │ ├── Configs/ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ ├── Debug.xcconfig │ │ │ │ ├── Release.xcconfig │ │ │ │ └── Warnings.xcconfig │ │ │ ├── DebugProfile.entitlements │ │ │ ├── Info.plist │ │ │ ├── MainFlutterWindow.swift │ │ │ └── Release.entitlements │ │ ├── Runner.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ └── xcshareddata/ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── Runner.xcscheme │ │ ├── Runner.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata/ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── RunnerTests/ │ │ │ └── RunnerTests.swift │ │ └── packaging/ │ │ └── dmg/ │ │ └── make_config.yaml │ ├── pubspec.yaml │ ├── rust/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── api/ │ │ │ ├── danmaku_mask.rs │ │ │ ├── mod.rs │ │ │ └── simple.rs │ │ ├── frb_generated.rs │ │ └── lib.rs │ ├── rust_builder/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── android/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── settings.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── AndroidManifest.xml │ │ ├── cargokit/ │ │ │ ├── .gitignore │ │ │ ├── LICENSE │ │ │ ├── README │ │ │ ├── build_pod.sh │ │ │ ├── build_tool/ │ │ │ │ ├── README.md │ │ │ │ ├── analysis_options.yaml │ │ │ │ ├── bin/ │ │ │ │ │ └── build_tool.dart │ │ │ │ ├── lib/ │ │ │ │ │ ├── build_tool.dart │ │ │ │ │ └── src/ │ │ │ │ │ ├── android_environment.dart │ │ │ │ │ ├── artifacts_provider.dart │ │ │ │ │ ├── build_cmake.dart │ │ │ │ │ ├── build_gradle.dart │ │ │ │ │ ├── build_pod.dart │ │ │ │ │ ├── build_tool.dart │ │ │ │ │ ├── builder.dart │ │ │ │ │ ├── cargo.dart │ │ │ │ │ ├── crate_hash.dart │ │ │ │ │ ├── environment.dart │ │ │ │ │ ├── logging.dart │ │ │ │ │ ├── options.dart │ │ │ │ │ ├── precompile_binaries.dart │ │ │ │ │ ├── rustup.dart │ │ │ │ │ ├── target.dart │ │ │ │ │ ├── util.dart │ │ │ │ │ └── verify_binaries.dart │ │ │ │ ├── pubspec.lock │ │ │ │ └── pubspec.yaml │ │ │ ├── cmake/ │ │ │ │ ├── cargokit.cmake │ │ │ │ └── resolve_symlinks.ps1 │ │ │ ├── gradle/ │ │ │ │ └── plugin.gradle │ │ │ ├── run_build_tool.cmd │ │ │ └── run_build_tool.sh │ │ ├── ios/ │ │ │ ├── Classes/ │ │ │ │ └── dummy_file.c │ │ │ └── rust_lib_simple_live_app.podspec │ │ ├── linux/ │ │ │ └── CMakeLists.txt │ │ ├── macos/ │ │ │ ├── Classes/ │ │ │ │ └── dummy_file.c │ │ │ └── rust_lib_simple_live_app.podspec │ │ ├── pubspec.yaml │ │ └── windows/ │ │ ├── .gitignore │ │ └── CMakeLists.txt │ ├── shorebird.yaml │ ├── test/ │ │ ├── tool_test.dart │ │ └── widget_test.dart │ ├── test_driver/ │ │ └── integration_test.dart │ └── windows/ │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter/ │ │ ├── CMakeLists.txt │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ └── generated_plugins.cmake │ ├── packaging/ │ │ ├── exe/ │ │ │ ├── ChineseSimplified.isl │ │ │ └── make_config.yaml │ │ └── msix/ │ │ └── make_config.yaml │ └── runner/ │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h ├── simple_live_console/ │ ├── .fvmrc │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── analysis_options.yaml │ ├── bin/ │ │ └── simple_live_console.dart │ ├── pubspec.yaml │ └── test/ │ └── all_live_console_test.dart ├── simple_live_core/ │ ├── .fvmrc │ ├── .gitattributes │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── analysis_options.yaml │ ├── assets/ │ │ └── js/ │ │ ├── a_bogus.js │ │ └── douyin-webmssdk.js │ ├── example/ │ │ └── simple_live_core_example.dart │ ├── lib/ │ │ ├── simple_live_core.dart │ │ └── src/ │ │ ├── common/ │ │ │ ├── binary_writer.dart │ │ │ ├── convert_helper.dart │ │ │ ├── core_error.dart │ │ │ ├── core_log.dart │ │ │ ├── custom_interceptor.dart │ │ │ ├── douyin/ │ │ │ │ ├── abogus.dart │ │ │ │ ├── douyinRequestParams.dart │ │ │ │ └── douyin_utils.dart │ │ │ ├── http_client.dart │ │ │ ├── js_engine.dart │ │ │ └── web_socket_util.dart │ │ ├── danmaku/ │ │ │ ├── bilibili_danmaku.dart │ │ │ ├── douyin_danmaku.dart │ │ │ ├── douyu_danmaku.dart │ │ │ ├── huya_danmaku.dart │ │ │ ├── proto/ │ │ │ │ ├── douyin.pb.dart │ │ │ │ ├── douyin.pbenum.dart │ │ │ │ ├── douyin.pbjson.dart │ │ │ │ └── douyin.proto │ │ │ └── twitch_danmaku.dart │ │ ├── interface/ │ │ │ ├── live_danmaku.dart │ │ │ └── live_site.dart │ │ ├── model/ │ │ │ ├── live_anchor_item.dart │ │ │ ├── live_category.dart │ │ │ ├── live_category_result.dart │ │ │ ├── live_message.dart │ │ │ ├── live_play_quality.dart │ │ │ ├── live_play_url.dart │ │ │ ├── live_room_detail.dart │ │ │ ├── live_room_item.dart │ │ │ ├── live_search_result.dart │ │ │ └── tars/ │ │ │ ├── get_cdn_token_ex_req.dart │ │ │ ├── get_cdn_token_ex_resp.dart │ │ │ ├── get_cdn_token_req.dart │ │ │ ├── get_cdn_token_resp.dart │ │ │ ├── huya_danmaku.dart │ │ │ ├── tar2dart.dart │ │ │ └── types.dart │ │ └── platforms/ │ │ ├── bilibili/ │ │ │ └── bilibili_site.dart │ │ ├── douyin/ │ │ │ ├── abogus.dart │ │ │ ├── douyin_request_params.dart │ │ │ ├── douyin_site.dart │ │ │ └── douyin_utils.dart │ │ ├── douyu/ │ │ │ └── douyu_site.dart │ │ ├── huya/ │ │ │ ├── huya_site.dart │ │ │ └── utils.dart │ │ ├── readme.md │ │ └── twitch/ │ │ ├── models.dart │ │ └── twitch_site.dart │ ├── packages/ │ │ └── tars_dart/ │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── analysis_options.yaml │ │ ├── lib/ │ │ │ └── tars/ │ │ │ ├── codec/ │ │ │ │ ├── tars_decode_exception.dart │ │ │ │ ├── tars_deep_copyable.dart │ │ │ │ ├── tars_displayer.dart │ │ │ │ ├── tars_encode_exception.dart │ │ │ │ ├── tars_input_stream.dart │ │ │ │ ├── tars_output_stream.dart │ │ │ │ └── tars_struct.dart │ │ │ ├── net/ │ │ │ │ └── base_tars_http.dart │ │ │ └── tup/ │ │ │ ├── basic_class_type_util.dart │ │ │ ├── const.dart │ │ │ ├── object_create_exception.dart │ │ │ ├── request_packet.dart │ │ │ ├── tars_uni_packet.dart │ │ │ ├── tup_response.dart │ │ │ ├── tup_result_exception.dart │ │ │ ├── uni_attribute.dart │ │ │ ├── uni_packet.dart │ │ │ └── write_buffer.dart │ │ └── pubspec.yaml │ ├── pubspec.yaml │ └── test/ │ ├── douyu_sc.json │ └── simple_live_core_test.dart └── simple_live_tv_app/ ├── .fvmrc ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android/ │ ├── .gitignore │ ├── app/ │ │ ├── build.gradle.kts │ │ ├── proguard-rules.pro │ │ └── src/ │ │ ├── debug/ │ │ │ └── AndroidManifest.xml │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── bgylde/ │ │ │ │ └── live/ │ │ │ │ └── MainActivity.java │ │ │ ├── kotlin/ │ │ │ │ └── com/ │ │ │ │ └── xycz/ │ │ │ │ └── simple_live_tv/ │ │ │ │ └── MainActivity.kt │ │ │ └── res/ │ │ │ ├── drawable/ │ │ │ │ └── launch_background.xml │ │ │ ├── drawable-v21/ │ │ │ │ └── launch_background.xml │ │ │ ├── mipmap-anydpi-v26/ │ │ │ │ └── ic_banner.xml │ │ │ ├── values/ │ │ │ │ ├── ic_banner_background.xml │ │ │ │ └── styles.xml │ │ │ ├── values-night/ │ │ │ │ └── styles.xml │ │ │ └── xml/ │ │ │ └── network_security_config.xml │ │ └── profile/ │ │ └── AndroidManifest.xml │ ├── build.gradle.kts │ ├── gradle/ │ │ └── wrapper/ │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ └── settings.gradle.kts ├── assets/ │ ├── lotties/ │ │ ├── empty.json │ │ ├── error.json │ │ └── loadding.json │ └── statement.txt ├── lib/ │ ├── app/ │ │ ├── app_error.dart │ │ ├── app_focus_node.dart │ │ ├── app_style.dart │ │ ├── base_focus_model.dart │ │ ├── constant.dart │ │ ├── controller/ │ │ │ ├── app_settings_controller.dart │ │ │ └── base_controller.dart │ │ ├── event_bus.dart │ │ ├── log.dart │ │ ├── sites.dart │ │ └── utils.dart │ ├── main.dart │ ├── models/ │ │ ├── account/ │ │ │ └── bilibili_user_info_page.dart │ │ ├── db/ │ │ │ ├── follow_user.dart │ │ │ ├── follow_user.g.dart │ │ │ ├── history.dart │ │ │ └── history.g.dart │ │ ├── follow_user_item.dart │ │ └── version_model.dart │ ├── modules/ │ │ ├── account/ │ │ │ └── bilibili/ │ │ │ ├── qr_login_controller.dart │ │ │ └── qr_login_page.dart │ │ ├── agreement/ │ │ │ └── agreement_page.dart │ │ ├── category/ │ │ │ ├── category_controller.dart │ │ │ ├── category_page.dart │ │ │ └── detail/ │ │ │ ├── category_detail_controller.dart │ │ │ └── category_detail_page.dart │ │ ├── follow_user/ │ │ │ └── follow_user_page.dart │ │ ├── history/ │ │ │ ├── history_controller.dart │ │ │ └── history_page.dart │ │ ├── home/ │ │ │ ├── home_controller.dart │ │ │ └── home_page.dart │ │ ├── hot_live/ │ │ │ ├── hot_live_controller.dart │ │ │ └── hot_live_page.dart │ │ ├── live_room/ │ │ │ ├── live_room_controller.dart │ │ │ ├── live_room_page.dart │ │ │ └── player/ │ │ │ ├── player_controller.dart │ │ │ └── player_controls.dart │ │ ├── search/ │ │ │ ├── anchor/ │ │ │ │ ├── search_anchor_controller.dart │ │ │ │ └── search_anchor_page.dart │ │ │ └── room/ │ │ │ ├── search_room_controller.dart │ │ │ └── search_room_page.dart │ │ ├── settings/ │ │ │ ├── settings_controller.dart │ │ │ └── settings_page.dart │ │ └── sync/ │ │ ├── sync_controller.dart │ │ └── sync_page.dart │ ├── requests/ │ │ ├── common_request.dart │ │ ├── custom_log_interceptor.dart │ │ ├── http_client.dart │ │ └── http_error.dart │ ├── routes/ │ │ ├── app_navigation.dart │ │ ├── app_pages.dart │ │ └── route_path.dart │ ├── services/ │ │ ├── bilibili_account_service.dart │ │ ├── db_service.dart │ │ ├── follow_user_service.dart │ │ ├── local_storage_service.dart │ │ ├── signalr_service.dart │ │ └── sync_service.dart │ └── widgets/ │ ├── app_scaffold.dart │ ├── button/ │ │ ├── highlight_button.dart │ │ ├── highlight_list_tile.dart │ │ └── home_big_button.dart │ ├── card/ │ │ ├── anchor_card.dart │ │ └── live_room_card.dart │ ├── highlight_widget.dart │ ├── keep_alive_wrapper.dart │ ├── net_image.dart │ ├── page_grid_view.dart │ ├── rectangular_indicator.dart │ ├── settings_item_widget.dart │ └── status/ │ ├── app_empty_widget.dart │ ├── app_error_widget.dart │ └── app_loadding_widget.dart ├── pubspec.yaml ├── test/ │ └── widget_test.dart └── windows/ ├── .gitignore ├── CMakeLists.txt ├── flutter/ │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake └── runner/ ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug.yml ================================================ name: BUG报告 title: "[BUG] 请填写简短易读的标题" description: "使用过程中遇到了BUG" assignees: SlotSun labels: - "bug" body: - type: checkboxes id: terms attributes: label: 提交前确认 description: 在提交前,请确认以下内容 options: - label: 先去Issues列表中查找下是否存在相似的Issue,如果有了就不要重复发了 required: true - label: 我正在使用最新版本的 Slive required: true - type: textarea id: description validations: required: true attributes: label: BUG内容 description: 请详细描述你遇到的问题 - type: textarea id: steps validations: required: true attributes: label: 重现步骤 render: plain text description: 请详细描述重现步骤 placeholder: | 1. xxxx 2. xxxx 3. xxxx - type: textarea id: screenshots attributes: label: 截图或视频 description: 请上传BUG截图、或视频 - type: textarea id: logs attributes: label: 日志 description: 请上传日志文件 placeholder: | 可以在[其他设置]-[开启日志记录]后获取日志文件 - type: dropdown id: release-type validations: required: true attributes: label: 使用版本来源 description: 使用的是什么版本,从哪里下载的 options: - 正式版(Releases) - 开发版(Actions) - 上游仓库(xiaoyaocz) default: 0 - type: dropdown id: platform validations: required: true attributes: label: 运行平台 description: 选择你当前运行应用的平台 multiple: false options: - Windows - macOS - Linux - Android - iOS - type: input id: os-version attributes: label: 操作系统信息 description: 请填写你的操作系统信息版本 placeholder: ex. Windows 10 21H1 / MIUI 15 Android 12 validations: required: true - type: input id: device attributes: label: 设备信息 description: 请填写你的设备信息 placeholder: ex. PC / iPhone 12 Pro Max validations: required: true - type: textarea id: remark attributes: label: 备注 description: 其他信息 ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false ================================================ FILE: .github/ISSUE_TEMPLATE/feature.yml ================================================ name: 功能请求 title: "[Feature] 请填写简短易读的标题" description: "请求开发新的功能/优化某项功能" assignees: SlotSun labels: - "enhancement" body: - type: markdown attributes: value: | 你的Issue大概率会搁置很久,除非是那种我能随手解决的。 暂时不会添加新的平台支持(因为我自己用不上)。如果你有开发能力,建议的自行开发后提交个PR。 - type: checkboxes id: terms attributes: label: 非重复的Issue description: 先去Issues列表中查找下是否存在相似的Issue,如果有了就不要重复发了 options: - label: 已确认没有相似的Issue required: true - type: textarea id: description validations: required: true attributes: label: 功能描述 description: 请填写功能描述,如果你有多个建议、请求,请分开提交Issue - type: checkboxes id: platform attributes: label: 平台 description: 这个功能针对什么平台 options: - label: Android - label: iOS - label: MacOS - label: Windows - label: Linux - type: textarea id: additional attributes: label: 附加信息 description: 可以附加相关截图、视频或链接(好让我知道要我抄哪个APP) ================================================ FILE: .github/workflows/docs-deploy.yaml ================================================ name: docs-deploy on: workflow_dispatch: push: branches: - 'master' paths: - 'docs/**' # 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages permissions: contents: read pages: write id-token: write # 只允许同时进行一次部署,跳过正在运行和最新队列之间的运行队列 # 但是,不要取消正在进行的运行,因为我们希望允许这些生产部署完成 concurrency: group: pages cancel-in-progress: false jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - uses: pnpm/action-setup@v3 with: version: latest - name: Setup Node uses: actions/setup-node@v4 with: node-version: 22 cache: pnpm cache-dependency-path: docs/pnpm-lock.yaml - name: Setup Pages uses: actions/configure-pages@v5 - name: start build run: | pnpm install pnpm docs:build working-directory: docs - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: docs/.vitepress/dist # 部署工作 deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} needs: build runs-on: ubuntu-latest name: Deploy steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .github/workflows/pr.yaml ================================================ name: PR Check on: pull_request: branches: - main - dev jobs: build-mac-ios-android: runs-on: macos-latest steps: - name: Checkout PR code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - name: Setup Java uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: "21" cache: "gradle" - name: Setup Flutter uses: subosito/flutter-action@v2 with: flutter-version-file: simple_live_app/pubspec.yaml cache: true - name: Enable Flutter Desktop run: flutter config --enable-macos-desktop - name: Restore packages run: | cd simple_live_app flutter pub get - name: Install appdmg run: npm install -g appdmg - name: Install fast-forge run: | git clone https://github.com/SlotSun/fastforge.git cd fastforge dart pub global activate melos melos run activate - name: Setup Xcode uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: latest-stable - name: Build APK run: | cd simple_live_app flutter build apk --debug - name: Build IPA run: | cd simple_live_app flutter build ios --no-codesign - name: Build MacOS run: | cd simple_live_app fastforge package --platform macos --targets dmg,zip --skip-clean build-linux: strategy: matrix: include: - runner: ubuntu-24.04 arch: x86_64 - runner: ubuntu-24.04-arm arch: aarch64 runs-on: ${{ matrix.runner }} steps: - name: Checkout PR code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - name: Setup Flutter uses: subosito/flutter-action@v2 with: flutter-version-file: simple_live_app/pubspec.yaml channel: master - name: Install Dependencies run: | sudo apt-get update sudo apt-get install -y clang cmake libgtk-3-dev unzip libasound2-dev gcc g++ autoconf automake \ debhelper glslang-dev ladspa-sdk xutils-dev libasound2-dev libarchive-dev libbluray-dev \ libdav1d-dev libdvdnav-dev libegl1-mesa-dev libepoxy-dev libfontconfig-dev libfreetype6-dev \ libfribidi-dev libgl1-mesa-dev libgbm-dev libgme-dev libgsm1-dev libharfbuzz-dev libjpeg-dev \ libbrotli-dev liblcms2-dev libmodplug-dev libmp3lame-dev libopenal-dev \ libopus-dev libopencore-amrnb-dev libopencore-amrwb-dev libpulse-dev librtmp-dev \ libsdl2-dev libsixel-dev libssh-dev libsoxr-dev libspeex-dev libtool \ libv4l-dev libva-dev libvdpau-dev libvorbis-dev libvo-amrwbenc-dev \ libunwind-dev libvpx-dev libwayland-dev libx11-dev libxext-dev libdrm-dev \ libxkbcommon-dev libxrandr-dev libxss-dev libxv-dev libxvidcore-dev \ linux-libc-dev nasm ninja-build pkg-config python3 python3-docutils wayland-protocols \ x11proto-core-dev zlib1g-dev libfdk-aac-dev libtheora-dev libwebp-dev \ unixodbc-dev libpq-dev libxxhash-dev libaom-dev libbs2b-dev libcaca-dev libcdio-paranoia-dev - name: Enable Flutter Desktop run: flutter config --enable-linux-desktop - name: Restore Packages run: | cd simple_live_app flutter pub get - name: Install fast-forge run: | git clone https://github.com/SlotSun/fastforge.git cd fastforge dart pub global activate melos melos run activate - name: Build Linux run: | cd simple_live_app fastforge package --platform linux --targets deb,zip --skip-clean cd build/dist/* mv simple_live_app-*-linux.deb simplelive-${{ matrix.arch }}-linux.deb mv simple_live_app-*-linux.zip simplelive-${{ matrix.arch }}-linux.zip build-windows: runs-on: windows-latest steps: - name: Checkout PR code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - name: Setup Flutter uses: subosito/flutter-action@v2 with: flutter-version-file: simple_live_app/pubspec.yaml cache: true - name: Enable Flutter Desktop run: flutter config --enable-windows-desktop - name: Restore Packages run: | cd simple_live_app flutter clean flutter pub get - name: Install fast-forge run: | git clone https://github.com/SlotSun/fastforge.git cd fastforge dart pub global activate melos melos run activate - name: Build Windows run: | cd simple_live_app fastforge package --platform windows --targets msix,zip --skip-clean # 打包 Flatpak build-flatpak: strategy: matrix: include: - runner: ubuntu-24.04 arch: x86_64 - runner: ubuntu-24.04-arm arch: aarch64 runs-on: ${{ matrix.runner }} permissions: contents: write steps: # 签出代码 - name: Checkout PR code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} # 安装依赖 - name: Install dependencies run: | sudo add-apt-repository -y ppa:flatpak/stable sudo apt-get update -q sudo apt-get install -y flatpak flatpak-builder ca-certificates gnupg software-properties-common flatpak remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo git config --global protocol.file.allow always # 构建 Flatpak bundle - name: Build flatpak run: | flatpak-builder build-dir flatpak/flatpak-manifest.yaml \ --arch=${{ matrix.arch }} \ --repo=${{ matrix.arch }} \ --force-clean \ --user \ --install-deps-from=flathub \ --ccache \ --disable-rofiles-fuse ================================================ FILE: .github/workflows/publish_app_dev.yaml ================================================ name: app-build-action-dev on: workflow_dispatch: push: branches: - 'dev' # If previous workflow is still running, we push again, we will cancel the previous workflow concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} cancel-in-progress: true jobs: # packing android on ubuntu, macos slowly build-android: runs-on: ubuntu-latest permissions: write-all steps: # 签出代码 - uses: actions/checkout@v6 with: ref: dev # APK 签名设置 - name: Download Android keystore id: android_keystore uses: timheuer/base64-to-file@v1.2 with: fileName: keystore.jks encodedString: ${{ secrets.KEYSTORE_BASE64 }} - name: Create key.properties run: | echo "${{ secrets.FIREBASE_JSON }}" | base64 --decode > simple_live_app/android/app/google-services.json echo "${{ secrets.FIREBASE_OPTIONS_DART }}" | base64 --decode > simple_live_app/lib/firebase_options.dart echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > simple_live_app/android/key.properties echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> simple_live_app/android/key.properties echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> simple_live_app/android/key.properties echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> simple_live_app/android/key.properties # 设置 JAVA 环境 - uses: actions/setup-java@v5 with: distribution: "zulu" java-version: "17" cache: "gradle" cache-dependency-path: | simple_live_app/android/*.gradle* simple_live_app/android/**/gradle-wrapper.properties # 设置 Flutter - name: Flutter action uses: subosito/flutter-action@v2 with: channel: stable flutter-version-file: simple_live_app/pubspec.yaml cache: true # 更新 Flutter 的 packages - name: Restore packages run: | cd simple_live_app flutter pub get # 打包 APK - name: Build APK run: | cd simple_live_app flutter build apk --release --split-per-abi # 上传 Artifacts - name: Upload APK to Artifacts uses: actions/upload-artifact@v4 with: name: android path: | simple_live_app/build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk simple_live_app/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk simple_live_app/build/app/outputs/flutter-apk/app-x86_64-release.apk # 打包iOS、Mac build-mac-ios: # https://github.com/actions/runner-images/issues/12541 runs-on: macos-15 permissions: contents: write steps: # 签出代码 - uses: actions/checkout@v4 with: ref: dev # 设置 JAVA 环境 - uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: "21" cache: "gradle" # 设置 Flutter - name: Flutter action uses: subosito/flutter-action@v2 with: flutter-version-file: simple_live_app/pubspec.yaml cache: true # 打开 MAC Desktop 支持 - name: Enable Flutter Desktop run: flutter config --enable-macos-desktop # 更新 Flutter 的 packages - name: Restore packages run: | cd simple_live_app flutter pub get # 安装 appdmg npm install -g appdmg - name: Install appdmg run: npm install -g appdmg # 设置 Fastforge 环境 - name: Install fast-forge run: | git clone https://github.com/SlotSun/fastforge.git cd fastforge dart pub global activate melos melos run activate # 打包 iOS - name: Build IPA run: | cd simple_live_app flutter build ios --release --no-codesign # 创建未签名 ipa - name: Create IPA run: | cd simple_live_app mkdir build/ios/iphoneos/Payload cp -R build/ios/iphoneos/Runner.app build/ios/iphoneos/Payload/Runner.app cd build/ios/iphoneos/ zip -q -r ios_no_sign.ipa Payload cd ../../.. # 上传 IPA 至 Artifacts - name: Upload IPA to Artifacts uses: actions/upload-artifact@v4 with: name: ios path: | simple_live_app/build/ios/iphoneos/ios_no_sign.ipa # 打包 MAC - name: Build MacOS run: | cd simple_live_app fastforge package --platform macos --targets dmg,zip --skip-clean # 上传 MAC 至 Artifacts - name: Upload MacOS to Artifacts uses: actions/upload-artifact@v4 with: name: mac path: | simple_live_app/build/dist/*/*.dmg simple_live_app/build/dist/*/*.zip # 完成 - run: echo "🍏 This job's status is ${{ job.status }}." # 打包 Linux build-linux: strategy: matrix: include: - runner: ubuntu-24.04 arch: x86_64 - runner: ubuntu-24.04-arm arch: aarch64 runs-on: ${{ matrix.runner }} permissions: contents: write steps: # 签出代码 - uses: actions/checkout@v4 with: ref: dev # 设置 Flutter 环境 - name: Setup Flutter uses: subosito/flutter-action@v2 with: flutter-version-file: simple_live_app/pubspec.yaml channel: master # 安装依赖 - name: Update apt-get run: sudo apt-get update - name: Install Dependencies run: | sudo apt-get update sudo apt-get install -y clang cmake libgtk-3-dev unzip libasound2-dev gcc g++ autoconf automake \ debhelper glslang-dev ladspa-sdk xutils-dev libasound2-dev libarchive-dev libbluray-dev \ libdav1d-dev libdvdnav-dev libegl1-mesa-dev libepoxy-dev libfontconfig-dev libfreetype6-dev \ libfribidi-dev libgl1-mesa-dev libgbm-dev libgme-dev libgsm1-dev libharfbuzz-dev libjpeg-dev \ libbrotli-dev liblcms2-dev libmodplug-dev libmp3lame-dev libopenal-dev \ libopus-dev libopencore-amrnb-dev libopencore-amrwb-dev libpulse-dev librtmp-dev \ libsdl2-dev libsixel-dev libssh-dev libsoxr-dev libspeex-dev libtool \ libv4l-dev libva-dev libvdpau-dev libvorbis-dev libvo-amrwbenc-dev \ libunwind-dev libvpx-dev libwayland-dev libx11-dev libxext-dev libdrm-dev \ libxkbcommon-dev libxrandr-dev libxss-dev libxv-dev libxvidcore-dev \ linux-libc-dev nasm ninja-build pkg-config python3 python3-docutils wayland-protocols \ x11proto-core-dev zlib1g-dev libfdk-aac-dev libtheora-dev libwebp-dev \ unixodbc-dev libpq-dev libxxhash-dev libaom-dev libbs2b-dev libcaca-dev libcdio-paranoia-dev # 打开 Linux Desktop 支持 - name: Enable Flutter Desktop run: flutter config --enable-linux-desktop # 更新 Flutter 的 packages - name: Restore Packages run: | cd simple_live_app flutter pub get # 设置 Fastforge 环境 - name: Install fast-forge run: | git clone https://github.com/SlotSun/fastforge.git cd fastforge dart pub global activate melos melos run activate # build Linux DEB \ ZIP - name: Build Linux run: | cd simple_live_app fastforge package --platform linux --targets deb,zip --skip-clean cd build/dist/* mv simple_live_app-*-linux.deb Slive-${{ matrix.arch }}-linux.deb mv simple_live_app-*-linux.zip Slive-${{ matrix.arch }}-linux.zip # 上传 Linux 包至 Artifacts - name: Upload Linux APP to Artifacts uses: actions/upload-artifact@v4 with: name: linux-${{ matrix.arch }} path: | simple_live_app/build/dist/*/*.deb simple_live_app/build/dist/*/*.zip # 完成 - run: echo "🍏 Linux job's status is ${{ job.status }}." # 打包 Windows build-windows: runs-on: windows-latest permissions: contents: write steps: # 签出代码 - uses: actions/checkout@v4 with: ref: dev # 设置 Flutter 环境 - name: Setup Flutter uses: subosito/flutter-action@v2 with: flutter-version-file: simple_live_app/pubspec.yaml cache: true - name: Enable Flutter Desktop run: flutter config --enable-windows-desktop - name: Restore Packages run: | cd simple_live_app flutter clean flutter pub get # 设置 Fastforge 环境 和 Inno Setup - name: Install fast-forge and Inno Setup run: | git clone https://github.com/SlotSun/fastforge.git cd fastforge dart pub global activate melos melos run activate choco install innosetup # 添加中文安装引导 - name: Add Chinese language file for Inno Setup run: | cd simple_live_app Copy-Item "windows/packaging/exe/ChineseSimplified.isl" "C:\Program Files (x86)\Inno Setup 6\Languages\ChineseSimplified.isl" shell: powershell # build Windows ZIP\MSIX\EXE - name: Build Windows run: | cd simple_live_app fastforge package --platform windows --targets msix,zip,exe --skip-clean cd build/dist/* Get-ChildItem "simple_live_app-*" | ForEach-Object { $newName = $_.Name -replace '^simple_live_app', 'Slive' Rename-Item $_ -NewName $newName } shell: pwsh # 上传 Windows 至 Artifacts - name: Upload Windows APP to Artifacts uses: actions/upload-artifact@v4 with: name: windows path: | simple_live_app/build/dist/*/*.msix simple_live_app/build/dist/*/*.zip simple_live_app/build/dist/*/*.exe # 完成 - run: echo "🍏 Windows job's status is ${{ job.status }}." # 打包 Flatpak build-flatpak: strategy: matrix: include: - runner: ubuntu-24.04 arch: x86_64 - runner: ubuntu-24.04-arm arch: aarch64 runs-on: ${{ matrix.runner }} permissions: contents: write steps: # 签出代码 - uses: actions/checkout@v4 with: ref: dev # 安装依赖 - name: Install dependencies run: | sudo add-apt-repository -y ppa:flatpak/stable sudo apt-get update -q sudo apt-get install -y flatpak flatpak-builder ca-certificates gnupg software-properties-common flatpak remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo git config --global protocol.file.allow always # 构建 Flatpak bundle - name: Build flatpak bundle run: | flatpak-builder build-dir flatpak/flatpak-manifest.yaml \ --arch=${{ matrix.arch }} \ --repo=${{ matrix.arch }} \ --force-clean \ --user \ --install-deps-from=flathub \ --ccache \ --disable-rofiles-fuse flatpak build-bundle ${{ matrix.arch }} Slive-${{ matrix.arch }}.flatpak io.github.SlotSun.Slive # 上传 Flatpak 包至 Artifacts - name: Upload linux flatpak to artifacts uses: actions/upload-artifact@v4 with: name: linux-flatpak-${{ matrix.arch }} path: Slive-*.flatpak # 完成 - run: echo "🍏 Flatpak job's status is ${{ job.status }}." ================================================ FILE: .github/workflows/publish_app_release.yml ================================================ name: app-build-action-release # 推送 Tag 时触发 on: workflow_dispatch: # 允许手动触发 push: tags: - "v*" jobs: # 打包 Android build-android: runs-on: ubuntu-latest permissions: write-all steps: # 签出代码 - uses: actions/checkout@v6 # APK 签名设置 - name: Download Android keystore id: android_keystore uses: timheuer/base64-to-file@v1.2 with: fileName: keystore.jks encodedString: ${{ secrets.KEYSTORE_BASE64 }} - name: Create key.properties run: | echo "${{ secrets.FIREBASE_JSON }}" | base64 --decode > simple_live_app/android/app/google-services.json echo "${{ secrets.FIREBASE_OPTIONS_DART }}" | base64 --decode > simple_live_app/lib/firebase_options.dart echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > simple_live_app/android/key.properties echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> simple_live_app/android/key.properties echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> simple_live_app/android/key.properties echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> simple_live_app/android/key.properties # 设置 JAVA 环境 - uses: actions/setup-java@v5 with: distribution: "zulu" java-version: "17" cache: "gradle" cache-dependency-path: | simple_live_app/android/*.gradle* simple_live_app/android/**/gradle-wrapper.properties # 设置 Flutter - name: Flutter action uses: subosito/flutter-action@v2 with: channel: stable flutter-version-file: simple_live_app/pubspec.yaml cache: true # 更新 Flutter 的 packages - name: Restore packages run: | cd simple_live_app flutter pub get # 打包 APK - name: Build APK run: | cd simple_live_app flutter build apk --release --split-per-abi # 上传 APK 至 Artifacts - name: Upload APK to Artifacts uses: actions/upload-artifact@v4 with: name: android path: | simple_live_app/build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk simple_live_app/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk simple_live_app/build/app/outputs/flutter-apk/app-x86_64-release.apk # 读取版本信息 - name: Read version id: version uses: juliangruber/read-file-action@v1 with: path: assets/app_version.json - name: Echo version run: echo "${{ fromJson(steps.version.outputs.content).version }}" - name: Echo version content run: echo "${{ fromJson(steps.version.outputs.content).version_desc }}" # 上传至 Release - name: Upload Release uses: softprops/action-gh-release@v1 with: name: "${{ fromJson(steps.version.outputs.content).version }}" body: "${{ fromJson(steps.version.outputs.content).version_desc }}" prerelease: ${{ fromJson(steps.version.outputs.content).prerelease }} token: ${{ secrets.TOKEN }} files: | simple_live_app/build/app/outputs/flutter-apk/app-x86_64-release.apk simple_live_app/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk simple_live_app/build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk # 完成 - run: echo "🍏 This job's status is ${{ job.status }}." # 打包 Linux build-linux: strategy: matrix: include: - runner: ubuntu-24.04 arch: x86_64 - runner: ubuntu-24.04-arm arch: aarch64 runs-on: ${{ matrix.runner }} permissions: contents: write steps: # 签出代码 - uses: actions/checkout@v4 # 设置 Flutter 环境 - name: Setup Flutter uses: subosito/flutter-action@v2 with: flutter-version-file: simple_live_app/pubspec.yaml channel: master # 安装依赖 - name: Update apt-get run: sudo apt-get update - name: Install Dependencies run: | sudo apt-get update sudo apt-get install -y clang cmake libgtk-3-dev unzip libasound2-dev gcc g++ autoconf automake \ debhelper glslang-dev ladspa-sdk xutils-dev libasound2-dev libarchive-dev libbluray-dev \ libdav1d-dev libdvdnav-dev libegl1-mesa-dev libepoxy-dev libfontconfig-dev libfreetype6-dev \ libfribidi-dev libgl1-mesa-dev libgbm-dev libgme-dev libgsm1-dev libharfbuzz-dev libjpeg-dev \ libbrotli-dev liblcms2-dev libmodplug-dev libmp3lame-dev libopenal-dev \ libopus-dev libopencore-amrnb-dev libopencore-amrwb-dev libpulse-dev librtmp-dev \ libsdl2-dev libsixel-dev libssh-dev libsoxr-dev libspeex-dev libtool \ libv4l-dev libva-dev libvdpau-dev libvorbis-dev libvo-amrwbenc-dev \ libunwind-dev libvpx-dev libwayland-dev libx11-dev libxext-dev libdrm-dev \ libxkbcommon-dev libxrandr-dev libxss-dev libxv-dev libxvidcore-dev \ linux-libc-dev nasm ninja-build pkg-config python3 python3-docutils wayland-protocols \ x11proto-core-dev zlib1g-dev libfdk-aac-dev libtheora-dev libwebp-dev \ unixodbc-dev libpq-dev libxxhash-dev libaom-dev libbs2b-dev libcaca-dev libcdio-paranoia-dev # 打开 Linux Desktop 支持 - name: Enable Flutter Desktop run: flutter config --enable-linux-desktop # 更新 Flutter 的 packages - name: Restore Packages run: | cd simple_live_app flutter pub get # 设置 Fastforge 环境 - name: Install fast-forge run: | git clone https://github.com/SlotSun/fastforge.git cd fastforge dart pub global activate melos melos run activate # build Linux DEB\ZIP - name: Build Linux run: | cd simple_live_app fastforge package --platform linux --targets deb,zip --skip-clean cd build/dist/* mv simple_live_app-*-linux.deb Slive-${{ matrix.arch }}-linux.deb mv simple_live_app-*-linux.zip Slive-${{ matrix.arch }}-linux.zip # 上传 Linux 包至 Artifacts - name: Upload Linux APP to Artifacts uses: actions/upload-artifact@v4 with: name: linux-${{ matrix.arch }} path: | simple_live_app/build/dist/*/*.deb simple_live_app/build/dist/*/*.zip # 读取版本信息 - name: Read version id: version uses: juliangruber/read-file-action@v1 with: path: assets/app_version.json - name: Echo version run: echo "${{ fromJson(steps.version.outputs.content).version }}" - name: Echo version content run: echo "${{ fromJson(steps.version.outputs.content).version_desc }}" # 上传至Release - name: Upload Release uses: softprops/action-gh-release@v1 with: name: "${{ fromJson(steps.version.outputs.content).version }}" body: "${{ fromJson(steps.version.outputs.content).version_desc }}" prerelease: ${{ fromJson(steps.version.outputs.content).prerelease }} token: ${{ secrets.TOKEN }} files: | simple_live_app/build/dist/*/*.deb simple_live_app/build/dist/*/*.zip # 完成 - run: echo "🍏 Linux job's status is ${{ job.status }}." # 打包Windows build-windows: runs-on: windows-latest permissions: contents: write steps: # 签出代码 - uses: actions/checkout@v4 # 设置 Flutter 环境 - name: Setup Flutter uses: subosito/flutter-action@v2 with: flutter-version-file: simple_live_app/pubspec.yaml cache: true channel: "stable" - name: Enable Flutter Desktop run: flutter config --enable-windows-desktop - name: Restore Packages run: | cd simple_live_app flutter pub get # 设置 Fastforge 环境 和 Inno Setup - name: Install Fastforge and Inno Setup run: | git clone https://github.com/SlotSun/fastforge.git cd fastforge dart pub global activate melos melos run activate choco install innosetup # 添加中文安装引导 - name: Add Chinese language file for Inno Setup run: | cd simple_live_app Copy-Item "windows/packaging/exe/ChineseSimplified.isl" "C:\Program Files (x86)\Inno Setup 6\Languages\ChineseSimplified.isl" shell: powershell # build Windows ZIP\MSIX\EXE - name: Build Windows run: | cd simple_live_app fastforge package --platform windows --targets msix,zip,exe --skip-clean cd build/dist/* Get-ChildItem "simple_live_app-*" | ForEach-Object { $newName = $_.Name -replace '^simple_live_app', 'Slive' Rename-Item $_ -NewName $newName } shell: pwsh # 上传 Windows 至 Artifacts - name: Upload Windows APP to Artifacts uses: actions/upload-artifact@v4 with: name: windows path: | simple_live_app/build/dist/*/*.msix simple_live_app/build/dist/*/*.zip simple_live_app/build/dist/*/*.exe # 读取版本信息 - name: Read version id: version uses: juliangruber/read-file-action@v1 with: path: assets/app_version.json - name: Echo version run: echo "${{ fromJson(steps.version.outputs.content).version }}" - name: Echo version content run: echo "${{ fromJson(steps.version.outputs.content).version_desc }}" # 上传至 Release - name: Upload Release uses: softprops/action-gh-release@v1 with: name: "${{ fromJson(steps.version.outputs.content).version }}" body: "${{ fromJson(steps.version.outputs.content).version_desc }}" prerelease: ${{ fromJson(steps.version.outputs.content).prerelease }} token: ${{ secrets.TOKEN }} files: | simple_live_app/build/dist/*/*.msix simple_live_app/build/dist/*/*.zip simple_live_app/build/dist/*/*.exe # 完成 - run: echo "🍏 Windows job's status is ${{ job.status }}." # 打包 Flatpak build-flatpak: strategy: matrix: include: - runner: ubuntu-24.04 arch: x86_64 - runner: ubuntu-24.04-arm arch: aarch64 runs-on: ${{ matrix.runner }} permissions: contents: write steps: # 签出代码 - uses: actions/checkout@v4 # 安装依赖 - name: Install dependencies run: | sudo add-apt-repository -y ppa:flatpak/stable sudo apt-get update -q sudo apt-get install -y flatpak flatpak-builder ca-certificates gnupg software-properties-common flatpak remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo git config --global protocol.file.allow always # 构建 Flatpak bundle - name: Build flatpak bundle run: | flatpak-builder build-dir flatpak/flatpak-manifest.yaml \ --arch=${{ matrix.arch }} \ --repo=${{ matrix.arch }} \ --force-clean \ --user \ --install-deps-from=flathub \ --ccache \ --disable-rofiles-fuse flatpak build-bundle ${{ matrix.arch }} Slive-${{ matrix.arch }}.flatpak io.github.SlotSun.Slive # 上传 Flatpak 包至 Artifacts - name: Upload linux flatpak to artifacts uses: actions/upload-artifact@v4 with: name: linux-flatpak-${{ matrix.arch }} path: Slive-*.flatpak # 读取版本信息 - name: Read version id: version uses: juliangruber/read-file-action@v1 with: path: assets/app_version.json - name: Echo version run: echo "${{ fromJson(steps.version.outputs.content).version }}" - name: Echo version content run: echo "${{ fromJson(steps.version.outputs.content).version_desc }}" # 上传至 Release - name: Upload Release uses: softprops/action-gh-release@v1 with: name: "${{ fromJson(steps.version.outputs.content).version }}" body: "${{ fromJson(steps.version.outputs.content).version_desc }}" prerelease: ${{ fromJson(steps.version.outputs.content).prerelease }} token: ${{ secrets.TOKEN }} files: Slive-*.flatpak # 完成 - run: echo "🍏 Flatpak job's status is ${{ job.status }}." ================================================ FILE: .github/workflows/publish_tv_app_release.yaml ================================================ name: app-tv-build-action-release # 手动触发 on: workflow_dispatch: jobs: build-tv: runs-on: macos-latest permissions: contents: write steps: # 签出代码 - uses: actions/checkout@v4 with: ref: master # APK 签名设置 - name: Download Android keystore id: android_tv_keystore uses: timheuer/base64-to-file@v1.2 with: fileName: keystore.jks encodedString: ${{ secrets.TV_KEYSTORE_BASE64 }} - name: Create key.properties run: | echo "storeFile=${{ steps.android_tv_keystore.outputs.filePath }}" > simple_live_tv_app/android/key.properties echo "storePassword=${{ secrets.TV_STORE_PASSWORD }}" >> simple_live_tv_app/android/key.properties echo "keyPassword=${{ secrets.TV_KEY_PASSWORD }}" >> simple_live_tv_app/android/key.properties echo "keyAlias=${{ secrets.TV_KEY_ALIAS }}" >> simple_live_tv_app/android/key.properties # 设置 JAVA 环境 - uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: "17" cache: "gradle" # 设置 Flutter - name: Flutter action uses: subosito/flutter-action@v2 with: flutter-version-file: simple_live_tv_app/pubspec.yaml cache: true # 更新 Flutter 的 packages - name: Restore packages run: | cd simple_live_tv_app flutter pub get # 打包 APK - name: Build APK run: | cd simple_live_tv_app flutter build apk --release --split-per-abi # 上传 APK 至 Artifacts - name: Upload APK to Artifacts uses: actions/upload-artifact@v3 with: name: android_tv path: | simple_live_tv_app/build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk simple_live_tv_app/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk simple_live_tv_app/build/app/outputs/flutter-apk/app-x86_64-release.apk # 读取版本信息 - name: Read version id: version uses: juliangruber/read-file-action@v1 with: path: assets/tv_app_version.json - name: Echo version run: echo "${{ fromJson(steps.version.outputs.content).version }}" - name: Echo version content run: echo "${{ fromJson(steps.version.outputs.content).version_desc }}" # 上传至 Release - name: Upload Release uses: softprops/action-gh-release@v1 with: name: "${{ fromJson(steps.version.outputs.content).version }}" body: "# Android TV \n${{ fromJson(steps.version.outputs.content).version_desc }}" prerelease: ${{ fromJson(steps.version.outputs.content).prerelease }} token: ${{ secrets.TOKEN }} files: | simple_live_tv_app/build/app/outputs/flutter-apk/app-x86_64-release.apk simple_live_tv_app/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk simple_live_tv_app/build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk # 完成 - run: echo "🍏 This job's status is ${{ job.status }}." ================================================ FILE: .gitignore ================================================ simple_live_app/.vscode/settings.json simple_live_tv_app/.vscode/settings.json .idea/ .vscode/ .vercel/ .flatpak-builder/ .crush/ CRUSH.md build/ dev-tool.ps1 *.py ================================================ FILE: CONTRIBUTING.md ================================================ # 开发贡献指南 / Contributing Guide 感谢你对 Slive 的关注!欢迎各种形式的贡献,无论是修复 bug、添加新功能、改进文档还是提出建议。 ## 贡献须知 - 因为一些原因,**dev** 作为Github Flow开发流程的主干 - **master** 分支发版时 通过 merge squash dev,会在停更时删除dev分支并脱敏 - 所以仓库的贡献者包括贡献代码统计都是不存在的 ## 🎯 贡献类型 欢迎以下类型的贡献: ### 🐛 修复 Bug - 在 Issues 中查找标记为 `bug` 的问题 - 如果发现新的 bug,请先[创建 Issue](https://github.com/SlotSun/dart_simple_live/issues/new/choose) 描述问题 ### ✨ 添加新功能 - 查找标记为 `enhancement`的 Issue - 对于较大的功能,建议先创建 Issue 讨论设计方案 ### 📖 改进文档 - 修复文档中的错误或不清晰的地方 - 添加使用示例和最佳实践 ### 🧪 添加测试 - 为现有功能添加单元测试 - 改进测试覆盖率 - 添加集成测试 ### 🎨 UI/UX 改进 - 优化界面交互体验 - 修复界面样式问题 - 提升易用性 ## 🚀 快速开始 ### 前置要求 确保你的环境中已安装: - **Flutter 3.38.6^** - **Rust 1.28^** - **Visual Studio** ### 安装 FVM ```bash # macOS/Linux curl -sL https://install.fvm.sh | bash # Windows choco install fvm ``` ### 安装 Flutter ```bash # windows fvm version fvm list fvm install 3.38.6 ``` **Linux** [详见官网教程](https://docs.flutter.dev/install/manual) ### 克隆仓库 ```bash git clone https://github.com/SlotSun/dart_simple_live.git cd dart_simple_live/simple_live_app ``` ## 🏷️ 认领任务 ### 查找任务 1. 浏览 [Issues 页面](https://github.com/SlotSun/dart_simple_live/issues) 2. 查找带有以下标签的 Issue: - `help wanted` - 需要社区帮助的任务 - `bug` - Bug 修复 - `enhancement` - 功能增强 ### 认领流程 在你想要处理的 Issue 下评论: ``` /assign me ``` 或者: ``` 我可以解决此问题 ``` ## 💻 本地开发 ### 1. 安装依赖 **Flutter依赖**: ```bash fvm use 3.38.6 # 安装Flutter_rust_bridge cargo install flutter_rust_bridge_codegen flutter pub get ``` ### 2. 启动开发服务器 **启动APP**(支持热重载): ```bash flutter run ``` ### 3. 构建项目 **安装 fastforge** ```bash git clone https://github.com/SlotSun/fastforge.git cd fastforge dart pub global activate melos melos run activate ``` **Windows-安装 innosetup** ```bash # 打包 windows choco install innosetup ``` **构建APP**: ```bash # bash in simple_live_app cd simple_live_app # 打包 windows fastforge package --platform windows --targets msix,zip,exe --skip-clean # Linux fastforge package --platform linux --targets deb,zip --skip-clean # Android-need key flutter build apk --release --split-per-abi ``` ## 📁 代码结构 ### 项目目录速览 - `simple_live_core` 项目核心库,实现获取各个网站的信息及弹幕。 - `simple_live_console` 基于simple_live_core的控制台程序。 - `simple_live_app` 基于核心库实现的Flutter APP客户端。 - `simple_live_tv_app` 基于核心库实现的Flutter Android TV客户端。 ### 技术栈 **Flutter**: - GetX(状态管理、路由) - Hive(持久化) - Dio(实时通信) ## 📝 提交规范 我们遵循 [Conventional Commits](https://www.conventionalcommits.org/) 规范: ### Commit 格式 ``` <类型>(<范围>): <简短描述> [可选的详细描述] [可选的脚注] ``` ### 类型 (Type) - `feat`: 新功能 - `fix`: Bug 修复 - `docs`: 文档变更 - `style`: 代码格式调整(不影响功能) - `refactor`: 重构(既不是新功能也不是 bug 修复) - `perf`: 性能优化 - `test`: 添加或修改测试 - `chore`: 辅助工具的变动 - `ci`: 构建过程的变动 ### 范围 (Scope) 可选,指明改动的模块: - `platfrom` - 直播平台 - `Windows/Linux/Android` - 用户平台 - `ui` - 用户界面 - `ci` - 构建系统 - `docs` - 文档 ### 示例 ```bash feat(api): add layered agent support ``` ## 🔍 Pull Request 要求 ### PR 流程 ```bash git clone https://github.com/SlotSun/dart_simple_live.git # 基于dev分支创建 Type-Scope的分支 git checkout -b fix-xx origin/dev ``` **完成测试后,向主仓库的dev分支提交pr** ### 提交前检查清单 - [ ] 代码通过格式检查 - [ ] 对于新功能,添加了相应的文档说明 - [ ] 对于 UI 改动,提供了截图或录屏 - [ ] Commit 信息遵循规范格式 - [ ] PR 描述清晰说明了改动内容和原因 ### PR 标题格式 PR 标题应该简洁明了,建议格式: ``` <类型>: <简短描述> ``` 例如: - `feat: add WiFi device pairing support` - `fix: resolve video stream crash on device disconnect` - `docs: improve quick start guide` ### PR 描述模板 提交 PR 时,请使用以下模板: ```markdown ## 改动说明 ## 相关 Issue ## 改动类型 - [ ] Bug 修复 - [ ] 新功能 - [ ] 文档更新 - [ ] 代码重构 - [ ] 性能优化 - [ ] 其他(请说明) ## 测试说明 ## 截图/录屏 ## 其他说明 ``` ### Code Review 提交 PR 后: 1. 维护者会审查你的代码 2. 可能会提出改进建议 3. 请及时回复评论并根据反馈调整代码 4. 所有讨论解决后,PR 会被合并 ## 🤝 行为准则 ### 我们的承诺 为了营造一个开放和友好的环境,我们承诺: - 尊重不同的观点和经验 - 优雅地接受建设性批评 - 关注对社区最有利的事情 - 对其他社区成员表示同理心 ### 不可接受的行为 以下行为被视为不可接受: - 使用性别化的语言或图像,以及不受欢迎的性关注 - 骚扰性评论、侮辱性/贬损性评论,以及人身或政治攻击 - 公开或私下骚扰 - 未经明确许可,发布他人的私人信息 - 其他在专业环境中可能被认为不适当的行为 ## 🎉 感谢贡献 感谢你花时间为 Slive 做出贡献!你的努力让这个项目变得更好。 有任何问题?欢迎: - 在 [Issues](https://github.com/SlotSun/dart_simple_live/issues) 提问 --- ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================

Simple Live logo

Slive

我就默默看你表演

![浅色模式](/assets/screenshot_light.jpg) ![深色模式](/assets/screenshot_dark.jpg) ## 支持直播平台: - 虎牙直播 - 斗鱼直播 - 哔哩哔哩直播 - 抖音直播 ## APP支持平台 - [x] Android - [x] Windows - [x] Linux - [x] iOS `自测` - [x] MacOS `自测` - [ ] Android TV `请自行打包` [说明](https://github.com/SlotSun/dart_simple_live/issues/89) #### Arch Linux: ```bash yay -S slive yay -S slive-bin ``` 只保证Android, Linux和Windows可用性 请到[Releases](https://github.com/slotsun/dart_simple_live/releases)下载最新版本,iOS请到上游或者action下载体验 如果想体验最新功能,可前往[Actions](https://github.com/slotsun/dart_simple_live/actions)下载自动打包的开发版本 Windows建议下载UWP版[聚合直播](https://www.microsoft.com/store/apps/9N1TWG2G84VD),体验会更好 ## 项目结构 - `simple_live_core` 项目核心库,实现获取各个网站的信息及弹幕。 - `simple_live_console` 基于simple_live_core的控制台程序。 - `simple_live_app` 基于核心库实现的Flutter APP客户端。 - `simple_live_tv_app` 基于核心库实现的Flutter Android TV客户端。 ## 环境 flutter 3.38.6 ## 参考及引用 [AllLive](https://github.com/xiaoyaocz/AllLive) `本项目的C#版,有兴趣可以看看` [dart_tars_protocol](https://github.com/xiaoyaocz/dart_tars_protocol.git) [lovelyyoshino/Bilibili-Live-API](https://github.com/lovelyyoshino/Bilibili-Live-API/blob/master/API.WebSocket.md) [IsoaSFlus/danmaku](https://github.com/IsoaSFlus/danmaku) [BacooTang/huya-danmu](https://github.com/BacooTang/huya-danmu) [TarsCloud/Tars](https://github.com/TarsCloud/Tars) [5ime/Tiktok_Signature](https://github.com/5ime/Tiktok_Signature) [biliup](https://github.com/biliup/biliup) ## 声明 本项目的所有功能都是基于互联网上公开的资料开发,无任何破解、逆向工程等行为。 本项目仅用于学习交流编程技术,严禁将本项目用于商业目的。如有任何商业行为,均与本项目无关。 如果本项目存在侵犯您的合法权益的情况,请及时与开发者联系,开发者将会及时删除有关内容。 ================================================ FILE: assets/app_version.json ================================================ { "version": "1.8.7", "version_num": 10807, "version_desc": "- 一个非常重要的过渡版本 \n- 修复huya搜索 @GH4NG \n- 引入Firebase \n- 大量数据错误和潜在问题修复 \n- 一些细节调整 \n- 关于linux的一系列修复 @pugaizai \n- tips: ios和macos用户请到action更新测试或者下载上游仓库版本", "prerelease": true, "download_url": "https://github.com/SlotSun/dart_simple_live/releases" } ================================================ FILE: assets/tv_app_version.json ================================================ { "version": "1.5.0", "version_num": 10500, "version_desc": "- 永远的1.5.0", "prerelease":true, "download_url": "https://github.com/xiaoyaocz/dart_simple_live/releases" } ================================================ FILE: assets/update-linux-metainfo.dart ================================================ import 'dart:convert'; import 'dart:io'; String _xmlEscape(String s) => s .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); void main() { final projectRoot = Directory.current.path; final jsonPath = '$projectRoot/assets/app_version.json'; final targetPath = '$projectRoot/simple_live_app/assets/io.github.SlotSun.Slive.metainfo.xml'; final jsonFile = File(jsonPath); if (!jsonFile.existsSync()) { stderr.writeln('app_version.json not found: $jsonPath'); exit(2); } final targetFile = File(targetPath); if (!targetFile.existsSync()) { stderr.writeln('target file not found: $targetPath'); exit(2); } final map = json.decode(jsonFile.readAsStringSync()); final version = (map['version'] ?? '').toString(); final desc = (map['version_desc'] ?? '').toString(); final date = DateTime.now().toIso8601String().split('T').first; final releaseBlock = [ ' ', ' ', for (final line in desc.split('\n')) '

${_xmlEscape(line.replaceAll("\r", ""))}

', '
', '
', ].join('\n'); final content = targetFile.readAsStringSync(); final releasesReg = RegExp( r'(]*>)([\s\S]*?)( )', multiLine: true, dotAll: true, ); // 直接替换第一个 区块(如果不存在则 content 保持不变) final newContent = content.replaceFirstMapped( releasesReg, (m) => '${m.group(1)}\n$releaseBlock\n${m.group(3)}', ); targetFile.writeAsStringSync(newContent); stdout.writeln('Update Done: $targetPath'); } ================================================ FILE: docs/.gitignore ================================================ /node_modules .idea .vitepress/cache ================================================ FILE: docs/.vitepress/config.mts ================================================ import { defineConfig } from 'vitepress' // https://vitepress.dev/reference/site-config export default defineConfig({ base: '/dart_simple_live/', title: "Slive Doc", head: [ ["link", { rel: "icon", href: "/favicon.ico" }], ], description: "Slive Doc", themeConfig: { // https://vitepress.dev/reference/default-theme-config nav: [ { text: 'Home', link: '/' }, { text: '开发指南', link: '/contributing' } ], sidebar: [ { text: '文档', items: [ { text: '开发指南', link: '/contributing' }, { text: '用户指南', link: '/user' } ] } ], socialLinks: [ { icon: 'github', link: 'https://github.com/SlotSun/dart_simple_live' } ] } }) ================================================ FILE: docs/README.md ================================================ # Slive Docs 基于 VitePress ## 部署 安装依赖 ```shell pnpm install ``` 运行 ```shell pnpm run docs:dev ``` ================================================ FILE: docs/contributing.md ================================================ # 开发贡献指南 / Contributing Guide 感谢你对 Slive 的关注!欢迎各种形式的贡献,无论是修复 bug、添加新功能、改进文档还是提出建议。 ## 贡献须知 - 因为一些原因,**dev** 作为Github Flow开发流程的主干 - **master** 分支发版时 通过 merge squash dev,会在停更时删除dev分支并脱敏 - 所以仓库的贡献者包括贡献代码统计都是不存在的 ## 🎯 贡献类型 欢迎以下类型的贡献: ### 🐛 修复 Bug - 在 Issues 中查找标记为 `bug` 的问题 - 如果发现新的 bug,请先[创建 Issue](https://github.com/SlotSun/dart_simple_live/issues/new/choose) 描述问题 ### ✨ 添加新功能 - 查找标记为 `enhancement`的 Issue - 对于较大的功能,建议先创建 Issue 讨论设计方案 ### 📖 改进文档 - 修复文档中的错误或不清晰的地方 - 添加使用示例和最佳实践 ### 🧪 添加测试 - 为现有功能添加单元测试 - 改进测试覆盖率 - 添加集成测试 ### 🎨 UI/UX 改进 - 优化界面交互体验 - 修复界面样式问题 - 提升易用性 ## 🚀 快速开始 ### 前置要求 确保你的环境中已安装: - **Flutter 3.38.6^** - **Rust 1.28^** - **Visual Studio** ### 安装 FVM ```bash # macOS/Linux curl -sL https://install.fvm.sh | bash # Windows choco install fvm ``` ### 安装 Flutter ```bash # windows fvm version fvm list fvm install 3.38.6 ``` **Linux** [详见官网教程](https://docs.flutter.dev/install/manual) ### 克隆仓库 ```bash git clone https://github.com/SlotSun/dart_simple_live.git cd dart_simple_live/simple_live_app ``` ## 🏷️ 认领任务 ### 查找任务 1. 浏览 [Issues 页面](https://github.com/SlotSun/dart_simple_live/issues) 2. 查找带有以下标签的 Issue: - `help wanted` - 需要社区帮助的任务 - `bug` - Bug 修复 - `enhancement` - 功能增强 ### 认领流程 在你想要处理的 Issue 下评论: ``` /assign me ``` 或者: ``` 我可以解决此问题 ``` ## 💻 本地开发 ### 1. 安装依赖 **Flutter依赖**: ```bash fvm use 3.38.6 # 安装Flutter_rust_bridge cargo install flutter_rust_bridge_codegen flutter pub get ``` ### 2. 启动开发服务器 **启动APP**(支持热重载): ```bash flutter run ``` ### 3. 构建项目 **安装 fastforge** ```bash git clone https://github.com/SlotSun/fastforge.git cd fastforge dart pub global activate melos melos run activate ``` **Windows-安装 innosetup** ```bash # 打包 windows choco install innosetup ``` **构建APP**: ```bash # bash in simple_live_app cd simple_live_app # 打包 windows fastforge package --platform windows --targets msix,zip,exe --skip-clean # Linux fastforge package --platform linux --targets deb,zip --skip-clean # Android-need key flutter build apk --release --split-per-abi ``` ## 📁 代码结构 ### 项目目录速览 - `simple_live_core` 项目核心库,实现获取各个网站的信息及弹幕。 - `simple_live_console` 基于simple_live_core的控制台程序。 - `simple_live_app` 基于核心库实现的Flutter APP客户端。 - `simple_live_tv_app` 基于核心库实现的Flutter Android TV客户端。 ### 技术栈 **Flutter**: - GetX(状态管理、路由) - Hive(持久化) - Dio(实时通信) ## 📝 提交规范 我们遵循 [Conventional Commits](https://www.conventionalcommits.org/) 规范: ### Commit 格式 ``` <类型>(<范围>): <简短描述> [可选的详细描述] [可选的脚注] ``` ### 类型 (Type) - `feat`: 新功能 - `fix`: Bug 修复 - `docs`: 文档变更 - `style`: 代码格式调整(不影响功能) - `refactor`: 重构(既不是新功能也不是 bug 修复) - `perf`: 性能优化 - `test`: 添加或修改测试 - `chore`: 辅助工具的变动 - `ci`: 构建过程的变动 ### 范围 (Scope) 可选,指明改动的模块: - `platfrom` - 直播平台 - `Windows/Linux/Android` - 用户平台 - `ui` - 用户界面 - `ci` - 构建系统 - `docs` - 文档 ### 示例 ```bash feat(api): add layered agent support ``` ## 🔍 Pull Request 要求 ### PR 流程 ```bash git clone https://github.com/SlotSun/dart_simple_live.git # 基于dev分支创建 Type-Scope的分支 git checkout -b fix-xx origin/dev ``` **完成测试后,向主仓库的dev分支提交pr** ### 提交前检查清单 - [ ] 代码通过格式检查 - [ ] 对于新功能,添加了相应的文档说明 - [ ] 对于 UI 改动,提供了截图或录屏 - [ ] Commit 信息遵循规范格式 - [ ] PR 描述清晰说明了改动内容和原因 ### PR 标题格式 PR 标题应该简洁明了,建议格式: ``` <类型>: <简短描述> ``` 例如: - `feat: add WiFi device pairing support` - `fix: resolve video stream crash on device disconnect` - `docs: improve quick start guide` ### PR 描述模板 提交 PR 时,请使用以下模板: ```markdown ## 改动说明 ## 相关 Issue ## 改动类型 - [ ] Bug 修复 - [ ] 新功能 - [ ] 文档更新 - [ ] 代码重构 - [ ] 性能优化 - [ ] 其他(请说明) ## 测试说明 ## 截图/录屏 ## 其他说明 ``` ### Code Review 提交 PR 后: 1. 维护者会审查你的代码 2. 可能会提出改进建议 3. 请及时回复评论并根据反馈调整代码 4. 所有讨论解决后,PR 会被合并 ## 🤝 行为准则 ### 我们的承诺 为了营造一个开放和友好的环境,我们承诺: - 尊重不同的观点和经验 - 优雅地接受建设性批评 - 关注对社区最有利的事情 - 对其他社区成员表示同理心 ### 不可接受的行为 以下行为被视为不可接受: - 使用性别化的语言或图像,以及不受欢迎的性关注 - 骚扰性评论、侮辱性/贬损性评论,以及人身或政治攻击 - 公开或私下骚扰 - 未经明确许可,发布他人的私人信息 - 其他在专业环境中可能被认为不适当的行为 ## 🎉 感谢贡献 感谢你花时间为 Slive 做出贡献!你的努力让这个项目变得更好。 有任何问题?欢迎: - 在 [Issues](https://github.com/SlotSun/dart_simple_live/issues) 提问 --- ================================================ FILE: docs/index.md ================================================ --- # https://vitepress.dev/reference/default-theme-home-page layout: home hero: name: "Slive Docs" text: "我就默默看你表演" tagline: 基于Flutter开发的直播聚合软件 actions: - theme: brand text: 开发指南 link: /contributing - theme: alt text: 用户指南 link: /user features: - title: 目标 details: 只需要一个软件就可以观看多个直播平台 - title: 直播平台支持 details: BiliBili/Douyu/Huya/Douyin - title: 平台支持 details: Android/Windows/Linux --- ================================================ FILE: docs/package.json ================================================ { "name": "Slive Docs", "version": "1.0.0", "devDependencies": { "vitepress": "2.0.0-alpha.15" }, "scripts": { "docs:dev": "vitepress dev", "docs:build": "vitepress build", "docs:preview": "vitepress preview" }, "dependencies": { "Slive Docs": "link:" } } ================================================ FILE: docs/user.md ================================================ # 用户指南 / User Guide # 必知必看 ## 提交问题前必知必看 - 随着用户数增多,问题反馈数增多,作者时间有限不能逐个回复还请见谅 - 与之伴生的是,很多人不能正确反馈问题导致作者不能明确复现并定位问题,浪费了作者有限的休息时间 - 一定要有一个共识,开源社区开源软件作者是在无偿贡献自己的业余时间和精力,出于技术学习和交流目的,是在作者自认为是正常人的认知范围内进行技术开发 - 更要明白,你的私人需求不一定能够得到满足,问题的解决是有优先级的 - 一定要先搜索仓库内是否已经有类似问题 - 一定要详细正确填写设备型号,系统信息 - 多次可复现后录大小低于10mb的视频 - 详细说明复现过程 - 例如观看直播问题,具体在什么手机什么系统什么直播平台什么房间使用什么流,出现了什么问题,使用硬解,硬解参数是怎么设置的,开启log后发送,无log不解决 - 例如非直播问题,具体在什么系统什么界面使用什么功能,如何操作导致此问题,有无排查硬件兼容,软件互斥,鼠标失灵,键盘卡键等自身问题 - 提出问题前使用AI或搜索引擎尝试解决并归纳问题 - 其余我还没想到但可能提高解决问题效率的注意事项 ## 风控问题 - 不要快速手动刷新,一定要把更新线程切换到4 - 尽量减少搜索使用频率,最好使用链接解析,很多平台的不准确搜索涉及到推荐系统模型;普通接口CDN很快,搜索走内部系统一次的成本大概是普通接口的几十倍,风控会比较明显 - 发现进入直播间报错,尝试切换账号切换网络重试 ### 安全问题 - 一定要在仓库或可信源下载软件 - 被作者签名的app hash对的上 签名对的上就表示没有被篡改,否则被篡改藏木马病毒你安装后可能有损失 - 开源软件不代表绝对的安全 - 一些二次封装收费的,那都不是我干的和我没关系;我再次声明,我自身有良好的本职工作收入,我对大家没有任何经济上的要求,甚至帮忙扫支付宝红包的需求都没有;我只是学习并提升下自己的开发技术,大家帮忙测试下就好,不用感谢也不用宣传 ## 用户账户配置 ### WebDav ### BiliBili ### Douyin ## 播放器设置 ### 播放器参数 ### 弹幕参数设置 ## 关注功能 ### 标签功能 ### 主播信息 ### 关注清理 ## 链接解析 ## 外观设置 ### 字体设置 ## 其他说明 ================================================ FILE: flatpak/flatpak-manifest.yaml ================================================ app-id: io.github.SlotSun.Slive runtime: org.freedesktop.Platform runtime-version: '25.08' sdk: org.freedesktop.Sdk sdk-extensions: - org.freedesktop.Sdk.Extension.llvm21 command: io.github.SlotSun.Slive finish-args: - --share=ipc - --socket=fallback-x11 - --socket=wayland - --socket=pulseaudio - --share=network - --device=dri - --talk-name=org.freedesktop.ScreenSaver # user settings synchronization feature requires this D-Bus - --system-talk-name=org.freedesktop.NetworkManager.IP4Config cleanup: - '*.a' - '*.la' - /include - /lib/cmake - /lib/pkgconfig - /man - /share/man - /share/doc - /share/gtk-docmodules modules: - name: rustup buildsystem: simple build-options: build-args: - --share=network env: CARGO_HOME: /var/lib/rustup RUSTUP_HOME: /var/lib/rustup build-commands: - chmod -v +x rustup-init && ./rustup-init -y --profile minimal --no-modify-path sources: - type: file only-arches: [x86_64] url: https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-gnu/rustup-init sha256: 20a06e644b0d9bd2fbdbfd52d42540bdde820ea7df86e92e533c073da0cdd43c - type: file only-arches: [aarch64] url: https://static.rust-lang.org/rustup/dist/aarch64-unknown-linux-gnu/rustup-init sha256: e3853c5a252fca15252d07cb23a1bdd9377a8c6f3efa01531109281ae47f841c - name: Slive buildsystem: simple build-options: build-args: - --share=network arch: x86_64: env: BUNDLE_PATH: simple_live_app/build/linux/x64/release/bundle aarch64: env: BUNDLE_PATH: simple_live_app/build/linux/arm64/release/bundle append-path: /var/lib/rustup/bin:/usr/lib/sdk/llvm21/bin:/run/build/Slive/flutter/bin prepend-ld-library-path: /usr/lib/sdk/llvm21/lib env: RUSTUP_HOME: /var/lib/rustup CARGO_HOME: /var/lib/rustup PUB_CACHE: /run/build/Slive/.pub-cache build-commands: - cd simple_live_app && flutter build linux --release - install -dvm755 ${FLATPAK_DEST}/bin - cp -av ${BUNDLE_PATH}/* ${FLATPAK_DEST}/bin - install -Dvm644 simple_live_app/assets/${FLATPAK_ID}.metainfo.xml -t ${FLATPAK_DEST}/share/metainfo - install -Dvm644 simple_live_app/assets/images/logo.png ${FLATPAK_DEST}/share/icons/hicolor/512x512/apps/${FLATPAK_ID}.png - install -Dvm644 simple_live_app/assets/${FLATPAK_ID}.desktop -t ${FLATPAK_DEST}/share/applications sources: - type: dir path: "../" - type: git url: https://github.com/flutter/flutter.git tag: 3.38.6 commit: 8b872868494e429d94fa06dca855c306438b22c0 dest: flutter ================================================ FILE: flatpak/libplacebo-python-3.14.patch ================================================ From 12509c0f1ee8c22ae163017f0a5e7b8a9d983a17 Mon Sep 17 00:00:00 2001 From: Nicolas Chauvet Date: Tue, 29 Jul 2025 11:42:35 +0200 Subject: [PATCH] vulkan/utils_gen: fix for python 3.14 Python 3.14+ has added more type checking. This patch fixes usage Fixes: https://github.com/haasn/libplacebo/issues/335 Signed-off-by: Nicolas Chauvet --- src/vulkan/utils_gen.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vulkan/utils_gen.py b/src/vulkan/utils_gen.py index 9a97d35f3..9b803d82b 100644 --- a/src/vulkan/utils_gen.py +++ b/src/vulkan/utils_gen.py @@ -202,7 +202,8 @@ def find_registry_xml(datadir): if not xmlfile or xmlfile == '': xmlfile = find_registry_xml(datadir) - registry = VkXML(ET.parse(xmlfile)) + tree = ET.parse(xmlfile) + registry = VkXML(tree.getroot()) with open(outfile, 'w') as f: f.write(TEMPLATE.render( vkresults = get_vkenum(registry, 'VkResult'), ================================================ FILE: simple_live_app/.fvmrc ================================================ { "flutter": "3.38.6" } ================================================ FILE: simple_live_app/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .build/ .buildlog/ .history .svn/ .swiftpm/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Android Studio will place build artifacts here /android/app/debug /android/app/profile /android/app/release /android/app/.cxx /windows/out /windows/.vs /ios/Podfile.lock .fvm/ # firebase firebase.json ================================================ FILE: simple_live_app/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2" channel: "stable" project_type: app # Tracks metadata for the flutter migrate command migration: platforms: - platform: root create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - platform: linux create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 # User provided section # List of Local paths (relative to this file) that should be # ignored by the migrate tool. # # Files that are not part of the templates will be ignored by default. unmanaged_files: - 'lib/main.dart' - 'ios/Runner.xcodeproj/project.pbxproj' ================================================ FILE: simple_live_app/README.md ================================================ # simple_live_app 基于核心库实现的Flutter APP客户端。 ================================================ FILE: simple_live_app/analysis_options.yaml ================================================ # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # invoked from the command line by running `flutter analyze`. # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. analyzer: errors: unintended_html_in_doc_comment: ignore include: package:flutter_lints/flutter.yaml formatter: trailing_commas: preserve linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints # and their documentation is published at # https://dart-lang.github.io/linter/lints/index.html. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code # or a specific dart file by using the `// ignore: name_of_lint` and # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule constant_identifier_names: false # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: simple_live_app/android/.gitignore ================================================ gradle-wrapper.jar /app/build/ /.gradle /captures/ /gradlew /gradlew.bat /local.properties GeneratedPluginRegistrant.java .cxx/ # Remember to never publicly share your keystore. # See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks ================================================ FILE: simple_live_app/android/app/build.gradle.kts ================================================ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import java.util.Properties import java.io.FileInputStream plugins { id("com.android.application") // START: FlutterFire Configuration id("com.google.gms.google-services") id("com.google.firebase.crashlytics") // END: FlutterFire Configuration id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") } val keystoreProperties = Properties() val keystorePropertiesFile = rootProject.file("key.properties") if (keystorePropertiesFile.exists()) { keystoreProperties.load(FileInputStream(keystorePropertiesFile)) } android { namespace = "com.slotsun.slive" compileSdk = flutter.compileSdkVersion ndkVersion = "28.0.13004108" compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_17) } } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "com.slotsun.slive" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName } signingConfigs { create("release") { keyAlias = keystoreProperties["keyAlias"] as String keyPassword = keystoreProperties["keyPassword"] as String storeFile = keystoreProperties["storeFile"]?.let { file(it) } storePassword = keystoreProperties["storePassword"] as String } } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig = signingConfigs.getByName("release") isMinifyEnabled = true isShrinkResources = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } } flutter { source = "../.." } ================================================ FILE: simple_live_app/android/app/google-services.json ================================================ { "project_info": { "project_number": "000000000000", "project_id": "com-slotsun-slive", "storage_bucket": "com-slotsun-slive.firebasestorage.app" }, "client": [ { "client_info": { "mobilesdk_app_id": "1:000000000000:android:0000000000000000", "android_client_info": { "package_name": "com.slotsun.slive" } }, "oauth_client": [], "api_key": [ { "current_key": "0" } ], "services": { "appinvite_service": { "other_platform_oauth_client": [] } } } ], "configuration_version": "1" } ================================================ FILE: simple_live_app/android/app/proguard-rules.pro ================================================ -keep class org.videolan.libvlc.** { *; } ================================================ FILE: simple_live_app/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: simple_live_app/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: simple_live_app/android/app/src/main/kotlin/com/slotsun/slive/MainActivity.kt ================================================ package com.slotsun.slive import io.flutter.embedding.android.FlutterActivity class MainActivity : FlutterActivity() ================================================ FILE: simple_live_app/android/app/src/main/res/drawable/ic_launcher_background.xml ================================================  ================================================ FILE: simple_live_app/android/app/src/main/res/drawable/ic_launcher_foreground.xml ================================================ ================================================ FILE: simple_live_app/android/app/src/main/res/drawable/ic_launcher_monochrome.xml ================================================ ================================================ FILE: simple_live_app/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: simple_live_app/android/app/src/main/res/drawable-v21/launch_background.xml ================================================ ================================================ FILE: simple_live_app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: simple_live_app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ ================================================ FILE: simple_live_app/android/app/src/main/res/values/ic_launcher_background.xml ================================================ #206CA1 ================================================ FILE: simple_live_app/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: simple_live_app/android/app/src/main/res/values-night/styles.xml ================================================ ================================================ FILE: simple_live_app/android/app/src/main/res/xml/network_security_config.xml ================================================ ================================================ FILE: simple_live_app/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: simple_live_app/android/build.gradle.kts ================================================ allprojects { repositories { google() mavenCentral() } } val newBuildDir: Directory = rootProject.layout.buildDirectory .dir("../../build") .get() rootProject.layout.buildDirectory.value(newBuildDir) subprojects { val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) project.layout.buildDirectory.value(newSubprojectBuildDir) } subprojects { project.evaluationDependsOn(":app") } tasks.register("clean") { delete(rootProject.layout.buildDirectory) } ================================================ FILE: simple_live_app/android/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip ================================================ FILE: simple_live_app/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true ================================================ FILE: simple_live_app/android/settings.gradle.kts ================================================ pluginManagement { val flutterSdkPath = run { val properties = java.util.Properties() file("local.properties").inputStream().use { properties.load(it) } val flutterSdkPath = properties.getProperty("flutter.sdk") require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } flutterSdkPath } includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") repositories { google() mavenCentral() gradlePluginPortal() } } plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.11.1" apply false // START: FlutterFire Configuration id("com.google.gms.google-services") version("4.3.15") apply false id("com.google.firebase.crashlytics") version("2.8.1") apply false // END: FlutterFire Configuration id("org.jetbrains.kotlin.android") version "2.2.20" apply false } include(":app") ================================================ FILE: simple_live_app/assets/fonts/fonts-manifest.json ================================================ [ { "id": "judousansui", "name": "句读黑体 UI Hans", "files": [ "judousansui/JudouSansUiHans-ExtraLight.ttf", "judousansui/JudouSansUiHans-Light.ttf", "judousansui/JudouSansUiHans-Regular.ttf", "judousansui/JudouSansUiHans-Medium.ttf", "judousansui/JudouSansUiHans-Bold.ttf", "judousansui/JudouSansUiHans-Heavy.ttf" ], "desc": "句读黑体,基于思源黑体和 FiraGO 等字体,商用免费的多文种混排字体。", "official": "https://github.com/JudouEco/JudouSans", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "judousansuihant", "name": "句读黑体 UI Hant", "files": [ "judousansuihant/JudouSansUiHant-ExtraLight.ttf", "judousansuihant/JudouSansUiHant-Light.ttf", "judousansuihant/JudouSansUiHant-Regular.ttf", "judousansuihant/JudouSansUiHant-Medium.ttf", "judousansuihant/JudouSansUiHant-Bold.ttf", "judousansuihant/JudouSansUiHant-Heavy.ttf" ], "desc": "句读黑体 传承字形版本", "official": "https://github.com/JudouEco/JudouSans", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "frex-sans", "name": "械黑", "files": [ "frex-sans/FrexSansGB-Thin.ttf", "frex-sans/FrexSansGB-ExtraLight.ttf", "frex-sans/FrexSansGB-Light.ttf", "frex-sans/FrexSansGB-Text.ttf", "frex-sans/FrexSansGB-Medium.ttf", "frex-sans/FrexSansGB-SemiBold.ttf", "frex-sans/FrexSansGB-Bold.ttf" ], "desc": "将 IBM Plex Sans SC 中成部件的大量不必要修改还原成字体的原有风格,并修复了其中的一部分错形,部分零碎的字形也已恢复到繁体版和日文版原本的设计。", "official": "https://www.maoken.com/freefonts/26125.html", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "chilldin", "name": "寒蝉德黑体", "files": [ "chilldin/ChillDINGothic_Regular.otf", "chilldin/ChillDINGothic_Medium.otf", "chilldin/ChillDINGothic_Bold.otf" ], "desc": "源于德国工业标准字体“DIN”,基于D-DIN的修缮。", "official": "github.com/Warren2060/ChillDIN-ChillDINGothic", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "zhiyimaru", "name": "致一圆体", "files": [ "zhiyimaru/ZhiyiMaru-Regular.ttf", "zhiyimaru/ZhiyiMaru-Bold.ttf" ], "desc": "基于旧印刷字形的「霞鹜晰黑」修改而来,并制作了圆体、粗圆,以适应现代阅读环境对多样式、多字重字型的需要。", "official": "https://github.com/RiyipuSipino/ZhiyiSans", "license": { "name": "IPA Font License 1.0", "url": "https://moji.or.jp/ipafont/license/" } }, { "id": "chiron-go-round", "name": "昭源环方", "files": [ "chiron-go-round/ChironGoRoundTC-300L.ttf", "chiron-go-round/ChironGoRoundTC-400R.ttf", "chiron-go-round/ChironGoRoundTC-500M.ttf", "chiron-go-round/ChironGoRoundTC-700B.ttf" ], "desc": "现代笔形风格,平衡标准字形和印刷体惯用笔形的免费开源仿圆体字体。", "official": "https://github.com/chiron-fonts/chiron-go-round-tc", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "copperzhengkai", "name": "汇迹正楷", "files": [ "copperzhengkai/CooperZhengKai.ttf" ], "desc": "Cooper 搭配汇文正楷,更楷体。", "official": "https://www.maoken.com/freefonts/24749.html", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "fusion-pixel", "name": "缝合像素字体", "files": [ "fusion-pixel/fusion-pixel-12px-proportional-zh_hans.ttf" ], "desc": "该项目为「方舟像素字体」的临时性过渡方案。使用多个像素字体合并而成,因此以「缝合」命名。", "official": "https://github.com/TakWolf/fusion-pixel-font", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "chillroundm", "name": "寒蝉半圆体", "files": [ "chillroundm/ChillRoundM.otf" ], "desc": "寒蝉半圆体,基于日本开源字体 Zen Maru Gothic 进行调整的圆体字体,更新为符合 GB2312 标准的字符集,使其应用范围更广;修正部分字形和补充部分字形,调整到更均衡的排版。", "official": "https://github.com/Warren2060/ChillRound", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "huiwen-hkhei", "name": "汇文港黑", "files": [ "huiwen-hkhei/HuiwenHKHei-Regular.ttf" ], "desc": "特里王系列精品免费商用字体,字形主要参考自照排时期香港广泛使用的港版岩田黑体。", "official": "https://zhuanlan.zhihu.com/p/12669052378", "license": { "name": "其他协议", "url": "https://zhuanlan.zhihu.com/p/12669052378" } }, { "id": "huiwen-mincho", "name": "汇文明朝体 GBK", "files": [ "huiwen-mincho/HuiwenMinchoGBK-Regular.ttf" ], "desc": "特里王系列精品免费商用字体,字形主要来自五六十年代官方各种汉字改革文件。", "official": "https://zhuanlan.zhihu.com/p/12669052378", "license": { "name": "其他协议", "url": "https://zhuanlan.zhihu.com/p/12669052378" } }, { "id": "huiwen-zhengkai", "name": "汇文正楷", "files": [ "huiwen-zhengkai/HuiwenZhengKai-Regular.ttf" ], "desc": "特里王系列精品免费商用字体,字形来自大名鼎鼎的「汉文正楷」。", "official": "https://zhuanlan.zhihu.com/p/12669052378", "license": { "name": "其他协议", "url": "https://zhuanlan.zhihu.com/p/12669052378" } }, { "id": "huiwen-fangsong", "name": "汇文仿宋", "files": [ "huiwen-fangsong/HuiwenFangSong-Regular.ttf" ], "desc": "特里王系列精品免费商用字体,字形来自新华字模厂的老 59-4 仿宋体。", "official": "https://zhuanlan.zhihu.com/p/12669052378", "license": { "name": "其他协议", "url": "https://zhuanlan.zhihu.com/p/12669052378" } }, { "id": "blix", "name": "卜力士", "files": [ "blix/Blix-Thin.otf", "blix/Blix-ExtraLight.otf", "blix/Blix-Light.otf", "blix/Blix-Text.otf", "blix/Blix-Medium.otf", "blix/Blix-SemiBold.otf", "blix/Blix-Bold.otf" ], "desc": "基于 IBM Plex Sans 汉字系列的合并字体,尽可能减少规范字形的󠄁影响。", "official": "https://drive.google.com/drive/folders/14naJSasVxrPstbWkHtK696LLGAb5H-vm?usp=sharing", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "plix", "name": "普力士", "files": [ "plix/Plix-Thin.otf", "plix/Plix-ExtraLight.otf", "plix/Plix-Light.otf", "plix/Plix-Text.otf", "plix/Plix-Medium.otf", "plix/Plix-SemiBold.otf", "plix/Plix-Bold.otf" ], "desc": "基于 IBM Plex Sans 汉字系列的合并字体,尽可能减少规范字形的󠄁影响。", "official": "https://drive.google.com/drive/folders/14naJSasVxrPstbWkHtK696LLGAb5H-vm?usp=sharing", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "ibm-plex-sans-sc", "name": "IBM Plex Sans SC", "files": [ "ibm-plex-sans-sc/IBMPlexSansSC-Thin.otf", "ibm-plex-sans-sc/IBMPlexSansSC-ExtraLight.otf", "ibm-plex-sans-sc/IBMPlexSansSC-Light.otf", "ibm-plex-sans-sc/IBMPlexSansSC-Text.otf", "ibm-plex-sans-sc/IBMPlexSansSC-Medium.otf", "ibm-plex-sans-sc/IBMPlexSansSC-SemiBold.otf", "ibm-plex-sans-sc/IBMPlexSansSC-Bold.otf" ], "desc": "IBM 的开源字体。全球性,多功能,且像 IBM 一样独特。", "official": "https://github.com/IBM/plex/tree/feat-plex-sans-sc/packages/plex-sans-sc", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "yozai", "name": "悠哉字体", "files": [ "yozai/Yozai-Light.ttf", "yozai/Yozai-Regular.ttf", "yozai/Yozai-Medium.ttf" ], "desc": "一款基于 Y.OzFont 的手写风格的字体。", "official": "https://github.com/lxgw/yozai-font", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "childfunsans", "name": "游趣体", "files": [ "childfunsans/ChildFunSans.ttf" ], "desc": "一款开源简体中文字体,基于 Fontworks 的 Stick 衍生。", "official": "https://github.com/Des-Magmeta/ChildFunSans", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "tsuipita", "name": "晓声通秋茄体", "files": [ "tsuipita/ToneOZ-Tsuipita-TC.ttf" ], "desc": "基于随峰体扩展,开源免费可商用的的手写字型。", "official": "https://toneoz.com/blog/tsuipita/", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "kaisei", "name": "江城解星体", "files": [ "kaisei/kaisei.ttf" ], "desc": "不同于传统的印刷,通过在右侧添加加圆型衬线,使字体左右更加平衡,同时也为字体添加了不一样的特色。", "official": "https://toneoz.com/blog/tsuipita/", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "xuandongkaishu", "name": "玄冬楷书", "files": [ "xuandongkaishu/XuandongKaishu.ttf" ], "desc": "基于开源字体《马善政毛笔楷书》的拓展字体。在原版的基础之上修复了错误的字形并添加竖排排版的特性。", "official": "https://github.com/Skr-ZERO/Xuandong-Kaishu", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "misans", "name": "MiSans", "files": [ "misans/MiSans-Bold.ttf", "misans/MiSans-Heavy.ttf", "misans/MiSans-Light.ttf", "misans/MiSans-Medium.ttf", "misans/MiSans-Regular.ttf", "misans/MiSans-Thin.ttf" ], "desc": "MiSans Global 是由小米主导,联合蒙纳字库、汉仪字库共同打造的全球语言字体定制项目。这是一个庞大的字体家族,涵盖 20 多种书写系统,支持 600 多种语言,字符数量超过 10 万个。作为 Xiaomi HyperOS 系统默认字体,我们以简约/清晰,人文/易读,统一的视觉风格为基本原则出发,构建多语言信息体验一致性,旨在帮助为 Xiaomi HyperOS 提供互联的通用体验。", "official": "https://hyperos.mi.com/font", "license": { "name": "其他协议", "url": "https://hyperos.mi.com/font/faq" } }, { "id": "alibabapuhuiti3", "name": "阿里巴巴普惠体 3.0", "files": [ "alibabapuhuiti3/AlibabaPuHuiTi-3-45-Light.ttf", "alibabapuhuiti3/AlibabaPuHuiTi-3-55-Regular.ttf", "alibabapuhuiti3/AlibabaPuHuiTi-3-75-SemiBold.ttf", "alibabapuhuiti3/AlibabaPuHuiTi-3-95-ExtraBold.ttf" ], "desc": "阿里巴巴普惠体是一套全球永久免费正版商用的字体家族。阿里巴巴普惠体 3.0 为一套符合新国家标准 GB18030-2022 的简体中文字符集,包含 GB18030-2022 强制规范三个实现级别:实现级别 1 + 实现级别 2 标准规格的 7 字重、实现级别 3 标准规格的 Regular 单一字重。7 字重共 194460 个全形汉字。包含拉丁字母、希腊字母、西里尔字母、标点符号。", "official": "https://www.alibabafonts.com/", "license": { "name": "其他协议", "url": "https://www.yuque.com/yiguang-wkqc2/puhuiti/nus9wiinq4aeiegy" } }, { "id": "lxgwneoxihei", "name": "霞鹜新晰黑 屏幕阅读版", "files": [ "lxgwneoxihei/LXGWNeoXiHeiScreen.ttf" ], "desc": "一款基于 IPAex 黑体的中文开源字体,是将日本写法的字体改造成中国大陆规范写法的尝试。", "official": "https://github.com/lxgw/LxgwNeoXiZhi-Screen", "license": { "name": "IPA Font License 1.0", "url": "https://moji.or.jp/ipafont/license/" } }, { "id": "lxgwneozhisong", "name": "霞鹜新致宋 屏幕阅读版", "files": [ "lxgwneozhisong/LXGWNeoZhiSongScreen.ttf" ], "desc": "基于「IPAex 明朝」「IPAmj 明朝」的中文开源字体,是将日本写法的字体改造成中国大陆规范写法的尝试。", "official": "https://github.com/lxgw/LxgwNeoXiZhi-Screen", "license": { "name": "IPA Font License 1.0", "url": "https://moji.or.jp/ipafont/license/" } }, { "id": "chillroundf", "name": "寒蝉全圆体", "files": [ "chillroundf/ChillRoundF.ttf" ], "desc": "寒蝉全圆体,基于《小杉圆体》《justfont 粉圆》《猫啃糖圆体》进行调整的圆体字型,更新为超大字符数量的字符集,使其应用范围更广。综合考量各版本字形的使用并挑选合理的字形。修正部分字形和补充部分字形。调整到更均衡的排版。", "official": "https://github.com/Warren2060/ChillRound", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "hmsans", "name": "HarmonyOS Sans", "files": [ "hmsans/HarmonyOS_Sans_SC_Black.ttf", "hmsans/HarmonyOS_Sans_SC_Bold.ttf", "hmsans/HarmonyOS_Sans_SC_Light.ttf", "hmsans/HarmonyOS_Sans_SC_Medium.ttf", "hmsans/HarmonyOS_Sans_SC_Regular.ttf", "hmsans/HarmonyOS_Sans_SC_Thin.ttf" ], "desc": "华为全新品牌定制字体,HarmonyOS Sans 字体版权归华为所有", "official": "https://developer.huawei.com/consumer/cn/design/resource", "license": { "name": "其他协议", "url": "hmsans/LICENSE.txt" } }, { "id": "xwwk", "name": "霞鹜文楷 Lite", "files": [ "xwwk/LXGWWenKaiLite-Light.ttf", "xwwk/LXGWWenKaiLite-Regular.ttf", "xwwk/LXGWWenKaiLite-Medium.ttf" ], "desc": "An open-source Chinese font derived from Fontworks' Klee One. 一款基于 FONTWORKS 的 Klee One 的开源中文字体,保留「亠」「宀」等起笔的直点和「㇙」「㇜」等笔画折角处断笔设计等。", "official": "https://github.com/lxgw/LxgwWenKai-Lite", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "xwwkgb", "name": "霞鹜文楷 GB Lite", "files": [ "xwwkgb/LXGWWenKaiGBLite-Light.ttf", "xwwkgb/LXGWWenKaiGBLite-Regular.ttf", "xwwkgb/LXGWWenKaiGBLite-Medium.ttf" ], "desc": "在霞鹜文楷的基础上,尽可能严格按照中国大陆的字形标准(G 源字形)对字形、笔形作进一步修改。", "official": "https://github.com/lxgw/LxgwWenkaiGB-Lite", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "lhzt", "name": "小赖字体", "files": [ "lhzt/XiaolaiSC-Regular.ttf" ], "desc": "一款衍生于「濑户字体」的中文手写字型。", "official": "https://github.com/lxgw/kose-font", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "knmaiyuan", "name": "荆南麦圆体", "files": [ "knmaiyuan/KNMaiyuan-Regular.ttf" ], "desc": "麦圆体萌系可爱的手写风格,字体每个笔画的粗细相同,两端圆润饱满,凸显可爱风格。虽是手写字体,笔画分明没有细小的连接,使字体更加清新俏皮。无论是做食品类还是母婴类,抑或是儿童类的设计都特别适合。", "official": "https://github.com/maoken-fonts/KNMaiyuan", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "mizukigothic", "name": "水木黑体", "files": [ "mizukigothic/MizukiGothic-Regular.ttf" ], "desc": "一个还原未被楷化的大陆字形项目。", "official": "https://github.com/enderseven1/Mizuki-Gothic", "license": { "name": "IPA Font License 1.0", "url": "https://moji.or.jp/ipafont/license/" } }, { "id": "mizukiming", "name": "水木明体", "files": [ "mizukiming/MizukiMing-Regular.ttf" ], "desc": "致力于打造印刷体风格字形的项目。", "official": "https://github.com/enderseven1/Mizuki-Ming/", "license": { "name": "IPA Font License 1.0", "url": "https://moji.or.jp/ipafont/license/" } }, { "id": "yrdzst", "name": "杨任东竹石体", "files": [ "yrdzst/yrdzst-Bold.ttf", "yrdzst/yrdzst-Extralight.ttf", "yrdzst/yrdzst-Heavy.ttf", "yrdzst/yrdzst-Light.ttf", "yrdzst/yrdzst-Medium.ttf", "yrdzst/yrdzst-Regular.ttf", "yrdzst/yrdzst-Semibold.ttf" ], "desc": "杨任东竹石体于 2017 年 4 月 26 日世界知识产权日正式发布,本字体版本为 1.23。字体按照 GB2312-80 书写,并增补了少量常见汉字;整套字体有 7 个字重,共计 50000 余个字符。本套字体授权全社会免费商用,意味着您可以将其无限制地捆入您的商业产品中,如海报、包装、logo、网页、APP 等。", "official": "ttps://mp.weixin.qq.com/s/7kv3i_YEs7x9_9IrCDYvBA", "license": { "name": "其他协议", "url": "https://mp.weixin.qq.com/s/7kv3i_YEs7x9_9IrCDYvBA" } }, { "id": "opposans", "name": "OPlusSans", "files": [ "opposans/OPlusSans3-ExtraLight.ttf", "opposans/OPlusSans3-Light.ttf", "opposans/OPlusSans3-Regular.ttf", "opposans/OPlusSans3-Medium.ttf", "opposans/OPlusSans3-Bold.ttf" ], "desc": "OPlusSans(原名 OPPOSans)允许个人或企业免费使用,含商业用途,版权归 OPPO 广东移动通信有限公司所有。", "official": "https://www.coloros.com/index/newsDetail?id=72", "license": { "name": "其他协议", "url": "https://www.coloros.com/index/newsDetail?id=72" } }, { "id": "sarasa-gothic", "name": "更纱黑体", "files": [ "sarasa-gothic/SarasaGothicSC-Light.ttf", "sarasa-gothic/SarasaGothicSC-Regular.ttf", "sarasa-gothic/SarasaGothicSC-SemiBold.ttf", "sarasa-gothic/SarasaGothicSC-Bold.ttf" ], "desc": "更纱黑体使用全宽引号和比例数字。", "official": "https://github.com/be5invis/Sarasa-Gothic", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "sarasa-ui", "name": "更纱黑体 UI", "files": [ "sarasa-ui/SarasaUiSC-Light.ttf", "sarasa-ui/SarasaUiSC-Regular.ttf", "sarasa-ui/SarasaUiSC-SemiBold.ttf", "sarasa-ui/SarasaUiSC-Bold.ttf" ], "desc": "更纱黑体 UI 使用缩进引号和等宽数字。", "official": "https://github.com/be5invis/Sarasa-Gothic", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "sarasa-mono", "name": "等距更纱黑体", "files": [ "sarasa-mono/SarasaMonoSC-Light.ttf", "sarasa-mono/SarasaMonoSC-Regular.ttf", "sarasa-mono/SarasaMonoSC-SemiBold.ttf", "sarasa-mono/SarasaMonoSC-Bold.ttf" ], "desc": "等距更纱黑体使用等宽西文《Iosevka》。", "official": "https://github.com/be5invis/Sarasa-Gothic", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "sarasa-slab", "name": "等距更纱黑体 Slab", "files": [ "sarasa-slab/SarasaMonoSlabSC-Light.ttf", "sarasa-slab/SarasaMonoSlabSC-Regular.ttf", "sarasa-slab/SarasaMonoSlabSC-SemiBold.ttf", "sarasa-slab/SarasaMonoSlabSC-Bold.ttf" ], "desc": "等距更纱黑体 Slab 使用等宽西文《Iosevka Slab》。", "official": "https://github.com/be5invis/Sarasa-Gothic", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "sarasa-gothic-cl", "name": "更纱黑体 CL 旧字形", "files": [ "sarasa-gothic-cl/SarasaGothicCL-Light.ttf", "sarasa-gothic-cl/SarasaGothicCL-Regular.ttf", "sarasa-gothic-cl/SarasaGothicCL-SemiBold.ttf", "sarasa-gothic-cl/SarasaGothicCL-Bold.ttf" ], "desc": "更纱黑体使用全宽引号和比例数字。", "official": "https://github.com/be5invis/Sarasa-Gothic", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "sarasa-ui-cl", "name": "更纱黑体 UI CL 旧字形", "files": [ "sarasa-ui-cl/SarasaUiCL-Light.ttf", "sarasa-ui-cl/SarasaUiCL-Regular.ttf", "sarasa-ui-cl/SarasaUiCL-SemiBold.ttf", "sarasa-ui-cl/SarasaUiCL-Bold.ttf" ], "desc": "更纱黑体 UI 使用缩进引号和等宽数字。", "official": "https://github.com/be5invis/Sarasa-Gothic", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "sarasa-mono-cl", "name": "等距更纱黑体 CL 旧字形", "files": [ "sarasa-mono-cl/SarasaMonoCL-Light.ttf", "sarasa-mono-cl/SarasaMonoCL-Regular.ttf", "sarasa-mono-cl/SarasaMonoCL-SemiBold.ttf", "sarasa-mono-cl/SarasaMonoCL-Bold.ttf" ], "desc": "等距更纱黑体使用等宽西文《Iosevka》。", "official": "https://github.com/be5invis/Sarasa-Gothic", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "sarasa-slab-cl", "name": "等距更纱黑体 Slab CL 旧字形", "files": [ "sarasa-slab-cl/SarasaMonoSlabCL-Light.ttf", "sarasa-slab-cl/SarasaMonoSlabCL-Regular.ttf", "sarasa-slab-cl/SarasaMonoSlabCL-SemiBold.ttf", "sarasa-slab-cl/SarasaMonoSlabCL-Bold.ttf" ], "desc": "等距更纱黑体 Slab 使用等宽西文《Iosevka Slab》。", "official": "https://github.com/be5invis/Sarasa-Gothic", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "ysxxk", "name": "演示夏行楷", "files": [ "ysxxk/ysxxk.ttf" ], "desc": "「演示夏行楷」是 Keynote 研究所的人生哥制作发布的一款毛笔字体。字稿作者是刘锡栋:中国漓江书画艺术家协会理事丨北京中国书画研究院研究员丨中国民族硬笔书法家协会会员丨中国楹联学会会员丨黑龙江楹联家协会会员丨高级政工师,已退休 该字体简体中文行楷,包含标准的 6763 个字符。", "official": "https://mp.weixin.qq.com/s/CRnRsYu8ymlG9_oK6wmBag", "license": { "name": "其他协议", "url": "https://mp.weixin.qq.com/s/CRnRsYu8ymlG9_oK6wmBag" } }, { "id": "ysfxt", "name": "演示佛系体", "files": [ "ysfxt/ysfxt.ttf" ], "desc": "「演示佛系体」是由一生酷爱书法的纪国才老先生,在 80 岁这年亲手写了一套独具风格的字体,并授权 Keynote 研究所 和 秋叶 PPT 面向全社会联合发布,全渠道免费商用!", "official": "https://mp.weixin.qq.com/s/iWn8SWH5ymBKmsGiHe8Yfw", "license": { "name": "其他协议", "url": "https://mp.weixin.qq.com/s/iWn8SWH5ymBKmsGiHe8Yfw" } }, { "id": "ysqhk", "name": "演示秋鸿楷", "files": [ "ysqhk/ysqhk.ttf" ], "desc": "「演示秋鸿楷」是 Keynote 研究所的人生哥制作发布的一款毛笔字体。字稿的作者是叶运鹏:高中教师丨辽宁人丨 98 年加入农民书画家协会。该字体简体中文楷书,包含标准的 6763 个字符。", "official": "https://mp.weixin.qq.com/s/CRnRsYu8ymlG9_oK6wmBag", "license": { "name": "其他协议", "url": "https://mp.weixin.qq.com/s/CRnRsYu8ymlG9_oK6wmBag" } }, { "id": "yscfk", "name": "演示春风楷", "files": [ "yscfk/yscfk.ttf" ], "desc": "「演示春风楷」是 Keynote 研究所的人生哥制作发布的一款毛笔字体。字稿的作者是徐占海,军人出身,1983 年入伍。每日笔耕不辍练习写字。该字体简体中文楷书,包含标准的 6763 个字符。", "official": "https://mp.weixin.qq.com/s/CRnRsYu8ymlG9_oK6wmBag", "license": { "name": "其他协议", "url": "https://mp.weixin.qq.com/s/CRnRsYu8ymlG9_oK6wmBag" } }, { "id": "ysyrxk", "name": "演示悠然小楷", "files": [ "ysyrxk/ysyrxk.ttf" ], "desc": "「演示悠然小楷」是秋叶 PPT 联合 keynote 研究所推出的一款书法字体,字稿的作者是孟祥媛,一位还在大学读书的书法专业学生。初看「演示悠然小楷」,可能并不觉得惊艳,再品,却会发现透着质朴、率真的气息。正如尚在象牙塔中的作者本人,那是未经雕琢的拙朴书生气。接下来,一起体会悠然小楷的这股「拙气」。悠然小楷笔画分明,结构清晰,没有过度的连笔和飞白,对阅读识别更为友好;小楷字形较小,天然拥有更大的字间距,即便作为长段文字使用,阅读上也不会有太大视觉压力;悠然小楷的笔画力度分布均衡,起收有序,没有太过强烈的对比,自然沉劲而不漂浮;在小楷的基础上有些行楷的影子,为字体增添了灵动,但又有克制,形成停而不断、笔断意连之态;部分笔画细节可能并不完美,不够顺滑,但这份未经雕琢,又何尝不是一种难得的拙朴;所以见过很多大开大合的书法字体后,再见悠然小楷,可能没有很惊艳之感。但艳丽与喧嚣并不一定代表力量,沉劲入骨更难能可贵", "official": "https://mp.weixin.qq.com/s/Q1lAIre4yJ-Zlf2CD82EPA", "license": { "name": "其他协议", "url": "https://mp.weixin.qq.com/s/Q1lAIre4yJ-Zlf2CD82EPA" } }, { "id": "cyroithb", "name": "Cyroit HB", "files": [ "cyroithb/CyroitHB-Regular.ttf", "cyroithb/CyroitHB-Bold.ttf" ], "desc": "全角英数や半角カナが判別しやすい、文字間隔調整機能付き等幅フォント。", "official": "https://github.com/omonomo/Cyroit", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "advocateancientrounded", "name": "尙古圆体", "files": [ "advocateancientrounded/ShangguRoundST-Light.ttf", "advocateancientrounded/ShangguRoundST-Regular.ttf", "advocateancientrounded/ShangguRoundST-Medium.ttf", "advocateancientrounded/ShangguRoundST-Bold.ttf" ], "desc": "基于思源的传承字形(旧字形)字体。", "official": "https://github.com/GuiWonder/Shanggu", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "fanthinkgrotesk", "name": "繁 Think 黑", "files": [ "fanthinkgrotesk/FanThinkGrotesk-Thin.otf", "fanthinkgrotesk/FanThinkGrotesk-ExtraLight.otf", "fanthinkgrotesk/FanThinkGrotesk-Light.otf", "fanthinkgrotesk/FanThinkGrotesk-Text.otf", "fanthinkgrotesk/FanThinkGrotesk-Medium.otf", "fanthinkgrotesk/FanThinkGrotesk-SemiBold.otf", "fanthinkgrotesk/FanThinkGrotesk-Bold.otf" ], "desc": "繁 Think 黑是一套基於 IBM Plex Sans TC 的自動簡轉繁字體。簡轉繁代碼來自 GuiWonder。", "official": "https://github.com/pkj-l/FanThinkGrotesk", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "ipagothic", "name": "IPAGothic", "files": [ "ipagothic/ipag.ttf" ], "desc": "应 IPA 许可证要求,增加 IPA 原版字体以允许用户“替换回原始字体”。", "official": "https://moji.or.jp/ipafont/ipa00303/", "license": { "name": "IPA Font License 1.0", "url": "https://moji.or.jp/ipafont/license/" } }, { "id": "ipamincho", "name": "IPAMincho", "files": [ "ipamincho/ipam.ttf" ], "desc": "应 IPA 许可证要求,增加 IPA 原版字体以允许用户“替换回原始字体”。", "official": "https://moji.or.jp/ipafont/ipa00303/", "license": { "name": "IPA Font License 1.0", "url": "https://moji.or.jp/ipafont/license/" } }, { "id": "atkinson", "name": "Atkinson Hyperlegible Next", "files": [ "atkinson/AtkinsonHyperlegibleNext-ExtraLight.ttf", "atkinson/AtkinsonHyperlegibleNext-Light.ttf", "atkinson/AtkinsonHyperlegibleNext-Regular.ttf", "atkinson/AtkinsonHyperlegibleNext-Medium.ttf", "atkinson/AtkinsonHyperlegibleNext-SemiBold.ttf", "atkinson/AtkinsonHyperlegibleNext-Bold.ttf", "atkinson/AtkinsonHyperlegibleNext-ExtraBold.ttf" ], "desc": "Atkinson Hyperlegible, named after the founder of the Braille Institute, has been developed specifically to increase legibility for readers with low vision, and to improve comprehension.", "official": "https://www.brailleinstitute.org/freefont/", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "lexend", "name": "Lexend Deca", "files": [ "lexend/LexendDeca-Thin.ttf", "lexend/LexendDeca-ExtraLight.ttf", "lexend/LexendDeca-Light.ttf", "lexend/LexendDeca-Regular.ttf", "lexend/LexendDeca-Medium.ttf", "lexend/LexendDeca-SemiBold.ttf", "lexend/LexendDeca-Bold.ttf", "lexend/LexendDeca-ExtraBold.ttf", "lexend/LexendDeca-Black.ttf" ], "desc": "Lexend is a variable typeface designed by Bonnie Shaver-Troup and Thomas Jockin in 2018. Applying the Shaver-Troup Individually Optimal Text Formation Factors, studies have found readers instantaneously improve their reading fluency.", "official": "https://www.lexend.com/", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } }, { "id": "playpen", "name": "Playpen Sans", "files": [ "playpen/PlaypenSans-Thin.ttf", "playpen/PlaypenSans-ExtraLight.ttf", "playpen/PlaypenSans-Light.ttf", "playpen/PlaypenSans-Regular.ttf", "playpen/PlaypenSans-Medium.ttf", "playpen/PlaypenSans-SemiBold.ttf", "playpen/PlaypenSans-Bold.ttf", "playpen/PlaypenSans-ExtraBold.ttf" ], "desc": "Playpen Sans is one of the font families produced by TypeTogether after more than two years of primary research into handwriting education for Latin-based languages. It has seven automatic alternates for each character and a built-in shuffler that both ensures variation and avoids repetitive shapes in close proximity. This feature adds to the overall organic, spontaneous, and authentic feel of the handwritten style.", "official": "https://github.com/TypeTogether/Playpen-Sans", "license": { "name": "SIL OFL 1.1", "url": "https://openfontlicense.org/open-font-license-official-text/" } } ] ================================================ FILE: simple_live_app/assets/io.github.SlotSun.Slive.desktop ================================================ [Desktop Entry] Type=Application Name=Slive GenericName=Livestream Player Icon=io.github.SlotSun.Slive Exec=io.github.SlotSun.Slive %U Categories=AudioVideo;Audio;Video;Network Keywords=Livestream;Video StartupNotify=true ================================================ FILE: simple_live_app/assets/io.github.SlotSun.Slive.metainfo.xml ================================================ io.github.SlotSun.Slive io.github.SlotSun.Slive.desktop Slive SlotSun Watch live streams simply 我就默默看你表演 CC0-1.0 GPL-3.0 https://github.com/SlotSun/dart_simple_live/ https://github.com/SlotSun/dart_simple_live/issues

Slive is a lightweight and user-friendly application for watching live streams across multiple platforms, offering a seamless watching experience with support for popular streaming services.

Slive 是一款轻量级且用户友好的直播观看应用,支持多个主流直播平台,提供流畅的观看体验。

Slive support platforms:

支持直播平台:

  • Huya
  • 虎牙
  • Douyu
  • 斗鱼
  • Bilibili
  • 哔哩哔哩
  • Douyin
  • 抖音
Light Mode https://raw.githubusercontent.com/SlotSun/dart_simple_live/v1.7.18/assets/screenshot_light.jpg Dark Mode https://raw.githubusercontent.com/SlotSun/dart_simple_live/v1.7.18/assets/screenshot_dark.jpg

- 一个非常重要的过渡版本

- 修复huya搜索 @GH4NG

- 引入Firebase

- 大量数据错误和潜在问题修复

- 一些细节调整

- 关于linux的一系列修复 @pugaizai

- tips: ios和macos用户请到action更新测试或者下载上游仓库版本

i@pugai.life
================================================ FILE: simple_live_app/assets/io.github.SlotSun.dart_simple_live.desktop ================================================ [Desktop Entry] Type=Application Name=Slive GenericName=Livestream Player Icon=io.github.SlotSun.dart_simple_live Exec=Slive %U Categories=AudioVideo;Audio;Video;Network Keywords=Livestream;Video StartupNotify=true ================================================ FILE: simple_live_app/assets/io.github.SlotSun.dart_simple_live.metainfo.xml ================================================ io.github.SlotSun.dart_simple_live io.github.SlotSun.dart_simple_live.desktop Slive SlotSun Watch live streams simply 我就默默看你表演 CC0-1.0 GPL-3.0 https://github.com/SlotSun/dart_simple_live/ https://github.com/SlotSun/dart_simple_live/issues

Slive is a lightweight and user-friendly application for watching live streams across multiple platforms, offering a seamless watching experience with support for popular streaming services.

Slive 是一款轻量级且用户友好的直播观看应用,支持多个主流直播平台,提供流畅的观看体验。

Slive support platforms:

支持直播平台:

  • Huya
  • 虎牙
  • Douyu
  • 斗鱼
  • Bilibili
  • 哔哩哔哩
  • Douyin
  • 抖音
Light Mode https://raw.githubusercontent.com/SlotSun/dart_simple_live/v1.7.18/assets/screenshot_light.jpg Dark Mode https://raw.githubusercontent.com/SlotSun/dart_simple_live/v1.7.18/assets/screenshot_dark.jpg

### 已修改包名slotsun.slive,相关事宜请查看 #93

- fix:huya

- 优化弹幕去重功能

- 关于linux的一系列修复 @pugaizai

- fix: 一些代码和逻辑错误

- tips: ios和macos用户请到action更新测试或者下载上游仓库版本

i@pugai.life
================================================ FILE: simple_live_app/assets/lotties/empty.json ================================================ {"v":"4.7.0","fr":25,"ip":0,"op":50,"w":120,"h":120,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"ruoi","ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.967]},"o":{"x":[0.167],"y":[0.033]},"n":["0p833_0p967_0p167_0p033"],"t":35,"s":[100],"e":[0]},{"t":49}]},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0,"y":0},"n":"0p833_0p833_0_0","t":0,"s":[57.361,61.016,0],"e":[57.699,41.796,0],"to":[-4.67500305175781,-4.12800598144531,0],"ti":[-13.9099960327148,5.27300262451172,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":10.219,"s":[57.699,41.796,0],"e":[79.084,33.982,0],"to":[12.8159942626953,-4.85800170898438,0],"ti":[-4.54498291015625,3.73400115966797,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":19.445,"s":[79.084,33.982,0],"e":[59.691,9.121,0],"to":[6.61601257324219,-5.43799591064453,0],"ti":[20.0290069580078,1.20700073242188,0]},{"t":35}]},"a":{"a":0,"k":[60.531,10.945,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.994,0],[0,-0.994],[0.995,0],[0,0.994]],"o":[[0.995,0],[0,0.994],[-0.994,0],[0,-0.994]],"v":[[-0.001,-1.801],[1.801,-0.001],[-0.001,1.801],[-1.801,-0.001]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":0,"k":[0.4235294117647059,0.4235294117647059,0.4235294117647059,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[62.4,13.144],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.422,0],[0,-1.422],[1.421,0],[0,1.422]],"o":[[1.421,0],[0,1.422],[-1.422,0],[0,-1.422]],"v":[[0.001,-2.574],[2.574,0],[0.001,2.574],[-2.574,0]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"st","c":{"a":0,"k":[0.4235294117647059,0.4235294117647059,0.4235294117647059,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0.7},"lc":1,"lj":1,"ml":10,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[64.145,9.606],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"ix":2,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.996,0],[0,-1.996],[1.996,0],[0,1.996]],"o":[[1.996,0],[0,1.996],[-1.996,0],[0,-1.996]],"v":[[0,-3.614],[3.614,0],[0,3.614],[-3.614,0]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"st","c":{"a":0,"k":[0.4235294117647059,0.4235294117647059,0.4235294117647059,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0.7},"lc":1,"lj":1,"ml":10,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[57.957,10.552],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":3,"cix":2,"ix":3,"mn":"ADBE Vector Group"},{"ty":"tr","p":{"a":0,"k":[60.531,10.941],"ix":2},"a":{"a":0,"k":[60.531,10.941],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"ruoi","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":50,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 2","ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.967]},"o":{"x":[0.167],"y":[0.033]},"n":["0p833_0p967_0p167_0p033"],"t":35,"s":[100],"e":[0]},{"t":49}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[-0.75,-0.75,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-13.91,5.273],[-4.545,3.734],[20.029,1.207]],"o":[[-4.675,-4.128],[12.816,-4.858],[6.616,-5.438],[0,0]],"v":[[-7.383,24.76],[-7.046,5.54],[14.34,-2.273],[-3.178,-24.76]],"c":false}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"st","c":{"a":0,"k":[0.34901960784313724,0.3686274509803922,0.4,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":2.028}},{"n":"g","nm":"gap","v":{"a":0,"k":2.028}},{"n":"o","nm":"offset","v":{"a":0,"k":0}}],"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"tr","p":{"a":0,"k":[67.87,37.631],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 6","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group"},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.953]},"o":{"x":[0.167],"y":[0.033]},"n":["0p833_0p953_0p167_0p033"],"t":0,"s":[0],"e":[100]},{"t":35}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim"}],"ip":0,"op":50,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":3,"ty":4,"nm":"im_emptyBox Outlines","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[60,60,0]},"a":{"a":0,"k":[60,60,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-0.001,-16.607],[-32.143,-0.002],[-0.001,16.607],[32.144,-0.002]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":0,"k":[0.8,0.82,0.851,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[60,55.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[12.856,-23.249],[0,-16.605],[-12.857,-23.249],[-45,-6.641],[-32.144,0.001],[-45,6.645],[-12.857,23.249],[0,16.609],[12.856,23.249],[45,6.645],[32.143,0.001],[45,-6.641]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":0,"k":[0.9372549019607843,0.9372549019607843,0.9372549019607843,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[60,55.748],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-16.072,24.171],[16.072,11.312],[16.072,-24.171],[-16.072,-24.171]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":0,"k":[0.9529411764705882,0.9529411764705882,0.9529411764705882,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[76.072,83.33],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":2,"cix":2,"ix":3,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-32.143,-24.171],[-32.143,11.311],[-0.001,24.171],[32.144,11.311],[32.144,-24.171]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":0,"k":[0.8,0.82,0.851,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[60,83.33],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":2,"cix":2,"ix":4,"mn":"ADBE Vector Group"},{"ty":"tr","p":{"a":0,"k":[60,60.186],"ix":2},"a":{"a":0,"k":[60,60.186],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"box","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":50,"st":0,"bm":0,"sr":1}]} ================================================ FILE: simple_live_app/assets/lotties/error.json ================================================ {"v":"5.8.1","fr":29.9700012207031,"ip":0,"op":301.000012259981,"w":1080,"h":900,"nm":"Composition 1","ddd":0,"assets":[{"id":"comp_0","nm":"Nuage","fr":29.9700012207031,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Calque de forme 11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,540,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[54,54],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Tracé d'ellipse 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.901960784314,0.901960784314,0.901960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-22,-239],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Calque de forme 12","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[544.675,315.475,0],"ix":2,"l":2},"a":{"a":0,"k":[-54.5,-226.5,0],"ix":1,"l":2},"s":{"a":0,"k":[80,80,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[31,31],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Tracé d'ellipse 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.901960784314,0.901960784314,0.901960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-54.5,-226.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Calque de forme 10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,540,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[31,31],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Tracé d'ellipse 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.901960784314,0.901960784314,0.901960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-54.5,-226.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Calque de forme 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,540,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[100,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":20,"ix":4},"nm":"Tracé rectangulaire 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.901960784314,0.901960784314,0.901960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-26,-216],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0}]},{"id":"comp_1","nm":"éolienne","fr":29.9700012207031,"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Hélices","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.137],"y":[1]},"o":{"x":[0.567],"y":[0]},"t":1,"s":[0]},{"t":300.00001221925,"s":[1440]}],"ix":10},"p":{"a":0,"k":[800,440,0],"ix":2,"l":2},"a":{"a":0,"k":[800,440,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":1080,"h":900,"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Socle eolienne","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,540,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[264.75,-95],[257,-95],[242,252],[288,251]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.901960784314,0.901960784314,0.901960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0}]},{"id":"comp_2","nm":"Hélices","fr":29.9700012207031,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Calque de forme 11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-239.6,"ix":10},"p":{"a":0,"k":[803,442,0],"ix":2,"l":2},"a":{"a":0,"k":[263,-98,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[310,-98],[284,-92],[263,-96],[439,10],[423,-17]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.901960784314,0.901960784314,0.901960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Calque de forme 10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-122.747,"ix":10},"p":{"a":0,"k":[803,442,0],"ix":2,"l":2},"a":{"a":0,"k":[263,-98,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[310,-98],[284,-92],[263,-96],[439,10],[423,-17]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.901960784314,0.901960784314,0.901960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Calque de forme 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[803,442,0],"ix":2,"l":2},"a":{"a":0,"k":[263,-98,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[310,-98],[284,-92],[263,-96],[439,10],[423,-17]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.901960784314,0.901960784314,0.901960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Calque de forme 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[800.5,439.5,0],"ix":2,"l":2},"a":{"a":0,"k":[266.5,-94.5,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[27,27],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Tracé d'ellipse 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.901960784314,0.901960784314,0.901960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[266.5,-94.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0}]},{"id":"comp_3","nm":"plot base","fr":29.9700012207031,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Calque de forme 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[541,387.5,0],"ix":2,"l":2},"a":{"a":0,"k":[1,-152.5,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[13,-223],[-13,-223],[-52,-82],[54,-82]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.9019607843137255,0.3764705882352941,0.27058823529411763,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Calque de forme 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,502,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-38,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[58,-56],[-59,-56],[-70,-20],[70,-20]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.9019607843137255,0.3764705882352941,0.27058823529411763,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Calque de forme 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[539.75,606.25,0],"ix":2,"l":2},"a":{"a":0,"k":[-0.25,66.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[256.5,28.5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Tracé rectangulaire 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.7647058823529411,0.3215686274509804,0.23137254901960785,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-0.25,66.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Calque de forme 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,572,0],"ix":2,"l":2},"a":{"a":0,"k":[0,32,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[78,3],[-76,3],[-93,60],[93,61]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.9019607843137255,0.3764705882352941,0.27058823529411763,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0}]},{"id":"comp_4","nm":"Tree","fr":29.9700012207031,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Calque de forme 10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,540,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-171.5,-79.5],[-152.5,-45]],"c":false},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980451995,0.84313731474,0.84313731474,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":7,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Calque de forme 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,540,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-154.5,-23.5],[-137.5,-48.5]],"c":false},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980451995,0.84313731474,0.84313731474,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":7,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Calque de forme 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[386,620,0],"ix":2,"l":2},"a":{"a":0,"k":[-154,80,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-154,256],[-154,-96]],"c":false},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.850980392157,0.844306078144,0.844306078144,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Calque de forme 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[384,475,0],"ix":2,"l":2},"a":{"a":0,"k":[-156,-65,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[96,222],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":62,"ix":4},"nm":"Tracé rectangulaire 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019667682,0.949019667682,0.949019667682,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-156,-65],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0}]},{"id":"comp_5","nm":"ground","fr":29.9700012207031,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Calque de forme 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[515,795.5,0],"ix":2,"l":2},"a":{"a":0,"k":[-273.5,78,0],"ix":1,"l":2},"s":{"a":0,"k":[662,317,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[59,2],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2371,"ix":4},"nm":"Tracé rectangulaire 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.133333333333,0.145098039216,0.235294132607,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-273.5,78],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Calque de forme 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[817,795.5,0],"ix":2,"l":2},"a":{"a":0,"k":[-273.5,78,0],"ix":1,"l":2},"s":{"a":0,"k":[78,317,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[59,2],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2371,"ix":4},"nm":"Tracé rectangulaire 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.133333333333,0.145098039216,0.235294132607,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-273.5,78],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Calque de forme 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[752,795.5,0],"ix":2,"l":2},"a":{"a":0,"k":[-273.5,78,0],"ix":1,"l":2},"s":{"a":0,"k":[89,317,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[59,2],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2371,"ix":4},"nm":"Tracé rectangulaire 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.133333333333,0.145098039216,0.235294132607,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-273.5,78],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Calque de forme 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[270,795.5,0],"ix":2,"l":2},"a":{"a":0,"k":[-273.5,78,0],"ix":1,"l":2},"s":{"a":0,"k":[102,317,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[59,2],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2371,"ix":4},"nm":"Tracé rectangulaire 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.133333333333,0.145098039216,0.235294132607,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-273.5,78],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Nuage","refId":"comp_0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":51,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":234,"s":[100]},{"t":299.00001217852,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.284,"y":1},"o":{"x":0.387,"y":0},"t":0,"s":[704,380,0],"to":[-36.667,0,0],"ti":[36.667,0,0]},{"t":299.00001217852,"s":[484,380,0]}],"ix":2,"l":2},"a":{"a":0,"k":[514,308,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":1080,"h":900,"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Calque de forme 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,540,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.5,-0.5],[0,0.5],[0,-1],[0,-0.5],[0,0],[0.5,-0.5],[0,0],[0.5,-0.5],[-0.5,-0.5]],"o":[[-19,-5],[-34,-13],[-18.5,1.5],[-17,10.5],[0,0],[17,-19.5],[6.5,-4],[-3.5,-13],[11.5,-19.5]],"v":[[263.5,176],[242.5,196],[218,213],[209.5,232.5],[215,254.5],[297,253.5],[289.5,225],[294,210],[271.5,204]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.83137254902,0.821591725069,0.821591725069,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"éolienne","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[804,796,0],"ix":2,"l":2},"a":{"a":0,"k":[804,796,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":1080,"h":900,"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"plot base","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":7.368,"s":[6]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":14.736,"s":[-5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22.264,"s":[4]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":29.633,"s":[-2]},{"t":37.0000015070409,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[540,792,0],"to":[0,-2.667,0],"ti":[0,2,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":7.368,"s":[540,776,0],"to":[0,-2,0],"ti":[0,-1.333,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":14.736,"s":[540,780,0],"to":[0,1.333,0],"ti":[0,-1.333,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":22.264,"s":[540,784,0],"to":[0,1.333,0],"ti":[0,-2,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":29.633,"s":[540,788,0],"to":[0,2,0],"ti":[0,-1.333,0]},{"t":37.0000015070409,"s":[540,796,0]}],"ix":2,"l":2},"a":{"a":0,"k":[540,616,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":1080,"h":900,"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"éolienne","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[670,796,0],"ix":2,"l":2},"a":{"a":0,"k":[804,796,0],"ix":1,"l":2},"s":{"a":0,"k":[63,63,100],"ix":6,"l":2}},"ao":0,"w":1080,"h":900,"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"Tree","refId":"comp_4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[280,790,0],"ix":2,"l":2},"a":{"a":0,"k":[384,790,0],"ix":1,"l":2},"s":{"a":0,"k":[82,82,100],"ix":6,"l":2}},"ao":0,"w":1080,"h":900,"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Tree","refId":"comp_4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,540,0],"ix":2,"l":2},"a":{"a":0,"k":[540,540,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":1080,"h":900,"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Arbuste","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[68,540,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.5,-0.5],[0,0.5],[0,-1],[0,-0.5],[0,0],[0.5,-0.5],[0,0],[0.5,-0.5],[-0.5,-0.5]],"o":[[-19,-5],[-34,-13],[-18.5,1.5],[-17,10.5],[0,0],[17,-19.5],[6.5,-4],[-3.5,-13],[11.5,-19.5]],"v":[[263.5,176],[242.5,196],[218,213],[209.5,232.5],[215,254.5],[297,253.5],[289.5,225],[294,210],[271.5,204]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.901960784314,0.901960784314,0.901960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":0,"nm":"ground","refId":"comp_5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,540,0],"ix":2,"l":2},"a":{"a":0,"k":[540,540,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":1080,"h":900,"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"Nuage","refId":"comp_0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":51,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":234,"s":[100]},{"t":299.00001217852,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[193,606,0],"to":[56.5,0,0],"ti":[-56.5,0,0]},{"t":299.00001217852,"s":[532,606,0]}],"ix":2,"l":2},"a":{"a":0,"k":[514,308,0],"ix":1,"l":2},"s":{"a":0,"k":[60,60,100],"ix":6,"l":2}},"ao":0,"w":1080,"h":900,"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Calque de forme 6","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[572,712,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[784,628],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Tracé rectangulaire 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803921569,0.909803921569,0.909803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-36,-234],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Calque de forme 5","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,716,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[728,728],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Tracé d'ellipse 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470588235,0.976470588235,0.976470588235,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[8,-140],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":630.000025660426,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":1,"nm":"Blanc uni 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,540,0],"ix":2,"l":2},"a":{"a":0,"k":[540,540,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"sw":1080,"sh":900,"ip":0,"op":630.000025660426,"st":0,"bm":0}],"markers":[]} ================================================ FILE: simple_live_app/assets/lotties/loadding.json ================================================ { "v": "5.5.8", "fr": 50, "ip": 0, "op": 147, "w": 800, "h": 600, "nm": "Paperplane", "ddd": 0, "assets": [ { "id": "comp_0", "layers": [ { "ddd": 0, "ind": 1, "ty": 4, "nm": "planete Outlines - Group 4", "sr": 1, "ks": { "o": { "a": 1, "k": [ { "i": { "x": [ 0.833 ], "y": [ 0.833 ] }, "o": { "x": [ 0.167 ], "y": [ 0.167 ] }, "t": 0, "s": [ 0 ] }, { "i": { "x": [ 0.833 ], "y": [ 0.833 ] }, "o": { "x": [ 0.167 ], "y": [ 0.167 ] }, "t": 38, "s": [ 50 ] }, { "i": { "x": [ 0.833 ], "y": [ 0.833 ] }, "o": { "x": [ 0.167 ], "y": [ 0.167 ] }, "t": 88, "s": [ 50 ] }, { "t": 120, "s": [ 0 ] } ], "ix": 11 }, "r": { "a": 0, "k": 0, "ix": 10 }, "p": { "a": 1, "k": [ { "i": { "x": 0.833, "y": 0.833 }, "o": { "x": 0.167, "y": 0.167 }, "t": 0, "s": [ 468.336, 323.378, 0 ], "to": [ -29, 0, 0 ], "ti": [ 29, 0, 0 ] }, { "t": 102, "s": [ 294.336, 323.378, 0 ] } ], "ix": 2 }, "a": { "a": 0, "k": [ 453.672, 304.756, 0 ], "ix": 1 }, "s": { "a": 0, "k": [ 50, 50, 100 ], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [ 6.742, 0 ], [ 0.741, -0.14 ], [ 0, 0.074 ], [ 13.484, 0 ], [ 1.669, -0.361 ], [ 19.79, 0 ], [ 3.317, -19.082 ], [ 2.691, 0 ], [ 0, -13.484 ], [ -0.048, -0.629 ], [ 2.405, 0 ], [ 0, -6.742 ], [ -6.742, 0 ], [ 0, 0 ], [ 0, 6.743 ] ], "o": [ [ -0.781, 0 ], [ 0.001, -0.074 ], [ 0, -13.484 ], [ -1.778, 0 ], [ -3.594, -18.742 ], [ -20.03, 0 ], [ -2.421, -0.804 ], [ -13.485, 0 ], [ 0, 0.642 ], [ -1.89, -1.199 ], [ -6.742, 0 ], [ 0, 6.743 ], [ 0, 0 ], [ 6.742, 0 ], [ 0, -6.742 ] ], "v": [ [ 75.134, 16.175 ], [ 72.85, 16.396 ], [ 72.856, 16.175 ], [ 48.44, -8.241 ], [ 43.262, -7.685 ], [ 3.406, -40.591 ], [ -36.571, -6.995 ], [ -44.269, -8.241 ], [ -68.685, 16.175 ], [ -68.604, 18.079 ], [ -75.133, 16.175 ], [ -87.341, 28.383 ], [ -75.133, 40.592 ], [ 75.134, 40.592 ], [ 87.342, 28.383 ] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.815686334348, 0.823529471603, 0.827451040231, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [ 453.672, 304.756 ], "ix": 2 }, "a": { "a": 0, "k": [ 0, 0 ], "ix": 1 }, "s": { "a": 0, "k": [ 100, 100 ], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 4", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 0, "op": 151, "st": 0, "bm": 0 }, { "ddd": 0, "ind": 2, "ty": 4, "nm": "Merged Shape Layer", "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 1, "k": [ { "i": { "x": [ 0.667 ], "y": [ 1 ] }, "o": { "x": [ 0.547 ], "y": [ 0 ] }, "t": 0, "s": [ 0 ] }, { "i": { "x": [ 0.845 ], "y": [ 1 ] }, "o": { "x": [ 0.333 ], "y": [ 0 ] }, "t": 77, "s": [ 35 ] }, { "t": 150, "s": [ 0 ] } ], "ix": 10 }, "p": { "a": 1, "k": [ { "i": { "x": 0.667, "y": 1 }, "o": { "x": 0.333, "y": 0 }, "t": 0, "s": [ 390.319, 298.2, 0 ], "to": [ 0, -2.583, 0 ], "ti": [ 0, 0, 0 ] }, { "i": { "x": 0.667, "y": 1 }, "o": { "x": 0.333, "y": 0 }, "t": 44, "s": [ 390.319, 282.7, 0 ], "to": [ 0, 0, 0 ], "ti": [ 0, 0, 0 ] }, { "i": { "x": 0.667, "y": 1 }, "o": { "x": 0.333, "y": 0 }, "t": 110, "s": [ 390.319, 319.25, 0 ], "to": [ 0, 0, 0 ], "ti": [ 0, 0, 0 ] }, { "t": 150, "s": [ 390.319, 298.2, 0 ] } ], "ix": 2 }, "a": { "a": 0, "k": [ 664.319, 256.2, 0 ], "ix": 1 }, "s": { "a": 0, "k": [ 100, 100, 100 ], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ], "o": [ [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ], "v": [ [ 18.967, -3.189 ], [ -18.967, 19.935 ], [ -0.949, -19.935 ] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.223528981209, 0.192156970501, 0.674510002136, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [ 236.879, 292.737 ], "ix": 2 }, "a": { "a": 0, "k": [ 0, 0 ], "ix": 1 }, "s": { "a": 0, "k": [ 100, 100 ], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 1", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [ 633.939, 275.369 ], "ix": 2 }, "a": { "a": 0, "k": [ 236.879, 292.737 ], "ix": 1 }, "s": { "a": 0, "k": [ 50, 50 ], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "planete Outlines - Group 1", "np": 1, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false }, { "ty": "gr", "it": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [ 0, 0 ], [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ], "o": [ [ 0, 0 ], [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ], "v": [ [ -98.335, 64.79 ], [ -105.619, 4.984 ], [ 105.619, -64.79 ], [ -80.316, 24.919 ] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.278430998325, 0.294117987156, 0.847059011459, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [ 316.247, 247.882 ], "ix": 2 }, "a": { "a": 0, "k": [ 0, 0 ], "ix": 1 }, "s": { "a": 0, "k": [ 100, 100 ], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 2", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [ 673.623, 252.941 ], "ix": 2 }, "a": { "a": 0, "k": [ 316.247, 247.882 ], "ix": 1 }, "s": { "a": 0, "k": [ 50, 50 ], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "planete Outlines - Group 2", "np": 1, "cix": 2, "bm": 0, "ix": 2, "mn": "ADBE Vector Group", "hd": false }, { "ty": "gr", "it": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [ 0, 0 ], [ 0, 0 ], [ 0, 0 ], [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ], "o": [ [ 0, 0 ], [ 0, 0 ], [ 0, 0 ], [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ], "v": [ [ -133.812, -42.171 ], [ 133.812, -75.141 ], [ 5.765, 75.141 ], [ -61.708, 18.402 ], [ 124.227, -71.307 ], [ -87.011, -1.534 ] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.365000009537, 0.407999992371, 0.976000010967, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [ 297.638, 254.4 ], "ix": 2 }, "a": { "a": 0, "k": [ 0, 0 ], "ix": 1 }, "s": { "a": 0, "k": [ 100, 100 ], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 3", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [ 664.319, 256.2 ], "ix": 2 }, "a": { "a": 0, "k": [ 297.638, 254.4 ], "ix": 1 }, "s": { "a": 0, "k": [ 50, 50 ], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "planete Outlines - Group 3", "np": 1, "cix": 2, "bm": 0, "ix": 3, "mn": "ADBE Vector Group", "hd": false } ], "ip": 0, "op": 151, "st": 0, "bm": 0 }, { "ddd": 0, "ind": 3, "ty": 4, "nm": "planete Outlines - Group 5", "sr": 1, "ks": { "o": { "a": 1, "k": [ { "i": { "x": [ 0.667 ], "y": [ 1 ] }, "o": { "x": [ 0.333 ], "y": [ 0 ] }, "t": 0, "s": [ 0 ] }, { "i": { "x": [ 0.667 ], "y": [ 1 ] }, "o": { "x": [ 0.333 ], "y": [ 0 ] }, "t": 45, "s": [ 100 ] }, { "i": { "x": [ 0.667 ], "y": [ 1 ] }, "o": { "x": [ 0.333 ], "y": [ 0 ] }, "t": 102, "s": [ 100 ] }, { "t": 150, "s": [ 0 ] } ], "ix": 11 }, "r": { "a": 0, "k": 0, "ix": 10 }, "p": { "a": 1, "k": [ { "i": { "x": 0.833, "y": 0.833 }, "o": { "x": 0.167, "y": 0.167 }, "t": 0, "s": [ 327.38, 267.583, 0 ], "to": [ 25.833, 0, 0 ], "ti": [ -25.833, 0, 0 ] }, { "t": 150, "s": [ 482.38, 267.583, 0 ] } ], "ix": 2 }, "a": { "a": 0, "k": [ 171.76, 193.166, 0 ], "ix": 1 }, "s": { "a": 0, "k": [ 50, 50, 100 ], "ix": 6 } }, "ao": 0, "shapes": [ { "ty": "gr", "it": [ { "ind": 0, "ty": "sh", "ix": 1, "ks": { "a": 0, "k": { "i": [ [ 13.485, 0 ], [ 4.38, -4.171 ], [ 21.913, 0 ], [ 3.575, -18.765 ], [ 1.851, 0 ], [ 0, -13.484 ], [ -0.011, -0.291 ], [ 1.599, 0 ], [ 0, -6.743 ], [ -6.742, 0 ], [ 0, 0 ], [ 0, 13.485 ] ], "o": [ [ -6.526, 0 ], [ -0.793, -21.719 ], [ -19.806, 0 ], [ -1.734, -0.391 ], [ -13.485, 0 ], [ 0, 0.293 ], [ -1.4, -0.559 ], [ -6.742, 0 ], [ 0, 6.742 ], [ 0, 0 ], [ 13.485, 0 ], [ 0, -13.484 ] ], "v": [ [ 59.669, -8.242 ], [ 42.84, -1.506 ], [ 2.287, -40.592 ], [ -37.576, -7.638 ], [ -42.962, -8.242 ], [ -67.378, 16.174 ], [ -67.356, 17.049 ], [ -71.878, 16.174 ], [ -84.086, 28.383 ], [ -71.878, 40.591 ], [ 59.669, 40.591 ], [ 84.086, 16.174 ] ], "c": true }, "ix": 2 }, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false }, { "ty": "fl", "c": { "a": 0, "k": [ 0.816000007181, 0.823999980852, 0.827000038297, 1 ], "ix": 4 }, "o": { "a": 0, "k": 100, "ix": 5 }, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false }, { "ty": "tr", "p": { "a": 0, "k": [ 171.76, 193.166 ], "ix": 2 }, "a": { "a": 0, "k": [ 0, 0 ], "ix": 1 }, "s": { "a": 0, "k": [ 100, 100 ], "ix": 3 }, "r": { "a": 0, "k": 0, "ix": 6 }, "o": { "a": 0, "k": 100, "ix": 7 }, "sk": { "a": 0, "k": 0, "ix": 4 }, "sa": { "a": 0, "k": 0, "ix": 5 }, "nm": "Transform" } ], "nm": "Group 5", "np": 2, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false } ], "ip": 0, "op": 151, "st": 0, "bm": 0 } ] } ], "layers": [ { "ddd": 0, "ind": 1, "ty": 0, "nm": "Pre-comp 1", "refId": "comp_0", "sr": 1, "ks": { "o": { "a": 0, "k": 100, "ix": 11 }, "r": { "a": 0, "k": 0, "ix": 10 }, "p": { "a": 0, "k": [ 406, 306, 0 ], "ix": 2 }, "a": { "a": 0, "k": [ 400, 300, 0 ], "ix": 1 }, "s": { "a": 0, "k": [ 179, 179, 100 ], "ix": 6 } }, "ao": 0, "w": 800, "h": 600, "ip": 0, "op": 147, "st": 0, "bm": 0 } ], "markers": [] } ================================================ FILE: simple_live_app/assets/statement.txt ================================================ 在使用本软件之前,请您仔细阅读以下内容,并确保您充分理解并同意以下条款: 1、本软件为开源软件,您可以免费获取并使用该软件。 2、本软件完全基于您个人意愿使用,您应该对自己的使用行为和所有结果承担全部责任。 3、本软件仅供学习交流、科研等非商业性质的用途,严禁将本软件用于商业目的。如有任何商业行为,均与本软件无关。 4、本软件并不保证与所有操作系统或硬件设备兼容。本软件作者或贡献者不对因使用本软件而产生的任何技术或安全问题承担责任。 5、本软件作者或贡献者不承担因使用本软件而造成的任何直接、间接、特殊或后果性的损失或损害的责任,包括但不限于财产损失、商业利润损失、信息或数据丢失或损坏等。 6、本软件使用者应遵守国家相关法律法规和使用规范,不得利用本软件从事任何违法违规行为。如因使用本软件而导致的违法行为,使用者应承担相应的法律责任。 7、本软件不会收集、存储、使用任何用户的个人信息,包括但不限于姓名、地址、电子邮件地址、电话号码等。在使用本软件过程中,不会进行任何形式的个人信息采集。如用户提供任何个人信息,将被视为用户已自愿提供,并且用户将自行承担由此产生的所有法律责任。 8、本软件作者或贡献者保留随时修改、增加、删除本免责声明中的内容而不另行通知的权利。 9、如果本软件存在侵犯您的合法权益的情况,请及时与作者联系,作者将会及时删除有关内容。 如您不同意本免责声明中的任何内容,请勿使用本软件。使用本软件即代表您已完全理解并同意上述内容。 ================================================ FILE: simple_live_app/distribute_options.yaml ================================================ output: build/dist/ ================================================ FILE: simple_live_app/flutter_rust_bridge.yaml ================================================ rust_input: crate::api rust_root: rust/ dart_output: lib/src/rust ================================================ FILE: simple_live_app/integration_test/simple_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:simple_live_app/main.dart'; import 'package:simple_live_app/src/rust/frb_generated.dart'; import 'package:integration_test/integration_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); setUpAll(() async => await RustLib.init()); testWidgets('Can call rust function', (WidgetTester tester) async { await tester.pumpWidget(const MyApp()); expect(find.textContaining('Result: `Hello, Tom!`'), findsOneWidget); }); } ================================================ FILE: simple_live_app/ios/.gitignore ================================================ **/dgph *.mode1v3 *.mode2v3 *.moved-aside *.pbxuser *.perspectivev3 **/*sync/ .sconsign.dblite .tags* **/.vagrant/ **/DerivedData/ Icon? **/Pods/ **/.symlinks/ profile xcuserdata **/.generated/ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ Flutter/flutter_export_environment.sh ServiceDefinitions.json Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !default.mode1v3 !default.mode2v3 !default.pbxuser !default.perspectivev3 ================================================ FILE: simple_live_app/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 13.0 ================================================ FILE: simple_live_app/ios/Flutter/Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: simple_live_app/ios/Flutter/Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: simple_live_app/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project platform :ios, '15.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', ## dart: PermissionGroup.calendar # 'PERMISSION_EVENTS=1', ## dart: PermissionGroup.reminders # 'PERMISSION_REMINDERS=1', ## dart: PermissionGroup.contacts # 'PERMISSION_CONTACTS=1', ## dart: PermissionGroup.camera #'PERMISSION_CAMERA=1', ## dart: PermissionGroup.microphone # 'PERMISSION_MICROPHONE=1', ## dart: PermissionGroup.speech # 'PERMISSION_SPEECH_RECOGNIZER=1', ## dart: PermissionGroup.photos 'PERMISSION_PHOTOS=1', ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse] #'PERMISSION_LOCATION=1', ## dart: PermissionGroup.notification #'PERMISSION_NOTIFICATIONS=1', ## dart: PermissionGroup.mediaLibrary # 'PERMISSION_MEDIA_LIBRARY=1', ## dart: PermissionGroup.sensors # 'PERMISSION_SENSORS=1', ## dart: PermissionGroup.bluetooth # 'PERMISSION_BLUETOOTH=1', ## dart: PermissionGroup.appTrackingTransparency # 'PERMISSION_APP_TRACKING_TRANSPARENCY=1', ## dart: PermissionGroup.criticalAlerts # 'PERMISSION_CRITICAL_ALERTS=1' ] end end end ================================================ FILE: simple_live_app/ios/Runner/AppDelegate.swift ================================================ import UIKit import Flutter @main @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { return super.application(application, didFinishLaunchingWithOptions: launchOptions) } func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) } } ================================================ FILE: simple_live_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images": [ { "size": "20x20", "idiom": "iphone", "filename": "icon-20@2x.png", "scale": "2x" }, { "size": "20x20", "idiom": "iphone", "filename": "icon-20@3x.png", "scale": "3x" }, { "size": "29x29", "idiom": "iphone", "filename": "icon-29.png", "scale": "1x" }, { "size": "29x29", "idiom": "iphone", "filename": "icon-29@2x.png", "scale": "2x" }, { "size": "29x29", "idiom": "iphone", "filename": "icon-29@3x.png", "scale": "3x" }, { "size": "40x40", "idiom": "iphone", "filename": "icon-40@2x.png", "scale": "2x" }, { "size": "40x40", "idiom": "iphone", "filename": "icon-40@3x.png", "scale": "3x" }, { "size": "60x60", "idiom": "iphone", "filename": "icon-60@2x.png", "scale": "2x" }, { "size": "60x60", "idiom": "iphone", "filename": "icon-60@3x.png", "scale": "3x" }, { "size": "20x20", "idiom": "ipad", "filename": "icon-20-ipad.png", "scale": "1x" }, { "size": "20x20", "idiom": "ipad", "filename": "icon-20@2x-ipad.png", "scale": "2x" }, { "size": "29x29", "idiom": "ipad", "filename": "icon-29-ipad.png", "scale": "1x" }, { "size": "29x29", "idiom": "ipad", "filename": "icon-29@2x-ipad.png", "scale": "2x" }, { "size": "40x40", "idiom": "ipad", "filename": "icon-40.png", "scale": "1x" }, { "size": "40x40", "idiom": "ipad", "filename": "icon-40@2x.png", "scale": "2x" }, { "size": "76x76", "idiom": "ipad", "filename": "icon-76.png", "scale": "1x" }, { "size": "76x76", "idiom": "ipad", "filename": "icon-76@2x.png", "scale": "2x" }, { "size": "83.5x83.5", "idiom": "ipad", "filename": "icon-83.5@2x.png", "scale": "2x" }, { "size": "1024x1024", "idiom": "ios-marketing", "filename": "icon-1024.png", "scale": "1x" } ], "info": { "version": 1, "author": "icon.wuruihong.com" } } ================================================ FILE: simple_live_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "LaunchImage.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "LaunchImage@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "LaunchImage@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: simple_live_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # Launch Screen Assets You can customize the launch screen with your own desired assets by replacing the image files in this directory. You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. ================================================ FILE: simple_live_app/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: simple_live_app/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: simple_live_app/ios/Runner/Info.plist ================================================ CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName Simple Live CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName simple_live_app CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS NSAppTransportSecurity NSAllowsArbitraryLoads UIApplicationSupportsIndirectInputEvents UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance UIBackgroundModes audio NSPhotoLibraryUsageDescription 保存图片至相册 UISupportsDocumentBrowser io.flutter.embedded_views_preview NSCameraUsageDescription 用于扫描二维码 UIApplicationSceneManifest UIApplicationSupportsMultipleScenes UISceneConfigurations UIWindowSceneSessionRoleApplication UISceneClassNameUIWindowScene UISceneDelegateClassNameFlutterSceneDelegate UISceneConfigurationNameflutter UISceneStoryboardFileMain ================================================ FILE: simple_live_app/ios/Runner/Runner-Bridging-Header.h ================================================ #import "GeneratedPluginRegistrant.h" ================================================ FILE: simple_live_app/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 73C9599770D417F3E7B681E2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 60E92DDDD814CB6531C03BCD /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0874FB99E210639E7D21C1F0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 2778CC04696D7C0E532931A0 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 60E92DDDD814CB6531C03BCD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6C36D91D13E89663DBB13E51 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 73C9599770D417F3E7B681E2 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 65B8FB167D986F3BCF168FB6 /* Frameworks */ = { isa = PBXGroup; children = ( 60E92DDDD814CB6531C03BCD /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, FD158AC2F4211953A42B4A25 /* Pods */, 65B8FB167D986F3BCF168FB6 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; FD158AC2F4211953A42B4A25 /* Pods */ = { isa = PBXGroup; children = ( 2778CC04696D7C0E532931A0 /* Pods-Runner.debug.xcconfig */, 6C36D91D13E89663DBB13E51 /* Pods-Runner.release.xcconfig */, 0874FB99E210639E7D21C1F0 /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( EEC3C9B6719E92514BD4D205 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 6D32CBFCD866586D78A69BE1 /* [CP] Embed Pods Frameworks */, 92FA5F4E31AA12D8C9AA408A /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 6D32CBFCD866586D78A69BE1 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 92FA5F4E31AA12D8C9AA408A /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; EEC3C9B6719E92514BD4D205 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Profile; }; 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 9R87RMF9C9; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.slotsun.slive; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 9R87RMF9C9; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.slotsun.slive; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 9R87RMF9C9; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.slotsun.slive; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: simple_live_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: simple_live_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: simple_live_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: simple_live_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: simple_live_app/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: simple_live_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: simple_live_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: simple_live_app/lib/app/app_style.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; class AppColors { static ColorScheme lightColorScheme = ColorScheme.fromSeed( // primarySwatch: Colors.blue, seedColor: const Color(0xff3498db), brightness: Brightness.light, ); static ColorScheme darkColorScheme = ColorScheme.fromSeed( seedColor: const Color(0xff3498db), brightness: Brightness.dark, ); static const Color black333 = Color(0xFF333333); } class AppStyle { static ThemeData light({String? fontFamily}) { return ThemeData( colorScheme: AppColors.lightColorScheme, useMaterial3: true, fontFamily: fontFamily, visualDensity: VisualDensity.standard, appBarTheme: AppBarTheme( //elevation: 0, centerTitle: true, titleTextStyle: TextStyle( fontFamily: fontFamily, fontSize: 16, color: AppColors.black333, ), foregroundColor: AppColors.black333, systemOverlayStyle: SystemUiOverlayStyle.dark.copyWith( systemNavigationBarColor: Colors.transparent, ), ), ); } static ThemeData darkTheme({String? fontFamily}) { return ThemeData.dark().copyWith( colorScheme: AppColors.darkColorScheme, visualDensity: VisualDensity.standard, textTheme: ThemeData.dark().textTheme.apply( fontFamily: fontFamily, ), primaryTextTheme: ThemeData().textTheme.apply( fontFamily: fontFamily, ), appBarTheme: AppBarTheme( //elevation: 0, centerTitle: true, titleTextStyle: TextStyle( fontFamily: fontFamily, fontSize: 16, color: Colors.white, ), foregroundColor: Colors.white, systemOverlayStyle: SystemUiOverlayStyle.light.copyWith( systemNavigationBarColor: Colors.transparent, ), ), // radioTheme: RadioThemeData( // fillColor: MaterialStateProperty.all(AppColors.darkColorScheme.primary), // ), // checkboxTheme: CheckboxThemeData( // fillColor: MaterialStateProperty.all(AppColors.darkColorScheme.primary), // ), // tabBarTheme: TabBarTheme( // labelColor: AppColors.darkColorScheme.primary, // unselectedLabelColor: Colors.white70, // indicator: RectangularIndicator( // color: Colors.white.withAlpha(50), // topLeftRadius: 24, // bottomLeftRadius: 24, // topRightRadius: 24, // bottomRightRadius: 24, // verticalPadding: 8, // horizontalPadding: 0, // ), // ), ); } static const vGap4 = SizedBox( height: 4, ); static const vGap8 = SizedBox( height: 8, ); static const vGap12 = SizedBox( height: 12, ); static const vGap24 = SizedBox( height: 24, ); static const vGap32 = SizedBox( height: 32, ); static const vGap48 = SizedBox( height: 48, ); static const hGap4 = SizedBox( width: 4, ); static const hGap8 = SizedBox( width: 8, ); static const hGap12 = SizedBox( width: 12, ); static const hGap16 = SizedBox( width: 16, ); static const hGap24 = SizedBox( width: 24, ); static const hGap32 = SizedBox( width: 32, ); static const hGap48 = SizedBox( width: 48, ); static const edgeInsetsH4 = EdgeInsets.symmetric(horizontal: 4); static const edgeInsetsH8 = EdgeInsets.symmetric(horizontal: 8); static const edgeInsetsH12 = EdgeInsets.symmetric(horizontal: 12); static const edgeInsetsH16 = EdgeInsets.symmetric(horizontal: 16); static const edgeInsetsH20 = EdgeInsets.symmetric(horizontal: 20); static const edgeInsetsH24 = EdgeInsets.symmetric(horizontal: 24); static const edgeInsetsV4 = EdgeInsets.symmetric(vertical: 4); static const edgeInsetsV8 = EdgeInsets.symmetric(vertical: 8); static const edgeInsetsV12 = EdgeInsets.symmetric(vertical: 12); static const edgeInsetsV24 = EdgeInsets.symmetric(vertical: 24); static const edgeInsetsA4 = EdgeInsets.all(4); static const edgeInsetsA8 = EdgeInsets.all(8); static const edgeInsetsA12 = EdgeInsets.all(12); static const edgeInsetsA16 = EdgeInsets.all(16); static const edgeInsetsA20 = EdgeInsets.all(20); static const edgeInsetsA24 = EdgeInsets.all(24); static const edgeInsetsR4 = EdgeInsets.only(right: 4); static const edgeInsetsR8 = EdgeInsets.only(right: 8); static const edgeInsetsR12 = EdgeInsets.only(right: 12); static const edgeInsetsR16 = EdgeInsets.only(right: 16); static const edgeInsetsR20 = EdgeInsets.only(right: 20); static const edgeInsetsR24 = EdgeInsets.only(right: 24); static const edgeInsetsL4 = EdgeInsets.only(left: 4); static const edgeInsetsL8 = EdgeInsets.only(left: 8); static const edgeInsetsL12 = EdgeInsets.only(left: 12); static const edgeInsetsL16 = EdgeInsets.only(left: 16); static const edgeInsetsL20 = EdgeInsets.only(left: 20); static const edgeInsetsL24 = EdgeInsets.only(left: 24); static const edgeInsetsT4 = EdgeInsets.only(top: 4); static const edgeInsetsT8 = EdgeInsets.only(top: 8); static const edgeInsetsT12 = EdgeInsets.only(top: 12); static const edgeInsetsT24 = EdgeInsets.only(top: 24); static const edgeInsetsB4 = EdgeInsets.only(bottom: 4); static const edgeInsetsB8 = EdgeInsets.only(bottom: 8); static const edgeInsetsB12 = EdgeInsets.only(bottom: 12); static const edgeInsetsB24 = EdgeInsets.only(bottom: 24); static BorderRadius radius4 = BorderRadius.circular(4); static BorderRadius radius8 = BorderRadius.circular(8); static BorderRadius radius12 = BorderRadius.circular(12); static BorderRadius radius24 = BorderRadius.circular(24); static BorderRadius radius32 = BorderRadius.circular(32); static BorderRadius radius48 = BorderRadius.circular(48); /// 顶部状态栏的高度 static double get statusBarHeight => MediaQuery.of(Get.context!).padding.top; /// 底部导航条的高度 static double get bottomBarHeight => MediaQuery.of(Get.context!).padding.bottom; static Divider get divider => Divider( height: 1, thickness: 1, indent: 16, endIndent: 16, color: Colors.grey.withAlpha(25), ); } ================================================ FILE: simple_live_app/lib/app/constant.dart ================================================ import 'package:flutter/material.dart'; import 'package:remixicon/remixicon.dart'; class Constant { static const String kUpdateFollow = "UpdateFollow"; static const String kUpdateHistory = "UpdateHistory"; static final Map allHomePages = { "recommend": HomePageItem( iconData: Remix.home_smile_line, title: "首页", index: 0, ), "follow": HomePageItem( iconData: Remix.heart_line, title: "关注", index: 1, ), "category": HomePageItem( iconData: Remix.apps_line, title: "分类", index: 2, ), "user": HomePageItem( iconData: Remix.user_smile_line, title: "我的", index: 3, ), }; static const String kBiliBili = "bilibili"; static const String kDouyu = "douyu"; static const String kHuya = "huya"; static const String kDouyin = "douyin"; static const String kTwitch = "twitch"; } class HomePageItem { final IconData iconData; final String title; final int index; HomePageItem({ required this.iconData, required this.title, required this.index, }); } enum DownloadState { notDownloaded, downloading, downloaded, } // 排序方法 enum SortMethod { watchDuration, siteId, recently, userNameASC, userNameDESC, } extension SortMethodStore on SortMethod { String get storeValue => name; static SortMethod fromStore(String? v) { if (v == null) return SortMethod.watchDuration; return SortMethod.values.firstWhere( (e) => e.name == v, orElse: () => SortMethod.watchDuration, ); } } ================================================ FILE: simple_live_app/lib/app/controller/app_settings_controller.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/services/local_storage_service.dart'; class AppSettingsController extends GetxController { static AppSettingsController get instance => Get.find(); /// 缩放模式 var scaleMode = 0.obs; var themeMode = 0.obs; var firstRun = false; var dbVer = 0; @override void onInit() { themeMode.value = LocalStorageService.instance .getValue(LocalStorageService.kThemeMode, 0); firstRun = LocalStorageService.instance .getValue(LocalStorageService.kFirstRun, true); danmuSize.value = LocalStorageService.instance .getValue(LocalStorageService.kDanmuSize, 16.0); danmuOpacity.value = LocalStorageService.instance .getValue(LocalStorageService.kDanmuOpacity, 1.0); danmuArea.value = LocalStorageService.instance .getValue(LocalStorageService.kDanmuArea, 0.8); danmuSpeed.value = LocalStorageService.instance .getValue(LocalStorageService.kDanmuSpeed, 10.0); danmuEnable.value = LocalStorageService.instance .getValue(LocalStorageService.kDanmuEnable, true); danmakuMaskEnable.value = LocalStorageService.instance .getValue(LocalStorageService.kDanmakuMaskEnable, false); danmuStrokeWidth.value = LocalStorageService.instance .getValue(LocalStorageService.kDanmuStrokeWidth, 2.0); danmuTopMargin.value = LocalStorageService.instance .getValue(LocalStorageService.kDanmuTopMargin, 0.0); danmuBottomMargin.value = LocalStorageService.instance .getValue(LocalStorageService.kDanmuBottomMargin, 0.0); danmuFontWeight.value = LocalStorageService.instance.getValue( LocalStorageService.kDanmuFontWeight, FontWeight.normal.index); hardwareDecode.value = LocalStorageService.instance .getValue(LocalStorageService.kHardwareDecode, true); chatTextSize.value = LocalStorageService.instance .getValue(LocalStorageService.kChatTextSize, 14.0); chatTextGap.value = LocalStorageService.instance .getValue(LocalStorageService.kChatTextGap, 4.0); chatBubbleStyle.value = LocalStorageService.instance.getValue( LocalStorageService.kChatBubbleStyle, false, ); qualityLevel.value = LocalStorageService.instance .getValue(LocalStorageService.kQualityLevel, 2); qualityLevelCellular.value = LocalStorageService.instance .getValue(LocalStorageService.kQualityLevelCellular, 1); autoExitEnable.value = LocalStorageService.instance .getValue(LocalStorageService.kAutoExitEnable, false); autoExitDuration.value = LocalStorageService.instance .getValue(LocalStorageService.kAutoExitDuration, 60); roomAutoExitDuration.value = LocalStorageService.instance .getValue(LocalStorageService.kRoomAutoExitDuration, 60); playerCompatMode.value = LocalStorageService.instance .getValue(LocalStorageService.kPlayerCompatMode, false); playerAutoPause.value = LocalStorageService.instance .getValue(LocalStorageService.kPlayerAutoPause, false); playerForceHttps.value = LocalStorageService.instance .getValue(LocalStorageService.kPlayerForceHttps, false); douyinHlsFirst.value = LocalStorageService.instance .getValue(LocalStorageService.kDouyinHlsFirst, false); autoFullScreen.value = LocalStorageService.instance .getValue(LocalStorageService.kAutoFullScreen, false); // ignore: invalid_use_of_protected_member shieldList.value = LocalStorageService.instance.shieldBox.values.toSet(); scaleMode.value = LocalStorageService.instance.getValue( LocalStorageService.kPlayerScaleMode, 0, ); playerVolume.value = LocalStorageService.instance.getValue( LocalStorageService.kPlayerVolume, 100.0, ); pipHideDanmu.value = LocalStorageService.instance .getValue(LocalStorageService.kPIPHideDanmu, true); bilibiliLoginTip.value = LocalStorageService.instance .getValue(LocalStorageService.kBilibiliLoginTip, true); playerBufferSize.value = LocalStorageService.instance .getValue(LocalStorageService.kPlayerBufferSize, 32); logEnable.value = LocalStorageService.instance .getValue(LocalStorageService.kLogEnable, false); if (logEnable.value) { Log.initWriter(); } firebaseEnable.value = LocalStorageService.instance .getValue(LocalStorageService.kFirebaseEnable, true); customPlayerOutput.value = LocalStorageService.instance .getValue(LocalStorageService.kCustomPlayerOutput, false); videoOutputDriver.value = LocalStorageService.instance.getValue( LocalStorageService.kVideoOutputDriver, Platform.isAndroid ? "mediacodec_embed" : "libmpv", ); audioOutputDriver.value = LocalStorageService.instance.getValue( LocalStorageService.kAudioOutputDriver, Platform.isAndroid ? "audiotrack" : Platform.isLinux ? "pulse" : Platform.isWindows ? "wasapi" : Platform.isIOS ? "audiounit" : Platform.isMacOS ? "coreaudio" : "sdl", ); videoHardwareDecoder.value = LocalStorageService.instance.getValue( LocalStorageService.kVideoHardwareDecoder, Platform.isAndroid ? "mediacodec" : "auto", ); videoDoubleBuffering.value = LocalStorageService.instance.getValue( LocalStorageService.kVideoDoubleBuffering, false, ); autoUpdateFollowEnable.value = LocalStorageService.instance .getValue(LocalStorageService.kAutoUpdateFollowEnable, true); autoUpdateFollowDuration.value = LocalStorageService.instance .getValue(LocalStorageService.kUpdateFollowDuration, 10); updateFollowThreadCount.value = LocalStorageService.instance .getValue(LocalStorageService.kUpdateFollowThreadCount, 4); // danmaku-去重参数 danmuFrequencyControl.value = LocalStorageService.instance .getValue(LocalStorageService.kDanmuFrequencyControl, false); danmuMaxFrequency.value = LocalStorageService.instance .getValue(LocalStorageService.kDanmuMaxFrequency, 3); danmuTextNormalization.value = LocalStorageService.instance .getValue(LocalStorageService.kDanmuTextNormalization, true); danmuWindowMs.value = LocalStorageService.instance .getValue(LocalStorageService.kDanmuWindowMs, 15); dbVer = LocalStorageService.instance .getValue(LocalStorageService.kHiveDbVer, 10708); followSortMethod.value = SortMethodStore.fromStore( LocalStorageService.instance.getValue( LocalStorageService.kFollowSortMethod, SortMethod.watchDuration.storeValue)); followStyleNotGrid.value = LocalStorageService.instance .getValue(LocalStorageService.kFollowStyleNotGrid, true); initSiteSort(); initHomeSort(); super.onInit(); } void initSiteSort() { var sort = LocalStorageService.instance .getValue( LocalStorageService.kSiteSort, Sites.allSites.keys.join(","), ) .split(","); //如果数量与allSites的数量不一致,将缺失的添加上 if (sort.length != Sites.allSites.length) { var keys = Sites.allSites.keys.toList(); for (var i = 0; i < keys.length; i++) { if (!sort.contains(keys[i])) { sort.add(keys[i]); } } } siteSort.value = sort; } void initHomeSort() { var sort = LocalStorageService.instance .getValue( LocalStorageService.kHomeSort, Constant.allHomePages.keys.join(","), ) .split(","); //如果数量与allSites的数量不一致,将缺失的添加上 if (sort.length != Constant.allHomePages.length) { var keys = Constant.allHomePages.keys.toList(); for (var i = 0; i < keys.length; i++) { if (!sort.contains(keys[i])) { sort.add(keys[i]); } } } homeSort.value = sort; } void setNoFirstRun() { LocalStorageService.instance.setValue(LocalStorageService.kFirstRun, false); } var hardwareDecode = true.obs; void setHardwareDecode(bool e) { hardwareDecode.value = e; LocalStorageService.instance .setValue(LocalStorageService.kHardwareDecode, e); } var chatTextSize = 14.0.obs; void setChatTextSize(double e) { chatTextSize.value = e; LocalStorageService.instance.setValue(LocalStorageService.kChatTextSize, e); } var chatTextGap = 4.0.obs; void setChatTextGap(double e) { chatTextGap.value = e; LocalStorageService.instance.setValue(LocalStorageService.kChatTextGap, e); } var chatBubbleStyle = false.obs; void setChatBubbleStyle(bool e) { chatBubbleStyle.value = e; LocalStorageService.instance .setValue(LocalStorageService.kChatBubbleStyle, e); } var danmuSize = 16.0.obs; void setDanmuSize(double e) { danmuSize.value = e; LocalStorageService.instance.setValue(LocalStorageService.kDanmuSize, e); } var danmuSpeed = 10.0.obs; void setDanmuSpeed(double e) { danmuSpeed.value = e; LocalStorageService.instance.setValue(LocalStorageService.kDanmuSpeed, e); } var danmuArea = 0.8.obs; void setDanmuArea(double e) { danmuArea.value = e; LocalStorageService.instance.setValue(LocalStorageService.kDanmuArea, e); } var danmuOpacity = 1.0.obs; void setDanmuOpacity(double e) { danmuOpacity.value = e; LocalStorageService.instance.setValue(LocalStorageService.kDanmuOpacity, e); } var danmuEnable = true.obs; void setDanmuEnable(bool e) { danmuEnable.value = e; LocalStorageService.instance.setValue(LocalStorageService.kDanmuEnable, e); } var danmakuMaskEnable = false.obs; void setDanmakuMaskEnable(bool e) { danmakuMaskEnable.value = e; LocalStorageService.instance .setValue(LocalStorageService.kDanmakuMaskEnable, e); } var danmuStrokeWidth = 2.0.obs; void setDanmuStrokeWidth(double e) { danmuStrokeWidth.value = e; LocalStorageService.instance .setValue(LocalStorageService.kDanmuStrokeWidth, e); } var danmuFontWeight = FontWeight.normal.index.obs; void setDanmuFontWeight(int e) { danmuFontWeight.value = e; LocalStorageService.instance .setValue(LocalStorageService.kDanmuFontWeight, e); } var qualityLevel = 1.obs; void setQualityLevel(int level) { qualityLevel.value = level; LocalStorageService.instance .setValue(LocalStorageService.kQualityLevel, level); } var qualityLevelCellular = 1.obs; void setQualityLevelCellular(int level) { qualityLevelCellular.value = level; LocalStorageService.instance .setValue(LocalStorageService.kQualityLevelCellular, level); } var autoExitEnable = false.obs; void setAutoExitEnable(bool e) { autoExitEnable.value = e; LocalStorageService.instance .setValue(LocalStorageService.kAutoExitEnable, e); } var autoExitDuration = 60.obs; void setAutoExitDuration(int e) { autoExitDuration.value = e; LocalStorageService.instance .setValue(LocalStorageService.kAutoExitDuration, e); } var roomAutoExitDuration = 60.obs; void setRoomAutoExitDuration(int e) { roomAutoExitDuration.value = e; LocalStorageService.instance .setValue(LocalStorageService.kRoomAutoExitDuration, e); } var playerCompatMode = false.obs; void setPlayerCompatMode(bool e) { playerCompatMode.value = e; LocalStorageService.instance .setValue(LocalStorageService.kPlayerCompatMode, e); } var playerBufferSize = 32.obs; void setPlayerBufferSize(int e) { playerBufferSize.value = e; LocalStorageService.instance .setValue(LocalStorageService.kPlayerBufferSize, e); } var playerAutoPause = false.obs; void setPlayerAutoPause(bool e) { playerAutoPause.value = e; LocalStorageService.instance .setValue(LocalStorageService.kPlayerAutoPause, e); } var autoFullScreen = false.obs; void setAutoFullScreen(bool e) { autoFullScreen.value = e; LocalStorageService.instance .setValue(LocalStorageService.kAutoFullScreen, e); } RxSet shieldList = {}.obs; void addShieldList(String e) { shieldList.add(e); LocalStorageService.instance.shieldBox.put(e, e); } void removeShieldList(String e) { shieldList.remove(e); LocalStorageService.instance.shieldBox.delete(e); } Future clearShieldList() async { shieldList.clear(); await LocalStorageService.instance.shieldBox.clear(); } void setScaleMode(int value) { scaleMode.value = value; LocalStorageService.instance.setValue( LocalStorageService.kPlayerScaleMode, value, ); } RxList siteSort = RxList(); void setSiteSort(List e) { siteSort.value = e; LocalStorageService.instance.setValue( LocalStorageService.kSiteSort, siteSort.join(","), ); } RxList homeSort = RxList(); void setHomeSort(List e) { homeSort.value = e; LocalStorageService.instance.setValue( LocalStorageService.kHomeSort, homeSort.join(","), ); } Rx playerVolume = 100.0.obs; void setPlayerVolume(double value) { playerVolume.value = value; LocalStorageService.instance.setValue( LocalStorageService.kPlayerVolume, value, ); } var pipHideDanmu = true.obs; void setPIPHideDanmu(bool e) { pipHideDanmu.value = e; LocalStorageService.instance.setValue(LocalStorageService.kPIPHideDanmu, e); } var danmuTopMargin = 0.0.obs; void setDanmuTopMargin(double e) { danmuTopMargin.value = e; LocalStorageService.instance .setValue(LocalStorageService.kDanmuTopMargin, e); } var danmuBottomMargin = 0.0.obs; void setDanmuBottomMargin(double e) { danmuBottomMargin.value = e; LocalStorageService.instance .setValue(LocalStorageService.kDanmuBottomMargin, e); } /// 弹幕去重参数设置 var danmuTextNormalization = true.obs; void setDanmuTextNormalization(bool e) { danmuTextNormalization.value = e; LocalStorageService.instance .setValue(LocalStorageService.kDanmuTextNormalization, e); } var danmuMaxFrequency = 3.obs; void setDanmuMaxFrequency(int e) { danmuMaxFrequency.value = e; LocalStorageService.instance .setValue(LocalStorageService.kDanmuMaxFrequency, e); } var danmuFrequencyControl = true.obs; void setDanmuFrequencyControl(bool e) { danmuFrequencyControl.value = e; LocalStorageService.instance .setValue(LocalStorageService.kDanmuFrequencyControl, e); } var danmuWindowMs = 15.obs; void setDanmuWindowMs(int e) { danmuWindowMs.value = e; LocalStorageService.instance .setValue(LocalStorageService.kDanmuWindowMs, e); } var bilibiliLoginTip = true.obs; void setBiliBiliLoginTip(bool e) { bilibiliLoginTip.value = e; LocalStorageService.instance .setValue(LocalStorageService.kBilibiliLoginTip, e); } var logEnable = false.obs; void setLogEnable(bool e) { logEnable.value = e; LocalStorageService.instance.setValue(LocalStorageService.kLogEnable, e); } var firebaseEnable = true.obs; void setFirebaseEnable(bool e) { firebaseEnable.value = e; LocalStorageService.instance.setValue(LocalStorageService.kFirebaseEnable, e); } var customPlayerOutput = false.obs; void setCustomPlayerOutput(bool e) { customPlayerOutput.value = e; LocalStorageService.instance .setValue(LocalStorageService.kCustomPlayerOutput, e); } var videoOutputDriver = "".obs; void setVideoOutputDriver(String e) { videoOutputDriver.value = e; LocalStorageService.instance .setValue(LocalStorageService.kVideoOutputDriver, e); } var audioOutputDriver = "".obs; void setAudioOutputDriver(String e) { audioOutputDriver.value = e; LocalStorageService.instance .setValue(LocalStorageService.kAudioOutputDriver, e); } var videoHardwareDecoder = "".obs; void setVideoHardwareDecoder(String e) { videoHardwareDecoder.value = e; LocalStorageService.instance .setValue(LocalStorageService.kVideoHardwareDecoder, e); } var videoDoubleBuffering = false.obs; void setVideoDoubleBuffering(bool e) { videoDoubleBuffering.value = e; LocalStorageService.instance .setValue(LocalStorageService.kVideoDoubleBuffering, e); } var autoUpdateFollowEnable = false.obs; void setAutoUpdateFollowEnable(bool e) { autoUpdateFollowEnable.value = e; LocalStorageService.instance .setValue(LocalStorageService.kAutoUpdateFollowEnable, e); } var autoUpdateFollowDuration = 10.obs; void setAutoUpdateFollowDuration(int e) { autoUpdateFollowDuration.value = e; LocalStorageService.instance .setValue(LocalStorageService.kUpdateFollowDuration, e); } var updateFollowThreadCount = 4.obs; void setUpdateFollowThreadCount(int e) { updateFollowThreadCount.value = e; LocalStorageService.instance .setValue(LocalStorageService.kUpdateFollowThreadCount, e); } var playerForceHttps = false.obs; void setPlayerForceHttps(bool e) { playerForceHttps.value = e; LocalStorageService.instance .setValue(LocalStorageService.kPlayerForceHttps, e); } var douyinHlsFirst = false.obs; void setDouyinHlsFirst(bool e) { douyinHlsFirst.value = e; LocalStorageService.instance .setValue(LocalStorageService.kDouyinHlsFirst, e); } var followSortMethod = SortMethod.watchDuration.obs; void setFollowSortMethod(SortMethod e) { followSortMethod.value = e; LocalStorageService.instance .setValue(LocalStorageService.kFollowSortMethod, e.storeValue); } // 关注样式是否卡片化 var followStyleNotGrid = true.obs; void setFollowStyleNotGrid(bool e){ followStyleNotGrid.value = e; LocalStorageService.instance.setValue(LocalStorageService.kFollowStyleNotGrid, e); } } ================================================ FILE: simple_live_app/lib/app/controller/base_controller.dart ================================================ import 'dart:async'; import 'package:flutter/widgets.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:flutter_easyrefresh/easy_refresh.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; class BaseController extends GetxController { /// 加载中,更新页面 var pageLoadding = false.obs; /// 加载中,不会更新页面 var loadding = false; /// 空白页面 var pageEmpty = false.obs; /// 页面错误 var pageError = false.obs; /// 未登录 var notLogin = false.obs; /// 错误信息 var errorMsg = "".obs; /// 显示错误 /// * [msg] 错误信息 /// * [showPageError] 显示页面错误 /// * 只在第一页加载错误时showPageError=true,后续页加载错误时使用Toast弹出通知 void handleError(Object exception, {bool showPageError = false}) { Log.e(exception.toString(), StackTrace.current); var msg = exceptionToString(exception); if (showPageError) { pageError.value = true; errorMsg.value = msg; } else { SmartDialog.showToast(exceptionToString(msg)); } } String exceptionToString(Object exception) { return exception.toString().replaceAll("Exception:", ""); } void onLogin() {} void onLogout() {} } class BasePageController extends BaseController { final ScrollController scrollController = ScrollController(); final EasyRefreshController easyRefreshController = EasyRefreshController(); int currentPage = 1; int count = 0; int maxPage = 0; int pageSize = 24; var canLoadMore = false.obs; var list = [].obs; Future refreshData() async { currentPage = 1; list.value = []; await loadData(); } Future loadData() async { try { if (loadding) return; loadding = true; pageError.value = false; pageEmpty.value = false; notLogin.value = false; pageLoadding.value = currentPage == 1; var result = await getData(currentPage, pageSize); //是否可以加载更多 if (result.isNotEmpty) { currentPage++; canLoadMore.value = true; pageEmpty.value = false; } else { canLoadMore.value = false; if (currentPage == 1) { pageEmpty.value = true; } } // 赋值数据 if (currentPage == 1) { list.value = result; } else { list.addAll(result); } } catch (e) { handleError(e, showPageError: currentPage == 1); } finally { loadding = false; pageLoadding.value = false; } } Future> getData(int page, int pageSize) async { return []; } void scrollToTopOrRefresh() { if (scrollController.offset > 0) { scrollController.animateTo( 0, duration: const Duration(milliseconds: 200), curve: Curves.linear, ); } else { easyRefreshController.callRefresh(); } } } ================================================ FILE: simple_live_app/lib/app/custom_throttle.dart ================================================ /// 这个类的目的是简化 throttle 的操作,以便更好的理解代码 /// 主要作用:节流,如果在很短时间内都会调用同一个方法,除了第一个方法有用以外 /// 剩下的方法将会被舍弃,在 [eachDelayMilli] 时间后,才会允许下一次调用 /// 会保存一个方法,在最后还会调用一次,和普通的 throttle 不太一样 class DelayedThrottle { bool isInvoking = false; int eachDelayMilli; Future Function()? storeFunc; DelayedThrottle(this.eachDelayMilli); void invoke(Future Function() longCostFunc) { if (isInvoking) { storeFunc = longCostFunc; return; } storeFunc = null; isInvoking = true; longCostFunc().then((value) { Future.delayed(Duration(milliseconds: eachDelayMilli), () { isInvoking = false; if (storeFunc != null) { invoke(storeFunc!); } }); }); } } ================================================ FILE: simple_live_app/lib/app/event_bus.dart ================================================ import 'dart:async'; import 'package:simple_live_app/app/log.dart'; /// 全局事件 class EventBus { /// 点击了底部导航 static const String kBottomNavigationBarClicked = "BottomNavigationBarClicked"; /// 用户按了Esc static const String kEscapePressed = "EscapePressed"; static EventBus? _instance; static EventBus get instance { _instance ??= EventBus(); return _instance!; } final Map _streams = {}; /// 触发事件 void emit(String name, T data) { if (!_streams.containsKey(name)) { _streams.addAll({name: StreamController.broadcast()}); } Log.d("Emit Event:$name\r\n$data"); _streams[name]!.add(data); } /// 监听事件 StreamSubscription listen(String name, Function(dynamic)? onData) { if (!_streams.containsKey(name)) { _streams.addAll({name: StreamController.broadcast()}); } return _streams[name]!.stream.listen(onData); } } ================================================ FILE: simple_live_app/lib/app/log.dart ================================================ import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:logger/logger.dart'; import 'package:path_provider/path_provider.dart'; import 'package:simple_live_app/app/utils.dart'; class Log { static LogFileWriter? logFileWriter; static void initWriter() { logFileWriter = LogFileWriter(); } static void disposeWriter() { logFileWriter?.close(); logFileWriter = null; } static void writeLog(Object content, [Level level = Level.info]) { logFileWriter ?.write("[${level.name.toUpperCase()}] $_currentTime:$content"); } static RxList debugLogs = [].obs; static void addDebugLog(String content, Color? color) { if (kReleaseMode) { return; } if (content.contains("请求响应")) { content = content.split("\n").join('\n💡 '); } try { debugLogs.insert(0, DebugLogModel(DateTime.now(), content, color: color)); } catch (e) { if (kDebugMode) { print(e); } } } static Logger logger = Logger( printer: PrettyPrinter( methodCount: 0, errorMethodCount: 8, lineLength: 120, colors: true, printEmojis: true, dateTimeFormat: DateTimeFormat.none, ), ); static void d(String message, [bool writeFile = true]) { addDebugLog(message, Colors.orange); logger.d("${DateTime.now().toString()}\n$message"); if (writeFile) { writeLog(message, Level.debug); } } static void i(String message, [bool writeFile = true]) { addDebugLog(message, Colors.blue); logger.i("${DateTime.now().toString()}\n$message"); if (writeFile) { logFileWriter?.write("[INFO] $_currentTime:$message"); writeLog(message, Level.info); } } static void e(String message, StackTrace stackTrace, [bool writeFile = true]) { addDebugLog('$message\r\n\r\n$stackTrace', Colors.red); logger.e("${DateTime.now().toString()}\n$message", stackTrace: stackTrace); if (writeFile) { writeLog("$message\n$stackTrace", Level.error); } } static void w(String message, [bool writeFile = true]) { addDebugLog(message, Colors.pink); logger.w("${DateTime.now().toString()}\n$message"); if (writeFile) { writeLog(message, Level.warning); } } static void logPrint(dynamic obj, [bool writeFile = true]) { addDebugLog(obj.toString(), Colors.red); if (writeFile) { writeLog(obj, Level.info); } //logger.e(obj.toString(), obj, obj?.stackTrace); if (kDebugMode) { print(obj); } } static String get _currentTime => Utils.timeFormat.format(DateTime.now()); } class LogFileWriter { late String fileName; LogFileWriter() { var dt = DateFormat("yyyy-MM-dd HH-mm-ss").format(DateTime.now()); fileName = "$dt.log"; initFile(); } IOSink? fileWriter; void initFile() async { var supportDir = await getApplicationSupportDirectory(); var logDir = Directory("${supportDir.path}/log"); if (!await logDir.exists()) { await logDir.create(); } var logFile = File("${logDir.path}/$fileName"); fileWriter = logFile.openWrite(mode: FileMode.append); writeSystemInfo(); } void write(String content) { fileWriter?.write(content); fileWriter?.write("\r\n"); } Future close() async { await fileWriter?.close(); } void writeSystemInfo() async { DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); write("System Info:"); write("Current Time: ${DateTime.now()}"); write("Platform: ${Platform.operatingSystem}"); write("Version: ${Platform.operatingSystemVersion}"); write("Local: ${Platform.localeName}"); write( "App Version: ${Utils.packageInfo.version}+${Utils.packageInfo.buildNumber}"); if (Platform.isAndroid) { write((await deviceInfo.androidInfo).data.toString()); } else if (Platform.isIOS) { write((await deviceInfo.iosInfo).data.toString()); } else if (Platform.isLinux) { write((await deviceInfo.linuxInfo).data.toString()); } else if (Platform.isMacOS) { write((await deviceInfo.macOsInfo).data.toString()); } else if (Platform.isWindows) { write((await deviceInfo.windowsInfo).data.toString()); } write("End System Info"); } } class DebugLogModel { final String content; final DateTime datetime; final Color? color; DebugLogModel(this.datetime, this.content, {this.color}); } ================================================ FILE: simple_live_app/lib/app/sites.dart ================================================ import 'package:flutter/material.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/icons/live_icons.dart'; import 'package:simple_live_core/simple_live_core.dart'; class Sites { static final Map allSites = { Constant.kBiliBili: Site( id: Constant.kBiliBili, iconData: RemixIcons.bilibili_line, logo: "assets/images/bilibili_2.png", name: "哔哩哔哩", liveSite: BiliBiliSite(), ), Constant.kDouyu: Site( id: Constant.kDouyu, iconData: LiveIcons.douyu, logo: "assets/images/douyu.png", name: "斗鱼直播", liveSite: DouyuSite(), ), Constant.kHuya: Site( id: Constant.kHuya, iconData: LiveIcons.huya, logo: "assets/images/huya.png", name: "虎牙直播", liveSite: HuyaSite(), ), Constant.kDouyin: Site( id: Constant.kDouyin, iconData: RemixIcons.tiktok_line, logo: "assets/images/douyin.png", name: "抖音直播", liveSite: DouyinSite(), ), Constant.kTwitch: Site( id: Constant.kTwitch, iconData: RemixIcons.twitch_line, logo: "assets/images/Twitch.png", name: "Twitch", liveSite: TwitchSite(), ) }; static List get supportSites { return AppSettingsController.instance.siteSort .where((key) => Sites.allSites[key]?.name != 'Twitch') .map((key) => allSites[key]!) .toList(); } } class Site { final String id; final String name; final String logo; final IconData iconData; final LiveSite liveSite; Site({ required this.id, required this.liveSite, required this.logo, required this.name, required this.iconData, }); } ================================================ FILE: simple_live_app/lib/app/utils/archive.dart ================================================ import 'dart:convert'; import 'dart:io'; import 'package:archive/archive_io.dart'; import 'package:path/path.dart'; extension ArchiveExt on Archive { void addDirectoryToArchive(String dirPath, String parentPath) { final dir = Directory(dirPath); final entities = dir.listSync(recursive: false); for (final entity in entities) { final relativePath = relative(entity.path, from: parentPath); if (entity is File) { final data = entity.readAsBytesSync(); final archiveFile = ArchiveFile(relativePath, data.length, data); addFile(archiveFile); } else if (entity is Directory) { addDirectoryToArchive(entity.path, parentPath); } } } void add(String name, T raw) { final data = json.encode(raw); addFile( // 这样会出现问题 不清楚原因 ArchiveFile(name, data.length, utf8.encode(data)), ); } } ================================================ FILE: simple_live_app/lib/app/utils/document.dart ================================================ import 'dart:io'; import 'package:simple_live_app/app/log.dart'; // 扩展 Directory 类,添加清空文件夹的功能并验证是否为文件夹 extension DirectoryCleaner on Directory { Future clear() async { // 首先判断是否为文件夹 if (await exists() && await FileSystemEntity.isDirectory(path)) { // 列出文件夹中的所有文件和子文件夹 List files = listSync(); // 遍历文件列表并删除每个文件或子文件夹 for (FileSystemEntity file in files) { if (file is File) { await file.delete(); Log.i('删除文件: ${file.path}'); } else if (file is Directory) { await Directory(file.path).delete(recursive: true); Log.i('删除文件夹: ${file.path}'); } } Log.i('文件夹清空完成'); } else { Log.i('$path 不是一个有效的文件夹'); } } // 阻塞主线程 void clearSync() { if (existsSync() && FileSystemEntity.isDirectorySync(path)) { List files = listSync(); for (FileSystemEntity file in files) { if (file is File) { file.deleteSync(); Log.i('删除文件: ${file.path}'); } else if (file is Directory) { Directory(file.path).deleteSync(recursive: true); Log.i('删除文件夹: ${file.path}'); } } Log.i('文件夹清空完成'); } else { Log.i('$path 不是一个有效的文件夹'); } } } ================================================ FILE: simple_live_app/lib/app/utils/duration_2_str_utils.dart ================================================ extension DurationStringExtensions on String { /// 将 "HH:MM:SS" 格式的字符串转换为 Duration Duration toDuration() { final parts = split(':'); if (parts.length != 3) { throw FormatException('Invalid duration format: $this'); } final hours = int.tryParse(parts[0]) ?? 0; final minutes = int.tryParse(parts[1]) ?? 0; final seconds = int.tryParse(parts[2]) ?? 0; return Duration(hours: hours, minutes: minutes, seconds: seconds); } } extension DurationExtensions on Duration { /// 将 Duration 转换为紧凑格式的字符串(如 "2h30m15s") String toHMSString() { final hours = inHours; // 计算总小时数 final minutes = inMinutes.remainder(60); // 计算剩余分钟数 final seconds = inSeconds.remainder(60); // 计算剩余秒数 // 格式化分钟和秒为两位数 final minutesStr = minutes.toString().padLeft(2, '0'); final secondsStr = seconds.toString().padLeft(2, '0'); return '$hours:$minutesStr:$secondsStr'; } } ================================================ FILE: simple_live_app/lib/app/utils/dynamic_filter.dart ================================================ import 'package:simple_live_app/app/log.dart'; // 定义筛选条件之间的逻辑关系 enum LogicalOperator { and, or } // 定义支持的筛选比较运算符 enum FilterOperator { equals, // == notEquals, // != greaterThan, // > lessThan, // < greaterThanOrEqual, // >= lessThanOrEqual, // <= contains, // 包含(用于List或String) } /// 表示一个单独的筛选条件。 class Condition { final String field; final FilterOperator operator; final dynamic value; /// (可选) 字段转换器 /// /// 字段转换器在比较前将数据项中的原始值转换为一个`Comparable`类型。 /// 目的对historyModel数据以非标准格式(时长字符串 "HH:MM:SS")存储。 /// /// 该函数接收来自数据项的原始值,通过转换器返回一个`Comparable`对象 /// (例如, `Duration`, `int`, `DateTime`)。 /// 此`Condition`的`value`字段应为相同`Comparable`类型。 final Comparable? Function(dynamic rawValue)? comparableValueProvider; Condition( this.field, this.operator, this.value, { this.comparableValueProvider, }); } /// 接口:标记可以被转换为Map的对象。 /// 数据模型类应该实现这个接口 abstract class Mappable { /// 将对象转换为Map。 Map toMap(); } /// (可选) 高级接口用于让模型类自定义实现筛选逻辑。 /// /// 模型实现了这个接口,`dynamicFilter` 将会调用其 `evaluate` 方法来判断条件, /// 用于实现最高效或最特殊的比较逻辑。 abstract class Filterable { /// 模型自己判断是否满足某个条件。 /// 返回 `true` 表示满足,`false` 表示不满足。 bool evaluate(Condition condition); } /// 一个用于动态多条件过滤列表的工具。 /// /// 根据一组动态条件过滤一个 `Mappable` 对象列表。 /// /// 此函数采用“渐进式增强”的设计: /// - 对于简单的模型(只实现 `Mappable`),它会自动通过 `toMap()` 进行筛选。 /// - 对于复杂的模型(额外实现了 `Filterable`),它会将筛选逻辑的控制权完全交给模型自身的 `evaluate()` 方法。 /// /// [T]: 必须是实现了 `Mappable` 接口的类型。 /// [list]: 要过滤的对象列表。 /// [conditions]: `Condition` 对象列表,定义了过滤规则。 /// [logic]: 条件间的逻辑关系 (`and` 或 `or`)。 /// [takeLast]: (可选) 从结果中获取最后N个元素。 /// /// 返回一个 `List`,其中包含通过所有筛选条件的原始对象。 List dynamicFilter( List list, List conditions, { LogicalOperator logic = LogicalOperator.and, int? takeLast = 15, }) { // 1. 内容筛选 var filteredList = list.where((item) { if (conditions.isEmpty) { return true; } bool check(Condition c) { if (item is Filterable) { return (item as Filterable).evaluate(c); } else { // 使用默认逻辑 late final Map itemMap; try { itemMap = item.toMap(); } catch (e, s) { Log.e( "Filter:Failed to convert item to map for default filtering.", s); return false; } return checkConditionOnMap(itemMap, c); } } if (logic == LogicalOperator.and) { return conditions.every(check); } else { return conditions.any(check); } }).toList(); // 2. 位置筛选 if (takeLast != null && takeLast > 0) { if (takeLast >= filteredList.length) { return filteredList; } return filteredList.sublist(filteredList.length - takeLast); } return filteredList; } /// 默认的、基于Map的条件检查逻辑。 /// 当一个对象没有实现`Filterable`接口时,`dynamicFilter`会调用此函数。 bool checkConditionOnMap(Map itemMap, Condition condition) { if (!itemMap.containsKey(condition.field)) { return true; } final itemValue = itemMap[condition.field]; final operand = condition.value; // 统一处理需要转换的场景 final dynamic transformedItemValue; if (condition.comparableValueProvider != null) { transformedItemValue = condition.comparableValueProvider!(itemValue); } else { transformedItemValue = itemValue; } switch (condition.operator) { case FilterOperator.equals: return transformedItemValue == operand; case FilterOperator.notEquals: return transformedItemValue != operand; case FilterOperator.greaterThan: case FilterOperator.lessThan: case FilterOperator.greaterThanOrEqual: case FilterOperator.lessThanOrEqual: { if (transformedItemValue == null || !_isComparable(transformedItemValue, operand)) { return false; } final comparison = (transformedItemValue as Comparable).compareTo(operand); if (condition.operator == FilterOperator.greaterThan) { return comparison > 0; } if (condition.operator == FilterOperator.lessThan) { return comparison < 0; } if (condition.operator == FilterOperator.greaterThanOrEqual) { return comparison >= 0; } if (condition.operator == FilterOperator.lessThanOrEqual) { return comparison <= 0; } return false; } case FilterOperator.contains: // 'contains' 通常不适用于自定义转换,它在原始值上操作 if (itemValue is List) return itemValue.contains(operand); if (itemValue is String && operand is String) { return itemValue.contains(operand); } return false; } } /// 辅助函数,检查两个值是否是可比较的相同类型。 bool _isComparable(dynamic a, dynamic b) { return a is Comparable && b is Comparable && a.runtimeType == b.runtimeType; } ================================================ FILE: simple_live_app/lib/app/utils/dynamic_sort.dart ================================================ import 'package:simple_live_app/app/utils/dynamic_filter.dart'; /// 排序条件 class SortCondition { final Comparable Function(T item) valueGetter; final bool ascending; SortCondition({ required this.valueGetter, this.ascending = true, }); } extension MappableListExtension on List { /// 动态排序函数 List dynamicSort(List> sortConditions) { sort( (a, b) { for (final cond in sortConditions) { final aValue = cond.valueGetter(a); final bValue = cond.valueGetter(b); final cmp = aValue.compareTo(bValue); if (cmp != 0) return cond.ascending ? cmp : -cmp; } return 0; }, ); return this; } } ================================================ FILE: simple_live_app/lib/app/utils/listen_fourth_button.dart ================================================ import 'package:flutter/gestures.dart'; /// 鼠标侧键点击手势识别器 /// - https://github.com/flutter/flutter/issues/115641 /// - https://github.com/witnet/my-wit-wallet/pull/261 class FourthButtonTapGestureRecognizer extends BaseTapGestureRecognizer { GestureTapDownCallback? onTapDown; @override void handleTapDown({required PointerDownEvent down}) { final TapDownDetails details = TapDownDetails( globalPosition: down.position, localPosition: down.localPosition, kind: getKindForPointer(down.pointer), ); switch (down.buttons) { case 8: if (onTapDown != null) { invokeCallback('onTapDown', () => onTapDown!(details)); } break; default: } } @override void handleTapCancel( {required PointerDownEvent down, PointerCancelEvent? cancel, required String reason}) {} @override void handleTapUp( {required PointerDownEvent down, required PointerUpEvent up}) {} } ================================================ FILE: simple_live_app/lib/app/utils/permission_handler.dart ================================================ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:permission_handler_platform_interface/permission_handler_platform_interface.dart'; export 'package:permission_handler_platform_interface/permission_handler_platform_interface.dart' show Permission, PermissionStatus, PermissionStatusGetters, PermissionWithService, FuturePermissionStatusGetters, ServiceStatus, ServiceStatusGetters, FutureServiceStatusGetters; PermissionHandlerPlatform get _handler => PermissionHandlerPlatform.instance; /// Opens the app settings page. /// /// Returns [true] if the app settings page could be opened, otherwise [false]. Future openAppSettings() => _handler.openAppSettings(); /// Actions that can be executed on a permission. extension PermissionActions on Permission { /// Callback for when permission is denied. static FutureOr? Function()? _onDenied; /// Callback for when permission is granted. static FutureOr? Function()? _onGranted; /// Callback for when permission is permanently denied. static FutureOr? Function()? _onPermanentlyDenied; /// Callback for when permission is restricted. static FutureOr? Function()? _onRestricted; /// Callback for when permission is limited. static FutureOr? Function()? _onLimited; /// Callback for when permission is Provisional. static FutureOr? Function()? _onProvisional; /// Method to set a callback for when permission is denied. Permission onDeniedCallback(FutureOr? Function()? callback) { _onDenied = callback; return this; } /// Method to set a callback for when permission is granted. Permission onGrantedCallback(FutureOr? Function()? callback) { _onGranted = callback; return this; } /// Method to set a callback for when permission is permanently denied. Permission onPermanentlyDeniedCallback(FutureOr? Function()? callback) { _onPermanentlyDenied = callback; return this; } /// Method to set a callback for when permission is restricted. Permission onRestrictedCallback(FutureOr? Function()? callback) { _onRestricted = callback; return this; } /// Method to set a callback for when permission is limited. Permission onLimitedCallback(FutureOr? Function()? callback) { _onLimited = callback; return this; } /// Method to set a callback for when permission is provisional. Permission onProvisionalCallback(FutureOr? Function()? callback) { _onProvisional = callback; return this; } /// Checks the current status of the given [Permission]. /// /// Notes about specific permissions: /// - **[Permission.bluetooth]** /// - iOS 13.0 only: /// - The method will **always** return [PermissionStatus.denied], /// regardless of the actual status. For the actual permission state, /// use [Permission.bluetooth.request]. Note that this will show a /// permission dialog if the permission was not yet requested. Future get status => _handler.checkPermissionStatus(this); /// If you should show a rationale for requesting permission. /// /// This is only implemented on Android, calling this on iOS always returns /// [false]. Future get shouldShowRequestRationale async { if (defaultTargetPlatform != TargetPlatform.android) { return false; } return _handler.shouldShowRequestPermissionRationale(this); } /// Request the user for access to this [Permission], if access hasn't already /// been grant access before. /// /// Returns the new [PermissionStatus]. Future request() async { final permissionStatus = (await [this].request())[this] ?? PermissionStatus.denied; if (permissionStatus.isDenied) { _onDenied?.call(); } else if (permissionStatus.isGranted) { _onGranted?.call(); } else if (permissionStatus.isPermanentlyDenied) { _onPermanentlyDenied?.call(); } else if (permissionStatus.isRestricted) { _onRestricted?.call(); } else if (permissionStatus.isLimited) { _onLimited?.call(); } else if (permissionStatus.isProvisional) { _onProvisional?.call(); } return permissionStatus; } } /// Shortcuts for checking the [status] of a [Permission]. extension PermissionCheckShortcuts on Permission { /// If the user granted this permission. Future get isGranted => status.isGranted; /// If the user denied this permission. Future get isDenied => status.isDenied; /// If the OS denied this permission. The user cannot change the status, /// possibly due to active restrictions such as parental controls being in /// place. /// *Only supported on iOS.* Future get isRestricted => status.isRestricted; /// User has authorized this application for limited access. /// *Only supported on iOS.(iOS14+ for photos, ios18+ for contacts)* Future get isLimited => status.isLimited; /// Returns `true` when permissions are denied permanently. /// /// When permissions are denied permanently, no new permission dialog will /// be showed to the user. Consuming Apps should redirect the user to the /// App settings to change permissions. Future get isPermanentlyDenied => status.isPermanentlyDenied; /// If the application is provisionally authorized to post noninterruptive user notifications. /// *Only supported on iOS.* Future get isProvisional => status.isProvisional; } /// Actions that apply only to permissions that have an associated service. extension ServicePermissionActions on PermissionWithService { /// Checks the current status of the service associated with the given /// [Permission]. /// /// Notes about specific permissions: /// - **[Permission.phone]** /// - Android: /// - The method will return [ServiceStatus.notApplicable] when: /// - the device lacks the TELEPHONY feature /// - TelephonyManager.getPhoneType() returns PHONE_TYPE_NONE /// - when no Intents can be resolved to handle the `tel:` scheme /// - The method will return [ServiceStatus.disabled] when: /// - the SIM card is missing /// - iOS: /// - The method will return [ServiceStatus.notApplicable] when: /// - the native code can not find a handler for the `tel:` scheme /// - The method will return [ServiceStatus.disabled] when: /// - the mobile network code (MNC) is either 0 or 65535. See /// https://stackoverflow.com/a/11595365 for details /// - **PLEASE NOTE that this is still not a perfect indication** of the /// device's capability to place & connect phone calls as it also depends /// on the network condition. /// - **[Permission.bluetooth]** /// - iOS: /// - The method will **always** return [ServiceStatus.disabled] when the /// Bluetooth permission was denied by the user. It is impossible to /// obtain the actual Bluetooth service status without having the /// Bluetooth permission granted. /// - The method will prompt the user for Bluetooth permission if the /// permission was not yet requested. Future get serviceStatus => _handler.checkServiceStatus(this); } /// Actions that can be taken on a [List] of [Permission]s. extension PermissionListActions on List { /// Requests the user for access to these permissions, if they haven't already /// been granted before. /// /// Returns a [Map] containing the status per requested [Permission]. Future> request() => _handler.requestPermissions(this); } ================================================ FILE: simple_live_app/lib/app/utils/sandbox.dart ================================================ import 'dart:io'; /// Returns `true` if the app is running in a sandbox, eg. Flatpak, Snap, Docker, Podman. bool runningInSandbox() { return Platform.environment.containsKey('FLATPAK_ID') || Platform.environment.containsKey('SNAP') || (Platform.environment['container']?.isNotEmpty == true) || FileSystemEntity.isFileSync('/.dockerenv'); } ================================================ FILE: simple_live_app/lib/app/utils/string_normalizer.dart ================================================ extension StringNormalizer on String { static final Map _toneMap = { // a 'ā': 'a', 'á': 'a', 'ǎ': 'a', 'à': 'a', // o 'ō': 'o', 'ó': 'o', 'ǒ': 'o', 'ò': 'o', // e 'ē': 'e', 'é': 'e', 'ě': 'e', 'è': 'e', // i 'ī': 'i', 'í': 'i', 'ǐ': 'i', 'ì': 'i', // u 'ū': 'u', 'ú': 'u', 'ǔ': 'u', 'ù': 'u', // ü / v 'ü': 'v', 'ǖ': 'v', 'ǘ': 'v', 'ǚ': 'v', 'ǜ': 'v', }; /// 去拼音声调 + ü→v + 转小写 String normalize() { final buffer = StringBuffer(); for (final rune in toLowerCase().runes) { final char = String.fromCharCode(rune); buffer.write(_toneMap[char] ?? char); } return buffer.toString(); } } ================================================ FILE: simple_live_app/lib/app/utils/url_parse.dart ================================================ import 'package:dio/dio.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/sites.dart'; class UrlParse { static UrlParse? _urlParse; static UrlParse get instance { _urlParse ??= UrlParse(); return _urlParse!; } /// 链接解析工具 Future parse(String url) async { var id = ""; if (url.contains("bilibili.com")) { var regExp = RegExp(r"bilibili\.com/([\d|\w]+)"); id = regExp.firstMatch(url)?.group(1) ?? ""; return [id, Sites.allSites[Constant.kBiliBili]!]; } if (url.contains("b23.tv")) { var btvReg = RegExp(r"https?:\/\/b23.tv\/[0-9a-z-A-Z]+"); var u = btvReg.firstMatch(url)?.group(0) ?? ""; var location = await _getLocation(u); return await parse(location); } if (url.contains("douyu.com")) { var regExp = RegExp(r"douyu\.com/([\d|\w]+)"); // 适配 topic_url if (url.contains("topic")) { regExp = RegExp(r"[?&]rid=([\d]+)"); } id = regExp.firstMatch(url)?.group(1) ?? ""; return [id, Sites.allSites[Constant.kDouyu]!]; } if (url.contains("huya.com")) { var regExp = RegExp(r"huya\.com/([\d|\w]+)"); id = regExp.firstMatch(url)?.group(1) ?? ""; return [id, Sites.allSites[Constant.kHuya]!]; } if (url.contains("live.douyin.com")) { var regExp = RegExp(r"live\.douyin\.com/([\d|\w]+)"); id = regExp.firstMatch(url)?.group(1) ?? ""; return [id, Sites.allSites[Constant.kDouyin]!]; } if (url.contains("webcast.amemv.com")) { var regExp = RegExp(r"reflow/(\d+)"); id = regExp.firstMatch(url)?.group(1) ?? ""; return [id, Sites.allSites[Constant.kDouyin]!]; } if (url.contains("twitch.tv/")) { final regExp = RegExp(r'twitch\.tv/([^/?]+)'); id = regExp.firstMatch(url)?.group(1) ?? ""; return [id, Sites.allSites[Constant.kTwitch]!]; } if (url.contains("v.douyin.com")) { var regExp = RegExp(r"http.?://v.douyin.com/[\d\w]+/"); var u = regExp.firstMatch(url)?.group(0) ?? ""; var location = await _getLocation(u); return await parse(location); } return []; } Future _getLocation(String url) async { try { if (url.isEmpty) return ""; await Dio().get( url, options: Options( followRedirects: false, ), ); } on DioException catch (e) { if (e.response!.statusCode == 302) { var redirectUrl = e.response!.headers.value("Location"); if (redirectUrl != null) { return redirectUrl; } } } catch (e) { Log.logPrint(e); } return ""; } } ================================================ FILE: simple_live_app/lib/app/utils.dart ================================================ import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:intl/intl.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/utils/permission_handler.dart'; import 'package:simple_live_app/requests/common_request.dart'; import 'package:url_launcher/url_launcher_string.dart'; typedef TextValidate = bool Function(String text); class Utils { static late PackageInfo packageInfo; static DateFormat dateFormat = DateFormat("MM-dd HH:mm"); static DateFormat dateFormatWithYear = DateFormat("yyyy-MM-dd HH:mm"); static DateFormat timeFormat = DateFormat("HH:mm:ss"); /// 处理时间 static String parseTime(DateTime? dt) { if (dt == null) { return ""; } var dtNow = DateTime.now(); if (dt.year == dtNow.year && dt.month == dtNow.month && dt.day == dtNow.day) { return "${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}"; } if (dt.year == dtNow.year) { return dateFormat.format(dt); } return dateFormatWithYear.format(dt); } /// 提示弹窗 /// - `content` 内容 /// - `title` 弹窗标题 /// - `confirm` 确认按钮内容,留空为确定 /// - `cancel` 取消按钮内容,留空为取消 static Future showAlertDialog( String content, { String title = '', String confirm = '', String cancel = '', bool selectable = false, List? actions, }) async { var result = await Get.dialog( AlertDialog( title: Text(title), content: Container( constraints: const BoxConstraints( maxHeight: 400, ), child: SingleChildScrollView( child: Padding( padding: AppStyle.edgeInsetsV12, child: selectable ? SelectableText(content) : Text(content), ), ), ), actions: [ ...?actions, TextButton( onPressed: (() => Get.back(result: false)), child: Text(cancel.isEmpty ? "取消" : cancel), ), TextButton( onPressed: (() => Get.back(result: true)), child: Text(confirm.isEmpty ? "确定" : confirm), ), ], ), ); return result ?? false; } /// 提示弹窗 /// - `content` 内容 /// - `title` 弹窗标题 /// - `confirm` 确认按钮内容,留空为确定 static Future showMessageDialog(String content, {String title = '', String confirm = '', bool selectable = false}) async { var result = await Get.dialog( AlertDialog( title: Text(title), content: Padding( padding: AppStyle.edgeInsetsV12, child: selectable ? SelectableText(content) : Text(content), ), actions: [ TextButton( onPressed: (() => Get.back(result: true)), child: Text(confirm.isEmpty ? "确定" : confirm), ), ], ), ); return result ?? false; } static void showRightDialog({ required String title, Function()? onDismiss, required Widget child, double width = 320, bool useSystem = false, }) { SmartDialog.show( alignment: Alignment.topRight, animationBuilder: (controller, child, animationParam) { //从右到左 return SlideTransition( position: Tween( begin: const Offset(1, 0), end: Offset.zero, ).animate(controller.view), child: child, ); }, useSystem: useSystem, maskColor: Colors.transparent, animationTime: const Duration(milliseconds: 200), builder: (context) => Container( width: width + MediaQuery.of(context).padding.right, padding: EdgeInsets.only(right: MediaQuery.of(context).padding.right), decoration: BoxDecoration( color: Get.theme.cardColor, borderRadius: const BorderRadius.only( topLeft: Radius.circular(4), bottomLeft: Radius.circular(4), ), ), child: SafeArea( left: false, right: false, child: MediaQuery( data: const MediaQueryData(padding: EdgeInsets.zero), child: Column( children: [ ListTile( visualDensity: VisualDensity.compact, contentPadding: EdgeInsets.zero, leading: IconButton( onPressed: () { SmartDialog.dismiss(status: SmartStatus.allCustom).then( (value) => onDismiss?.call(), ); }, icon: const Icon(Icons.arrow_back), ), title: Text( title, style: Get.textTheme.titleMedium, ), ), Divider( height: 1, color: Colors.grey.withAlpha(25), ), Expanded( child: child, ), ], ), ), ), ), ); } static void hideRightDialog() { SmartDialog.dismiss(status: SmartStatus.allCustom); } static Future showBottomSheet({ required String title, required Widget child, double maxWidth = 600, }) async { var result = await showModalBottomSheet( context: Get.context!, constraints: BoxConstraints( maxWidth: maxWidth, ), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(12), topRight: Radius.circular(12), ), ), builder: (_) => Column( children: [ ListTile( contentPadding: const EdgeInsets.only( left: 12, ), title: Text(title), trailing: IconButton( onPressed: Get.back, icon: const Icon(Remix.close_line), ), ), Expanded( child: child, ), ], ), ); return result; } /// 文本编辑的弹窗 /// - `content` 编辑框默认的内容 /// - `title` 弹窗标题 /// - `confirm` 确认按钮内容 /// - `cancel` 取消按钮内容 static Future showEditTextDialog( String content, { String title = '', String? hintText, String confirm = '', String cancel = '', TextValidate? validate, }) async { final TextEditingController textEditingController = TextEditingController(text: content); var result = await Get.dialog( AlertDialog( title: Text(title), content: Padding( padding: AppStyle.edgeInsetsT12, child: TextField( controller: textEditingController, decoration: InputDecoration( border: const OutlineInputBorder(), //prefixText: title, contentPadding: AppStyle.edgeInsetsA12, hintText: hintText ?? title, ), // style: TextStyle( // height: 1.0, // color: Get.isDarkMode ? Colors.white : Colors.black), autofocus: true, ), ), actions: [ TextButton( onPressed: Get.back, child: const Text("取消"), ), TextButton( onPressed: () { if (validate != null && !validate(textEditingController.text)) { return; } Get.back(result: textEditingController.text); }, child: const Text("确定"), ), ], ), // barrierColor: // Get.isDarkMode ? Colors.grey.withOpacity(.3) : Colors.black38, ); return result; } static Future showOptionDialog( List contents, T value, { String title = '', }) async { var result = await Get.dialog( SimpleDialog( title: Text(title), children: [ RadioGroup( groupValue: value, onChanged: (e) { Get.back(result: e); }, child: Column( children: contents .map( (e) => RadioListTile( title: Text(e.toString()), value: e, ), ) .toList(), ), ), ], ), ); return result; } static Future showInformationHelpDialog({ required List content, Widget? title, List? actions, }) async { var result = await Get.dialog( AlertDialog( title: title ?? const Text("帮助"), scrollable: true, content: SingleChildScrollView(child: ListBody(children: content)), actions: actions ?? [ TextButton( onPressed: Get.back, child: const Text("确定"), ), ], ), ); return result; } static Future showStatement() async { var text = await rootBundle.loadString("assets/statement.txt"); var result = await showAlertDialog( text, selectable: true, title: "免责声明", confirm: "已阅读并同意", cancel: "退出", ); if (!result) { exit(0); } } static Future showMapOptionDialog( Map contents, T value, { String title = '', }) async { var result = await Get.dialog( SimpleDialog( title: Text(title), children: [ RadioGroup( groupValue: value, onChanged: (e) { Get.back(result: e); }, child: Column( children: contents.keys .map( (e) => RadioListTile( title: Text((contents[e] ?? '-').tr), value: e, ), ) .toList(), ), ), ], ), ); return result; } static void checkUpdate({bool showMsg = false}) async { try { int currentVer = Utils.parseVersion(packageInfo.version); CommonRequest request = CommonRequest(); var versionInfo = await request.checkUpdate(); if (versionInfo.versionNum > currentVer) { Get.dialog( AlertDialog( title: Text( "发现新版本 ${versionInfo.version}", textAlign: TextAlign.center, style: const TextStyle(fontSize: 18), ), content: Text( versionInfo.versionDesc, style: const TextStyle(fontSize: 14, height: 1.4), ), actionsPadding: AppStyle.edgeInsetsH12, actions: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: TextButton( onPressed: () { Get.back(); }, child: const Text("取消"), ), ), AppStyle.hGap12, Expanded( child: ElevatedButton( style: ElevatedButton.styleFrom( elevation: 0, ), onPressed: () { launchUrlString( versionInfo.downloadUrl, mode: LaunchMode.externalApplication, ); }, child: const Text("更新"), ), ), ], ), ], ), ); } else { if (showMsg) { SmartDialog.showToast("当前已经是最新版本了"); } } } catch (e) { Log.logPrint(e); if (showMsg) { SmartDialog.showToast("检查更新失败"); } } } static int parseVersion(String version) { var sp = version.split('.'); var num = ""; for (var item in sp) { num = num + item.padLeft(2, '0'); } return int.parse(num); } static String onlineToString(int num) { if (num >= 10000) { return "${(num / 10000.0).toStringAsFixed(1)}万"; } return num.toString(); } /// 检查相册权限 static Future checkPhotoPermission() async { try { if (!Platform.isIOS) { return true; } var status = await Permission.photos.status; if (status == PermissionStatus.granted) { return true; } status = await Permission.photos.request(); if (status.isGranted) { return true; } else { SmartDialog.showToast( "请授予相册访问权限", ); return false; } } catch (e) { return false; } } static final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); /// 检查文件权限 static Future checkStorgePermission() async { try { if (!Platform.isAndroid) { return true; } Permission permission = Permission.storage; var androidIndo = await deviceInfo.androidInfo; if (androidIndo.version.sdkInt >= 33) { permission = Permission.manageExternalStorage; } var status = await permission.status; if (status == PermissionStatus.granted) { return true; } status = await permission.request(); if (status.isGranted) { return true; } else { SmartDialog.showToast( "请授予文件访问权限", ); return false; } } catch (e) { return false; } } ///16进制颜色转换 static Color convertHexColor(String hexColor) { hexColor = hexColor.replaceAll("#", ""); if (hexColor.length == 4) { hexColor = "00$hexColor"; } if (hexColor.length == 6) { var R = int.parse(hexColor.substring(0, 2), radix: 16); var G = int.parse(hexColor.substring(2, 4), radix: 16); var B = int.parse(hexColor.substring(4, 6), radix: 16); return Color.fromARGB(255, R, G, B); } if (hexColor.length == 8) { var A = int.parse(hexColor.substring(0, 2), radix: 16); var R = int.parse(hexColor.substring(2, 4), radix: 16); var G = int.parse(hexColor.substring(4, 6), radix: 16); var B = int.parse(hexColor.substring(6, 8), radix: 16); return Color.fromARGB(A, R, G, B); } return Colors.white; } /// 复制内容到剪贴板 static void copyToClipboard(String text) async { try { await Clipboard.setData(ClipboardData(text: text)); SmartDialog.showToast("已复制到剪贴板"); } catch (e) { Log.logPrint(e); SmartDialog.showToast("复制到剪贴板失败: $e"); } } /// 获取剪贴板内容 static Future getClipboard() async { try { var content = await Clipboard.getData(Clipboard.kTextPlain); if (content == null) { SmartDialog.showToast("无法读取剪贴板内容"); return null; } return content.text; } catch (e) { Log.logPrint(e); SmartDialog.showToast("读取剪切板内容失败:$e"); } return null; } static bool isRegexFormat(String keyword) { return keyword.startsWith('/') && keyword.endsWith('/') && keyword.length > 2; } static String removeRegexFormat(String keyword) { return keyword.substring(1, keyword.length - 1); } static String parseFileSize(int size) { if (size < 1024) { return "$size B"; } if (size < 1024 * 1024) { return "${(size / 1024).toStringAsFixed(2)} KB"; } if (size < 1024 * 1024 * 1024) { return "${(size / 1024 / 1024).toStringAsFixed(2)} MB"; } return "${(size / 1024 / 1024 / 1024).toStringAsFixed(2)} GB"; } } ================================================ FILE: simple_live_app/lib/firebase_options.dart ================================================ import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; class DefaultFirebaseOptions { static FirebaseOptions get currentPlatform { if (kIsWeb) { throw UnsupportedError( 'DefaultFirebaseOptions have not been configured for web - ' 'you can reconfigure this by running the FlutterFire CLI again.', ); } switch (defaultTargetPlatform) { case TargetPlatform.android: return android; case TargetPlatform.iOS: throw UnsupportedError( 'DefaultFirebaseOptions have not been configured for ios - ' 'you can reconfigure this by running the FlutterFire CLI again.', ); case TargetPlatform.macOS: throw UnsupportedError( 'DefaultFirebaseOptions have not been configured for macos - ' 'you can reconfigure this by running the FlutterFire CLI again.', ); case TargetPlatform.windows: throw UnsupportedError( 'DefaultFirebaseOptions have not been configured for windows - ' 'you can reconfigure this by running the FlutterFire CLI again.', ); case TargetPlatform.linux: throw UnsupportedError( 'DefaultFirebaseOptions have not been configured for linux - ' 'you can reconfigure this by running the FlutterFire CLI again.', ); default: throw UnsupportedError( 'DefaultFirebaseOptions are not supported for this platform.', ); } } static const FirebaseOptions android = FirebaseOptions( apiKey: '0', appId: '1:000000000000:android:0000000000000000', messagingSenderId: '000000000000', projectId: 'com-slotsun-slive', storageBucket: 'com-slotsun-slive.firebasestorage.app', ); } ================================================ FILE: simple_live_app/lib/icons/live_icons.dart ================================================ // This file is automatically generated. DO NOT EDIT, all your changes would be lost. // https://pub.dartlang.org/packages/iconfont_convert import 'package:flutter/material.dart'; class LiveIcons { static const String _family = 'iconfont'; LiveIcons._(); static const IconData douyu = IconData(0xe613, fontFamily: _family); // douyu static const IconData huya = IconData(0xe614, fontFamily: _family); // 虎牙 } class _PreviewIcon { const _PreviewIcon(this.icon, this.name, this.propName); final IconData icon; final String name; final String propName; } class LiveIconsPreview extends StatelessWidget { const LiveIconsPreview({super.key}); static const iconList = <_PreviewIcon>[ _PreviewIcon(LiveIcons.douyu, "douyu", "douyu"), _PreviewIcon(LiveIcons.huya, "huya", "虎牙"), ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('LiveIcons'), ), body: GridView.count( shrinkWrap: true, crossAxisCount: 4, children: iconList.map((e) { return InkWell( onTap: () { // }, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( margin: const EdgeInsets.only(bottom: 8), child: Icon(e.icon), ), Text(e.name, style: const TextStyle(fontSize: 12), overflow: TextOverflow.ellipsis, maxLines: 1), Text(e.propName, style: const TextStyle(fontSize: 12), overflow: TextOverflow.ellipsis, maxLines: 1), ], ), ); }).toList(), ), ); } } ================================================ FILE: simple_live_app/lib/main.dart ================================================ import 'dart:async'; import 'dart:io'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:logger/logger.dart'; import 'package:media_kit/media_kit.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/event_bus.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/app/utils/listen_fourth_button.dart'; import 'package:simple_live_app/firebase_options.dart'; import 'package:simple_live_app/models/db/follow_user.dart'; import 'package:simple_live_app/models/db/follow_user_tag.dart'; import 'package:simple_live_app/models/db/history.dart'; import 'package:simple_live_app/modules/other/debug_log_page.dart'; import 'package:simple_live_app/modules/settings/appstyle_settings/appstyle_setting_contorller.dart'; import 'package:simple_live_app/routes/app_analytics_observer.dart'; import 'package:simple_live_app/routes/app_pages.dart'; import 'package:simple_live_app/routes/route_path.dart'; import 'package:simple_live_app/services/bilibili_account_service.dart'; import 'package:simple_live_app/services/db_service.dart'; import 'package:simple_live_app/services/douyin_account_service.dart'; import 'package:simple_live_app/services/firebase_service.dart'; import 'package:simple_live_app/services/follow_service.dart'; import 'package:simple_live_app/services/history_service.dart'; import 'package:simple_live_app/services/local_storage_service.dart'; import 'package:simple_live_app/services/migration_service.dart'; import 'package:simple_live_app/services/sync_service.dart'; import 'package:simple_live_app/services/window_service.dart'; import 'package:simple_live_app/src/rust/frb_generated.dart'; import 'package:simple_live_app/widgets/status/app_loadding_widget.dart'; import 'package:simple_live_core/simple_live_core.dart'; import 'package:window_manager/window_manager.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // init-queue: // window(first)->migration->media_kit->Hive->services->start // window(second)->open await RustLib.init(); await MigrationService.migrateData(); MediaKit.ensureInitialized(); await Hive.initFlutter( (!Platform.isAndroid && !Platform.isIOS) ? (await getApplicationSupportDirectory()).path : null, ); //初始化服务 await initServices(); await initWindow(); await MigrationService.migrateDataByVersion(); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); //设置状态栏为透明 SystemUiOverlayStyle systemUiOverlayStyle = const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, systemNavigationBarColor: Colors.transparent, ); SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); runApp(const MyApp()); } Future initWindow() async { if (!(Platform.isMacOS || Platform.isWindows || Platform.isLinux)) { return; } await windowManager.ensureInitialized(); WindowService.instance.init(); } Future initServices() async { Hive.registerAdapter(FollowUserAdapter()); Hive.registerAdapter(HistoryAdapter()); Hive.registerAdapter(FollowUserTagAdapter()); //包信息 Utils.packageInfo = await PackageInfo.fromPlatform(); //本地存储 Log.d("Init LocalStorage Service"); await Get.put(LocalStorageService()).init(); await Get.put(DBService()).init(); //初始化设置控制器 Get.put(AppSettingsController()); await Get.put(AppStyleSettingController()).init(); Get.put(BiliBiliAccountService()); Get.put(DouyinAccountService()); Get.put(SyncService()); Get.put(FollowService()); Get.put(HistoryService()); // 移动平台不使用 windowManager if(!Platform.isAndroid && !Platform.isIOS){ Get.put(WindowService()); } // only android use firebase if(Platform.isAndroid){ await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); Get.put(FirebaseService()); } initCoreLog(); } void initCoreLog() { //日志信息 CoreLog.enableLog = !kReleaseMode || AppSettingsController.instance.logEnable.value; CoreLog.requestLogType = RequestLogType.short; CoreLog.onPrintLog = (level, msg) { switch (level) { case Level.debug: Log.d(msg); break; case Level.error: Log.e(msg, StackTrace.current); break; case Level.info: Log.i(msg); break; case Level.warning: Log.w(msg); break; default: Log.logPrint(msg); } }; } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { bool isDynamicColor = AppStyleSettingController.instance.isDynamic.value; Color styleColor = Color(AppStyleSettingController.instance.styleColor.value); return DynamicColorBuilder( builder: ((ColorScheme? lightDynamic, ColorScheme? darkDynamic) { ColorScheme? lightColorScheme; ColorScheme? darkColorScheme; if (lightDynamic != null && darkDynamic != null && isDynamicColor) { lightColorScheme = lightDynamic; darkColorScheme = darkDynamic; } else { lightColorScheme = ColorScheme.fromSeed( seedColor: styleColor, brightness: Brightness.light, ); darkColorScheme = ColorScheme.fromSeed( seedColor: styleColor, brightness: Brightness.dark); } return Obx( () => GetMaterialApp( title: "Slive", theme: AppStyle.light( fontFamily: AppStyleSettingController.instance.curFontName.value, ).copyWith(colorScheme: lightColorScheme), darkTheme: AppStyle.darkTheme( fontFamily: AppStyleSettingController.instance.curFontName.value, ).copyWith(colorScheme: darkColorScheme), themeMode: ThemeMode .values[Get.find().themeMode.value], initialRoute: RoutePath.kIndex, getPages: AppPages.routes, //国际化 locale: const Locale("zh", "CN"), localizationsDelegates: const [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [Locale("zh", "CN")], logWriterCallback: (text, {bool? isError}) { Log.addDebugLog(text, (isError ?? false) ? Colors.red : Colors.grey); Log.writeLog(text, (isError ?? false) ? Level.error : Level.info); }, //debugShowCheckedModeBanner: false, navigatorObservers: [ FlutterSmartDialog.observer, if (Platform.isAndroid) AppAnalyticsObserver.observer ], builder: FlutterSmartDialog.init( loadingBuilder: ((msg) => const AppLoaddingWidget()), //字体大小不跟随系统变化 builder: (context, child) => MediaQuery( data: MediaQuery.of(context) .copyWith(textScaler: const TextScaler.linear(1.0)), child: Stack( children: [ //侧键返回 RawGestureDetector( excludeFromSemantics: true, gestures: { FourthButtonTapGestureRecognizer: GestureRecognizerFactoryWithHandlers< FourthButtonTapGestureRecognizer>( () => FourthButtonTapGestureRecognizer(), (FourthButtonTapGestureRecognizer instance) { instance.onTapDown = (TapDownDetails details) async { //如果处于全屏状态,退出全屏 if (!Platform.isAndroid && !Platform.isIOS) { if (await windowManager.isFullScreen()) { await windowManager.setFullScreen(false); return; } } Get.back(); }; }, ), }, child: KeyboardListener( focusNode: FocusNode(), onKeyEvent: (KeyEvent event) async { if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.escape) { // ESC退出全屏 // 如果处于全屏状态,退出全屏 if (!Platform.isAndroid && !Platform.isIOS) { if (await windowManager.isFullScreen()) { await windowManager.setFullScreen(false); EventBus.instance.emit(EventBus.kEscapePressed, 0); return; } } } }, child: child!, ), ), //查看DEBUG日志按钮 //只在Debug、Profile模式显示 Visibility( visible: !kReleaseMode, child: Positioned( right: 12, bottom: 100 + context.mediaQueryViewPadding.bottom, child: Opacity( opacity: 0.4, child: ElevatedButton( child: const Text("DEBUG LOG"), onPressed: () { Get.bottomSheet( const DebugLogPage(), ); }, ), ), ), ), ], ), ), ), ), ); })); } } ================================================ FILE: simple_live_app/lib/models/account/bilibili_user_info_page.dart ================================================ import 'dart:convert'; T? asT(dynamic value) { if (value is T) { return value; } return null; } class BiliBiliUserInfoModel { BiliBiliUserInfoModel({ this.mid, this.uname, this.userid, this.sign, this.birthday, this.sex, this.nickFree, this.rank, }); factory BiliBiliUserInfoModel.fromJson(Map json) => BiliBiliUserInfoModel( mid: asT(json['mid']), uname: asT(json['uname']), userid: asT(json['userid']), sign: asT(json['sign']), birthday: asT(json['birthday']), sex: asT(json['sex']), nickFree: asT(json['nick_free']), rank: asT(json['rank']), ); int? mid; String? uname; String? userid; String? sign; String? birthday; String? sex; bool? nickFree; String? rank; @override String toString() { return jsonEncode(this); } Map toJson() => { 'mid': mid, 'uname': uname, 'userid': userid, 'sign': sign, 'birthday': birthday, 'sex': sex, 'nick_free': nickFree, 'rank': rank, }; } ================================================ FILE: simple_live_app/lib/models/account/douyin_user_info.dart ================================================ import 'dart:convert'; T? asT(dynamic value) { if (value is T) { return value; } return null; } class DouyinUserInfoModel { DouyinUserInfoModel({ this.id, this.nickname, this.shortId, this.sign, this.birthday, this.gender, }); factory DouyinUserInfoModel.fromJson(Map json) => DouyinUserInfoModel( id: asT(json['id_str']), nickname: asT(json['nickname']), shortId: asT(json['short_id']), sign: asT(json['sign']), birthday: asT(json['birthday']), gender: asT(json['gender']), ); String? id; String? nickname; String? shortId; String? sign; String? birthday; String? gender; @override String toString() { return jsonEncode(this); } Map toJson() => { 'id': id, 'nickname': nickname, 'short_id': shortId, 'sign': sign, 'birthday': birthday, 'gender': gender, }; } ================================================ FILE: simple_live_app/lib/models/db/follow_user.dart ================================================ import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:simple_live_app/app/utils/dynamic_filter.dart'; part 'follow_user.g.dart'; @HiveType(typeId: 1) class FollowUser implements Mappable { FollowUser( {required this.id, required this.roomId, required this.siteId, required this.userName, required this.face, required this.addTime, this.watchDuration = "00:00:00", this.tag = "全部", this.remark = "", this.romanName = ""}); ///id=siteId_roomId @HiveField(0) String id; @HiveField(1) String roomId; @HiveField(2) String siteId; @HiveField(3) String userName; @HiveField(4) String face; @HiveField(5) DateTime addTime; @HiveField(6) String? watchDuration; // "00:00:00" @HiveField(7) String tag; @HiveField(8) String? remark; @HiveField(9) String? romanName; /// 直播状态 /// 0=未知(加载中) 1=未开播 2=直播中 Rx liveStatus = 0.obs; /// 直播封面 Rx cover = "".obs; /// 直播标题 Rx title = "".obs; Rx online = 0.obs; factory FollowUser.fromJson(Map json) => FollowUser( id: json['id'], roomId: json['roomId'], siteId: json['siteId'], userName: json['userName'], face: json['face'], addTime: DateTime.parse(json['addTime']), watchDuration: json["watchDuration"] ?? "00:00:00", tag: json["tag"] ?? "全部", remark: json["remark"] ?? "", romanName: json["romanName"] ?? ""); Map toJson() => { 'id': id, 'roomId': roomId, 'siteId': siteId, 'userName': userName, 'face': face, 'addTime': addTime.toString(), "watchDuration": watchDuration ?? "00:00:00", "tag": tag, "remark": remark, "romanName": romanName }; @override Map toMap() => toJson(); } ================================================ FILE: simple_live_app/lib/models/db/follow_user.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND part of 'follow_user.dart'; // ************************************************************************** // TypeAdapterGenerator // ************************************************************************** class FollowUserAdapter extends TypeAdapter { @override final int typeId = 1; @override FollowUser read(BinaryReader reader) { final numOfFields = reader.readByte(); final fields = { for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), }; return FollowUser( id: fields[0] as String, roomId: fields[1] as String, siteId: fields[2] as String, userName: fields[3] as String, face: fields[4] as String, addTime: fields[5] as DateTime, watchDuration: fields[6] as String?, tag: fields[7] ?? "", remark: fields[8] as String?, romanName: fields[9] as String? ); } @override void write(BinaryWriter writer, FollowUser obj) { writer ..writeByte(9) ..writeByte(0) ..write(obj.id) ..writeByte(1) ..write(obj.roomId) ..writeByte(2) ..write(obj.siteId) ..writeByte(3) ..write(obj.userName) ..writeByte(4) ..write(obj.face) ..writeByte(5) ..write(obj.addTime) ..writeByte(6) ..write(obj.watchDuration) ..writeByte(7) ..write(obj.tag) ..writeByte(8) ..write(obj.remark) ..writeByte(9) ..write(obj.romanName); } @override int get hashCode => typeId.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is FollowUserAdapter && runtimeType == other.runtimeType && typeId == other.typeId; } ================================================ FILE: simple_live_app/lib/models/db/follow_user_tag.dart ================================================ import 'package:hive/hive.dart'; part 'follow_user_tag.g.dart'; @HiveType(typeId: 3) class FollowUserTag { @HiveField(1) String id; // 用户自定义tag @HiveField(2) String tag; // followUserId @HiveField(3) List userId; FollowUserTag({ required this.id, required this.tag, required this.userId, }); factory FollowUserTag.fromJson(Map json) { return FollowUserTag( id: json['id'], tag: json['tag'], userId: List.from(json['userId']), ); } Map toJson() { return { 'id': id, 'tag': tag, 'userId': userId, }; } FollowUserTag copyWith({ String? id, String? tag, List? userId, }) { return FollowUserTag( id: id ?? this.id, tag: tag ?? this.tag, userId: userId ?? this.userId, ); } } ================================================ FILE: simple_live_app/lib/models/db/follow_user_tag.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND part of 'follow_user_tag.dart'; // ************************************************************************** // TypeAdapterGenerator // ************************************************************************** class FollowUserTagAdapter extends TypeAdapter { @override final int typeId = 3; @override FollowUserTag read(BinaryReader reader) { final numOfFields = reader.readByte(); final fields = { for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), }; return FollowUserTag( id: fields[1] as String, tag: fields[2] as String, userId: (fields[3] as List).cast(), ); } @override void write(BinaryWriter writer, FollowUserTag obj) { writer ..writeByte(3) ..writeByte(1) ..write(obj.id) ..writeByte(2) ..write(obj.tag) ..writeByte(3) ..write(obj.userId); } @override int get hashCode => typeId.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is FollowUserTagAdapter && runtimeType == other.runtimeType && typeId == other.typeId; } ================================================ FILE: simple_live_app/lib/models/db/history.dart ================================================ import 'package:hive/hive.dart'; import 'package:simple_live_app/app/utils/duration_2_str_utils.dart'; import 'package:simple_live_app/app/utils/dynamic_filter.dart'; part 'history.g.dart'; @HiveType(typeId: 2) class History implements Mappable { History({ required this.id, required this.roomId, required this.siteId, required this.userName, required this.face, required this.updateTime, this.watchDuration = "00:00:00", }); ///id=siteId_roomId @HiveField(0) String id; @HiveField(1) String roomId; @HiveField(2) String siteId; @HiveField(3) String userName; @HiveField(4) String face; @HiveField(5) DateTime updateTime; @HiveField(6) String? watchDuration; // "00:00:00" Duration get duration => watchDuration!.toDuration(); //for filter factory History.fromJson(Map json) => History( id: json["id"], roomId: json["roomId"], siteId: json["siteId"], userName: json["userName"], face: json["face"], updateTime: DateTime.parse(json["updateTime"]), watchDuration: json["watchDuration"] ?? "00:00:00", ); Map toJson() => { "id": id, "roomId": roomId, "siteId": siteId, "userName": userName, "face": face, "updateTime": updateTime.toString(), "watchDuration": watchDuration ?? "00:00:00", }; @override Map toMap() => toJson(); } ================================================ FILE: simple_live_app/lib/models/db/history.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND part of 'history.dart'; // ************************************************************************** // TypeAdapterGenerator // ************************************************************************** class HistoryAdapter extends TypeAdapter { @override final int typeId = 2; @override History read(BinaryReader reader) { final numOfFields = reader.readByte(); final fields = { for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), }; return History( id: fields[0] as String, roomId: fields[1] as String, siteId: fields[2] as String, userName: fields[3] as String, face: fields[4] as String, updateTime: fields[5] as DateTime, watchDuration: fields[6] as String?, ); } @override void write(BinaryWriter writer, History obj) { writer ..writeByte(7) ..writeByte(0) ..write(obj.id) ..writeByte(1) ..write(obj.roomId) ..writeByte(2) ..write(obj.siteId) ..writeByte(3) ..write(obj.userName) ..writeByte(4) ..write(obj.face) ..writeByte(5) ..write(obj.updateTime) ..writeByte(6) ..write(obj.watchDuration); } @override int get hashCode => typeId.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is HistoryAdapter && runtimeType == other.runtimeType && typeId == other.typeId; } ================================================ FILE: simple_live_app/lib/models/font_model.dart ================================================ class FontModel { final String id; final String name; final List files; final String desc; final String official; final FontLicense? license; bool isDownloaded; FontModel({ required this.id, required this.name, required this.files, required this.desc, required this.official, this.license, this.isDownloaded = false, }); factory FontModel.fromJson(Map json) { return FontModel( id: json['id'] as String? ?? '', name: json['name'] as String? ?? '', files: (json['files'] as List?)?.map((e) => e as String).toList() ?? [], desc: json['desc'] as String? ?? '', official: json['official'] as String? ?? '', license: json['license'] != null ? FontLicense.fromJson(json['license'] as Map) : null, isDownloaded: json['isDownloaded'] as bool? ?? false, ); } Map toJson() { return { 'id': id, 'name': name, 'files': files, 'desc': desc, 'official': official, 'license': license?.toJson(), 'isDownloaded': isDownloaded, }; } @override bool operator ==(Object other) { if (identical(this, other)) return true; return other is FontModel && other.id == id; } @override int get hashCode => id.hashCode; } class FontLicense { final String name; final String url; const FontLicense({ required this.name, required this.url, }); factory FontLicense.fromJson(Map json) { return FontLicense( name: json['name'] as String? ?? '', url: json['url'] as String? ?? '', ); } Map toJson() { return { 'name': name, 'url': url, }; } } ================================================ FILE: simple_live_app/lib/models/fonts_model.dart ================================================ T? asT(dynamic value) { if (value is T) { return value; } return null; } class FontsModel{ String version; int versionNum; String versionDesc; String downloadUrl; FontsModel({ required this.version, required this.versionNum, required this.versionDesc, required this.downloadUrl, }); } ================================================ FILE: simple_live_app/lib/models/sync_client_info_model.dart ================================================ import 'dart:convert'; T? asT(dynamic value) { if (value is T) { return value; } return null; } class SyncClientInfoModel { SyncClientInfoModel({ required this.name, required this.version, required this.address, required this.port, required this.type, }); factory SyncClientInfoModel.fromJson(Map json) => SyncClientInfoModel( type: asT(json['type'])!, name: asT(json['name'])!, version: asT(json['version'])!, address: asT(json['address'])!, port: asT(json['port'])!, ); String type; String name; String version; String address; int port; @override String toString() { return jsonEncode(this); } Map toJson() => { 'name': name, 'version': version, 'address': address, 'port': port, 'type': type, }; } ================================================ FILE: simple_live_app/lib/models/version_model.dart ================================================ import 'dart:convert'; T? asT(dynamic value) { if (value is T) { return value; } return null; } class VersionModel { VersionModel({ required this.version, required this.versionNum, required this.versionDesc, required this.downloadUrl, }); factory VersionModel.fromJson(Map json) => VersionModel( version: asT(json['version'])!, versionNum: asT(json['version_num'])!, versionDesc: asT(json['version_desc'])!, downloadUrl: asT(json['download_url'])!, ); String version; int versionNum; String versionDesc; String downloadUrl; @override String toString() { return jsonEncode(this); } Map toJson() => { 'version': version, 'version_num': versionNum, 'version_desc': versionDesc, 'download_url': downloadUrl, }; } ================================================ FILE: simple_live_app/lib/modules/category/category_controller.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/event_bus.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/modules/category/category_list_controller.dart'; class CategoryController extends GetxController with GetSingleTickerProviderStateMixin { late TabController tabController; CategoryController() { tabController = TabController(length: Sites.supportSites.length, vsync: this); } StreamSubscription? streamSubscription; @override void onInit() { streamSubscription = EventBus.instance.listen( EventBus.kBottomNavigationBarClicked, (index) { if (index == 2) { refreshOrScrollTop(); } }, ); for (var site in Sites.supportSites) { Get.put(CategoryListController(site), tag: site.id); } super.onInit(); } void refreshOrScrollTop() { var tabIndex = tabController.index; BasePageController controller; controller = Get.find(tag: Sites.supportSites[tabIndex].id); controller.scrollToTopOrRefresh(); } @override void onClose() { streamSubscription?.cancel(); super.onClose(); } } ================================================ FILE: simple_live_app/lib/modules/category/category_list_controller.dart ================================================ import 'package:get/get.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_core/simple_live_core.dart'; class CategoryListController extends BasePageController { final Site site; CategoryListController(this.site); @override Future> getData(int page, int pageSize) async { var result = await site.liveSite.getCategores(); return result.map((e) => AppLiveCategory.fromLiveCategory(e)).toList(); } } class AppLiveCategory extends LiveCategory { var showAll = false.obs; AppLiveCategory({ required super.id, required super.name, required super.children, }) { showAll.value = children.length < 19; } List get take15 => children.take(15).toList(); factory AppLiveCategory.fromLiveCategory(LiveCategory item) { return AppLiveCategory( children: item.children, id: item.id, name: item.name, ); } } ================================================ FILE: simple_live_app/lib/modules/category/category_list_view.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_easyrefresh/easy_refresh.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/modules/category/category_list_controller.dart'; import 'package:simple_live_app/routes/app_navigation.dart'; import 'package:simple_live_app/widgets/keep_alive_wrapper.dart'; import 'package:simple_live_app/widgets/net_image.dart'; import 'package:simple_live_app/widgets/shadow_card.dart'; import 'package:simple_live_core/simple_live_core.dart'; import 'package:sticky_headers/sticky_headers.dart'; class CategoryListView extends StatelessWidget { final String tag; const CategoryListView(this.tag, {super.key}); CategoryListController get controller => Get.find(tag: tag); @override Widget build(BuildContext context) { return KeepAliveWrapper( child: Obx( () => EasyRefresh( firstRefresh: true, controller: controller.easyRefreshController, onRefresh: controller.refreshData, header: MaterialHeader( completeDuration: const Duration(milliseconds: 400), ), child: ListView.builder( padding: AppStyle.edgeInsetsA12, itemCount: controller.list.length, controller: controller.scrollController, itemBuilder: (_, i) { var item = controller.list[i]; return Column( children: [ StickyHeader( header: Container( padding: AppStyle.edgeInsetsV8.copyWith(left: 4), color: Theme.of(context).scaffoldBackgroundColor, alignment: Alignment.centerLeft, child: Text( item.name, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold), ), ), content: Obx( () => GridView.count( shrinkWrap: true, padding: AppStyle.edgeInsetsV8, physics: const NeverScrollableScrollPhysics(), crossAxisCount: MediaQuery.of(context).size.width ~/ 80, crossAxisSpacing: 8, mainAxisSpacing: 8, children: item.showAll.value ? (item.children .map( (e) => buildSubCategory(e), ) .toList()) : (item.take15 .map( (e) => buildSubCategory(e), ) .toList() ..add(buildShowMore(item))), ), ), ), ], ); }, ), ), ), ); } Widget buildSubCategory(LiveSubCategory item) { return ShadowCard( onTap: () { AppNavigator.toCategoryDetail(site: controller.site, category: item); }, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ NetImage( item.pic ?? "", width: 40, height: 40, borderRadius: 8, ), AppStyle.vGap4, Text( item.name, maxLines: 1, textAlign: TextAlign.center, style: const TextStyle(fontSize: 12), ), ], ), ); } Widget buildShowMore(AppLiveCategory item) { return ShadowCard( onTap: () { item.showAll.value = true; }, child: const Center( child: Text( "显示全部", maxLines: 1, textAlign: TextAlign.center, style: TextStyle(fontSize: 12), ), ), ); } } ================================================ FILE: simple_live_app/lib/modules/category/category_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/modules/category/category_controller.dart'; import 'package:simple_live_app/modules/category/category_list_view.dart'; class CategoryPage extends GetView { const CategoryPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( titleSpacing: 8, title: TabBar( controller: controller.tabController, padding: EdgeInsets.zero, tabAlignment: TabAlignment.center, tabs: Sites.supportSites .map( (e) => Tab( //text: e.name, child: Row( children: [ Image.asset( e.logo, width: 24, ), AppStyle.hGap8, Text(e.name), ], ), ), ) .toList(), labelPadding: AppStyle.edgeInsetsH20, isScrollable: true, indicatorSize: TabBarIndicatorSize.label, ), ), body: TabBarView( controller: controller.tabController, children: Sites.supportSites .map( (e) => CategoryListView( e.id, ), ) .toList(), ), ); } } ================================================ FILE: simple_live_app/lib/modules/category/detail/category_detail_controller.dart ================================================ import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_core/simple_live_core.dart'; class CategoryDetailController extends BasePageController { final Site site; final LiveSubCategory subCategory; CategoryDetailController({ required this.site, required this.subCategory, }); @override Future> getData(int page, int pageSize) async { var result = await site.liveSite.getCategoryRooms(subCategory, page: page); return result.items; } } ================================================ FILE: simple_live_app/lib/modules/category/detail/category_detail_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/modules/category/detail/category_detail_controller.dart'; import 'package:simple_live_app/widgets/keep_alive_wrapper.dart'; import 'package:simple_live_app/widgets/live_room_card.dart'; import 'package:simple_live_app/widgets/page_grid_view.dart'; class CategoryDetailPage extends GetView { const CategoryDetailPage({super.key}); @override Widget build(BuildContext context) { var c = MediaQuery.of(context).size.width ~/ 200; if (c < 2) { c = 2; } return Scaffold( appBar: AppBar( title: Text(controller.subCategory.name), ), body: KeepAliveWrapper( child: PageGridView( pageController: controller, padding: AppStyle.edgeInsetsA12, firstRefresh: true, mainAxisSpacing: 12, crossAxisSpacing: 12, crossAxisCount: c, itemBuilder: (_, i) { var item = controller.list[i]; return LiveRoomCard(controller.site, item); }, ), ), ); } } ================================================ FILE: simple_live_app/lib/modules/follow_user/follow_app_setting/follow_app_settings_controller.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:fractional_indexing_dart/fractional_indexing_dart.dart'; import 'package:get/get.dart' hide Condition; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/event_bus.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/app/utils/duration_2_str_utils.dart'; import 'package:simple_live_app/app/utils/dynamic_filter.dart'; import 'package:simple_live_app/models/db/follow_user.dart'; import 'package:simple_live_app/models/db/follow_user_tag.dart'; import 'package:simple_live_app/services/db_service.dart'; import 'package:simple_live_app/services/follow_service.dart'; class FollowAppSettingsController extends BaseController { final appC = Get.find(); // 用户自定义标签 RxList userTagList = [].obs; // 用户自定义条件 Rx takeLast = 15.obs; Rx minutes = 30.obs; @override void onInit() { updateTagList(); super.onInit(); } // 标签管理 void updateTagList() { userTagList.assignAll(FollowService.instance.followTagList); } Future removeTag(FollowUserTag tag) async { await FollowService.instance.removeFollowUserTag(tag); updateTagList(); Log.i('删除tag${tag.tag}'); } void addTag(String tag) async { await FollowService.instance.addFollowUserTag(tag); updateTagList(); } Future updateTag(FollowUserTag followUserTag) async { await FollowService.instance.updateFollowUserTag(followUserTag); } void updateTagName(FollowUserTag followUserTag, String newTagName) { // 未操作 if (followUserTag.tag == newTagName) { return; } // 避免重名 if (userTagList.any((item) => item.tag == newTagName)) { SmartDialog.showToast("标签名重复,修改失败"); return; } FollowService.instance.updateTagName(followUserTag, newTagName); SmartDialog.showToast("标签名修改成功"); updateTagList(); } Future updateTagOrder(int oldIndex, int newIndex) async { if (newIndex > oldIndex) newIndex -= 1; // 处理索引调整 final item = userTagList.removeAt(oldIndex); String newTagKey = FractionalIndexing.generateKeyBetween( newIndex > 0 ? userTagList[newIndex - 1].id : null, newIndex < userTagList.length ? userTagList[newIndex].id : null); final newTag = FollowUserTag(id: newTagKey, tag: item.tag, userId: item.userId); userTagList.insert(newIndex, newTag); await FollowService.instance.updateFollowTagOrder(item, newTag); } Future followDataCheck() async { await FollowService.instance.followUserAllDataCheck(); SmartDialog.showToast("数据校准完成"); } // 标签管理弹窗 void showTagsManager() { Utils.showBottomSheet( title: '标签管理', child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ AppStyle.divider, ListTile( title: const Text("添加标签"), leading: const Icon(Icons.add), onTap: () { editTagDialog("添加标签"); }, ), AppStyle.divider, // 列表内容 Expanded( child: Obx( () => ReorderableListView.builder( buildDefaultDragHandles: false, itemCount: userTagList.length, itemBuilder: (context, index) { // 偏移 FollowUserTag item = userTagList[index]; return ListTile( key: ValueKey(item.id), title: GestureDetector( child: Text(item.tag), onLongPress: () { { editTagDialog("修改标签", followUserTag: item); } }, ), leading: IconButton( icon: const Icon(Icons.delete), onPressed: () { removeTag(item); }, ), trailing: ReorderableDelayedDragStartListener( index: index, child: const Padding( padding: EdgeInsets.symmetric(horizontal: 12.0), child: Icon(Icons.drag_handle), ), ), ); }, onReorder: (int oldIndex, int newIndex) { updateTagOrder(oldIndex, newIndex); }, ), ), ), ]), ); } void editTagDialog(String title, {FollowUserTag? followUserTag}) { final TextEditingController tagEditController = TextEditingController(text: followUserTag?.tag); bool upMode = title == "添加标签" ? true : false; Get.dialog( AlertDialog( contentPadding: const EdgeInsets.fromLTRB(12, 12, 12, 4), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), ), content: SingleChildScrollView( padding: EdgeInsets.only( bottom: MediaQuery.of(Get.context!).viewInsets.bottom, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( title, style: const TextStyle( fontSize: 18, ), ), TextField( controller: tagEditController, minLines: 1, maxLines: 1, decoration: InputDecoration( border: const OutlineInputBorder(), contentPadding: AppStyle.edgeInsetsA12, enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.grey.withValues( alpha: .2, ), ), ), ), onSubmitted: (tag) { upMode ? addTag(tagEditController.text) : updateTagName(followUserTag!, tagEditController.text); Get.back(); }, ), SizedBox( width: double.infinity, child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () { Get.back(); }, child: const Text('否'), ), TextButton( onPressed: () { upMode ? addTag(tagEditController.text) : updateTagName( followUserTag!, tagEditController.text, ); Get.back(); }, child: const Text('是'), ), ], ), ) ], ), ), ), ); } // 关注清理功能 Future cleanFollow(List cleanPool) async { if (cleanPool.isEmpty) { SmartDialog.showToast("没有需要清理的用户"); return; } SmartDialog.showLoading(msg: "清理中"); for (var follow in cleanPool) { // 取消关注同时删除标签内的 userId if (follow.tag != "全部") { var tag = userTagList.firstWhere((tag) => tag.tag == follow.tag); tag.userId.remove(follow.id); await updateTag(tag); } await FollowService.instance.removeFollowUser(follow.id); } SmartDialog.dismiss(); EventBus.instance.emit(Constant.kUpdateFollow, 0); SmartDialog.showToast("清理完成"); } List buildAutoCleanPool() { var followList = FollowService.instance.followList; var histories = DBService.instance.getHistories(); if (histories.isEmpty || followList.isEmpty) return []; // 筛选出历史记录里已关注的 final followedIds = followList.map((follow) => follow.id).toSet(); // set性能略优 final followedHistories = histories.where((history) => followedIds.contains(history.id)).toList(); if (followedHistories.isEmpty) return []; List conditions = [ // Condition('siteId', FilterOperator.equals, Constant.kBiliBili), Condition( 'watchDuration', FilterOperator.lessThan, Duration(minutes: minutes.value), comparableValueProvider: (watchDuration) { if (watchDuration is String) { return watchDuration.toDuration(); } return null; }, ), ]; // 根据动态条件筛选出需要清理的 关注id final df = dynamicFilter(followedHistories, conditions, takeLast: takeLast.value); final uidsToClean = df.map((history) => history.id).toSet(); final autoCleanPool = followList.where((follow) => uidsToClean.contains(follow.id)).toList(); return autoCleanPool; } } ================================================ FILE: simple_live_app/lib/modules/follow_user/follow_app_setting/follow_app_settings_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/models/db/follow_user.dart'; import 'package:simple_live_app/modules/follow_user/follow_app_setting/follow_app_settings_controller.dart'; import 'package:simple_live_app/services/follow_service.dart'; import 'package:simple_live_app/widgets/settings/settings_action.dart'; import 'package:simple_live_app/widgets/settings/settings_card.dart'; import 'package:simple_live_app/widgets/settings/settings_menu_check.dart'; import 'package:simple_live_app/widgets/settings/settings_number.dart'; import 'package:simple_live_app/widgets/settings/settings_switch.dart'; class FollowSettingsPage extends GetView { const FollowSettingsPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("关注设置"), ), body: ListView( padding: AppStyle.edgeInsetsA12, children: [ Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 0), child: Text( "标签管理", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( mainAxisSize: MainAxisSize.min, children: [ SettingsAction( title: "标签管理", onTap: controller.showTagsManager, ), ], ), ), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 24), child: Text( "关注清理功能", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( mainAxisSize: MainAxisSize.min, children: [ // todo:筛选条件设置 // SettingsAction( // title: "筛选条件设置", // ), SettingsMenuCheck( title: '选择要清理的用户', subtitle: '默认条件为:观看时常低于30分钟,历史观看底部15', confirmText: '清理', itemToString: (user) => user.userName, // 告诉组件如何显示用户名 // 这里传入您自己的筛选函数 itemsProvider: () async { return controller.buildAutoCleanPool(); }, // 用户点击“清理”按钮后的回调 onConfirm: (List selectedUsers) { controller.cleanFollow(selectedUsers); }, ) ], ), ), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 24), child: Text( "自动更新设置", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( children: [ Obx( () => SettingsSwitch( value: controller.appC.autoUpdateFollowEnable.value, title: "自动更新关注直播状态", onChanged: (e) { controller.appC.setAutoUpdateFollowEnable(e); FollowService.instance.initTimer(); }, ), ), Obx( () => Visibility( visible: controller.appC.autoUpdateFollowEnable.value, child: AppStyle.divider, ), ), Obx( () => Visibility( visible: controller.appC.autoUpdateFollowEnable.value, child: SettingsAction( title: "自动更新间隔", value: "${controller.appC.autoUpdateFollowDuration.value ~/ 60}小时${controller.appC.autoUpdateFollowDuration.value % 60}分钟", onTap: () { setTimer(context); }, ), ), ), AppStyle.divider, Obx( () => SettingsNumber( value: controller.appC.updateFollowThreadCount.value, title: "更新线程数", subtitle: "多线程可以能更快的完成加载,但可能会因为请求太频繁导致读取状态失败", min: 1, max: 12, onChanged: (e) { controller.appC.setUpdateFollowThreadCount(e); }, ), ), ], ), ), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 24), child: Text( "关注导入导出", style: Get.textTheme.titleSmall, ), ), fileImportAndExportBuild(), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 24), child: Text( "数据校准", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( mainAxisSize: MainAxisSize.min, children: [ SettingsAction( title: "数据校准", subtitle: '关注以及标签数据错乱可以点击此功能进行校准,请勿重复点击', onTap: controller.followDataCheck, ), ], ), ), ], ), ], ), ); } Widget fileImportAndExportBuild() { return SettingsCard( child: Column( children: [ SettingsAction( leading: const Icon(Remix.save_2_line), title: "导出文件", onTap: ()=>FollowService.instance.exportFile(), ), SettingsAction( leading: const Icon(Remix.folder_open_line), title: "导入文件", onTap: ()=>FollowService.instance.inputFile(), ), SettingsAction( leading: const Icon(Remix.text), title: "导出文本", onTap: ()=>FollowService.instance.exportText(), ), SettingsAction( leading: const Icon(Remix.file_text_line), title: "导入文本", onTap: ()=>FollowService.instance.inputText(), ), ], ), ); } void setTimer(BuildContext context) async { var value = await showTimePicker( context: context, initialTime: TimeOfDay( hour: controller.appC.autoUpdateFollowDuration.value ~/ 60, minute: controller.appC.autoUpdateFollowDuration.value % 60, ), initialEntryMode: TimePickerEntryMode.inputOnly, builder: (_, child) { return MediaQuery( data: MediaQuery.of(context).copyWith( alwaysUse24HourFormat: true, ), child: child!, ); }, ); if (value == null || (value.hour == 0 && value.minute == 0)) { return; } var duration = Duration(hours: value.hour, minutes: value.minute); controller.appC.setAutoUpdateFollowDuration(duration.inMinutes); FollowService.instance.initTimer(); } } ================================================ FILE: simple_live_app/lib/modules/follow_user/follow_info_setting/follow_info_controller.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/app/utils/url_parse.dart'; import 'package:simple_live_app/models/db/follow_user.dart' show FollowUser; import 'package:simple_live_app/models/db/follow_user_tag.dart'; import 'package:simple_live_app/models/db/history.dart'; import 'package:simple_live_app/services/db_service.dart'; import 'package:simple_live_app/services/follow_service.dart'; import 'package:simple_live_core/simple_live_core.dart'; class FollowInfoController extends BasePageController { final Rxn followUser = Rxn(); // 下拉可选标签:只包含“全部”+用户自定义标签 final RxList tagOptions = [].obs; final Rx selectedTag = Rx(null); // 平台迁移预留:列出除当前平台外的其余平台 final RxList migrationSites = [].obs; // 平台迁移:输入链接 final TextEditingController migrationUrlController = TextEditingController(); @override void onInit() { super.onInit(); // 读取传入的 FollowUser final args = Get.arguments; if (args is FollowUser) { followUser.value = args; } else if (args is Map && args['follow'] is FollowUser) { followUser.value = args['follow'] as FollowUser; } _initTagOptions(); _initMigrationSites(); } void _initTagOptions() { final List options = FollowService.instance.getTagOptionsWithAll(); tagOptions.assignAll(options); // 设置选中项 final current = followUser.value; if (current != null) { FollowUserTag? matched; for (final e in options) { if (e.tag == current.tag) { matched = e; break; } } selectedTag.value = matched ?? options.first; } } void _initMigrationSites() { final current = followUser.value; if (current == null) { migrationSites.clear(); return; } final List all = Sites.supportSites; migrationSites.assignAll( all.where((s) => s.id != current.siteId).toList(), ); } void changeTag(FollowUserTag newTag) { final current = followUser.value; if (current == null) return; FollowService.instance.setFollowTag(current, newTag); selectedTag.value = newTag; followUser.refresh(); } void updateRemark(String newRemark) { final current = followUser.value; if (current == null) return; current.remark = newRemark; FollowService.instance.addFollow(current); followUser.refresh(); } Future pasteFromClipboard() async { final content = await Utils.getClipboard(); if (content != null) { migrationUrlController.text = content; } } Future parseAndMigrate() async { final url = migrationUrlController.text.trim(); if (url.isEmpty) { SmartDialog.showToast('链接不能为空'); return; } final result = await UrlParse.instance.parse(url); if (result.isEmpty || result.first == '') { SmartDialog.showToast('无法解析此链接'); return; } final String newRoomId = result.first as String; final Site newSite = result[1] as Site; final current = followUser.value; if (current == null) return; // 防呆 bool contain = FollowService.instance.getFollowExist("${newSite.id}_$newRoomId"); if (contain == true) { SmartDialog.showToast('目标主播已关注,无需迁移'); return; } final confirmed = await Get.dialog(AlertDialog( title: const Text('确认迁移'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '从:${Sites.allSites[current.siteId]?.name} 房间号:${current.roomId}'), const SizedBox(height: 8), Text('到:${newSite.name} 房间号:$newRoomId'), ], ), actions: [ TextButton( onPressed: () => Get.back(result: false), child: const Text('取消'), ), TextButton( onPressed: () => Get.back(result: true), child: const Text('确定'), ), ], )); if (confirmed != true) return; await _migrateTo(newSite, newRoomId); SmartDialog.showToast('迁移成功'); } @override Future refreshData() async { pageLoadding.value = true; var site = Sites.allSites[followUser.value?.siteId]!; await _migrateTo(site, followUser.value!.roomId); pageLoadding.value = false; SmartDialog.showToast('已刷新用户信息'); } Future _migrateTo(Site targetSite, String targetRoomId) async { final current = followUser.value; if (current == null) return; // 获取目标直播间详细信息 用于更新主播名和头像 LiveRoomDetail detail = await targetSite.liveSite.getRoomDetail(roomId: targetRoomId); // 复制并更新关键信息 final FollowUser newFollow = FollowUser( id: '${targetSite.id}_$targetRoomId', roomId: targetRoomId, siteId: targetSite.id, userName: detail.userName, face: detail.userAvatar, addTime: current.addTime, watchDuration: current.watchDuration, tag: current.tag, ); newFollow.liveStatus.value = current.liveStatus.value; // 更新标签归属 if (current.tag != '全部') { FollowUserTag? tagObj; for (final t in FollowService.instance.followTagList) { if (t.tag == current.tag) { tagObj = t; break; } } if (tagObj != null) { // 自刷新和迁移逻辑一致:删旧增新 tagObj.userId.remove(current.id); tagObj.userId.add(newFollow.id); await FollowService.instance.updateFollowUserTag(tagObj); } } // 替换关注 await FollowService.instance.removeFollowUser(current.id); FollowService.instance.addFollow(newFollow); // 更新关注同时 更新历史记录数据 History? oldHistroy = DBService.instance.getHistory(current.id); // null不迁移 if (oldHistroy != null) { final History newHistory = History( id: '${targetSite.id}_$targetRoomId', roomId: targetRoomId, siteId: targetSite.id, userName: detail.userName, face: detail.userAvatar, watchDuration: oldHistroy.watchDuration, updateTime: oldHistroy.updateTime, ); await DBService.instance.delHistory(oldHistroy.id); DBService.instance.addOrUpdateHistory(newHistory); } // 刷新本地数据并更新UI await FollowService.instance.loadData(updateStatus: false); followUser.value = newFollow; _initMigrationSites(); } } ================================================ FILE: simple_live_app/lib/modules/follow_user/follow_info_setting/follow_info_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/modules/follow_user/follow_info_setting/follow_info_controller.dart'; import 'package:simple_live_app/widgets/settings/settings_menu.dart'; class FollowInfoPage extends GetView { const FollowInfoPage({super.key}); @override Widget build(BuildContext context) { final site = Sites.allSites[controller.followUser.value!.siteId]!; return Scaffold( appBar: AppBar( title: const Text("关注信息设置"), actions: [ Obx( () => controller.pageLoadding.value ? const IconButton( onPressed: null, icon: SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, ), ), ) : IconButton( onPressed: () { controller.refreshData(); }, icon: const Icon(Icons.refresh), ), ), ], ), body: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 顶部:头像+平台+房间号 Padding( padding: AppStyle.edgeInsetsA12, child: Obx( () => Row( children: [ CircleAvatar( radius: 28, backgroundImage: NetworkImage(controller.followUser.value!.face), ), AppStyle.hGap12, Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( controller.followUser.value!.remark?.isNotEmpty == true ? '${controller.followUser.value!.userName} (${controller.followUser.value!.remark!})' : controller.followUser.value!.userName, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), AppStyle.vGap4, Row( children: [ Image.asset(site.logo, width: 18), AppStyle.hGap8, Flexible( child: Text( '${site.name} 房间号:${controller.followUser.value!.roomId}', style: TextStyle( fontSize: 12, color: Theme.of(context) .textTheme .bodySmall ?.color ?.withValues(alpha: .7), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), ], ), ), ], ), ), ), AppStyle.divider, // 标签设置:底部弹出选择 Padding( padding: AppStyle.edgeInsetsA12, child: Obx(() { final items = controller.tagOptions; final selected = controller.selectedTag.value; final Map valueMap = { for (final t in items) t.tag: t.tag, }; return SettingsMenu( title: '标签设置', value: selected?.tag ?? '全部', valueMap: valueMap, onChanged: (value) { final target = items.firstWhere( (e) => e.tag == value, orElse: () => items.first, ); controller.changeTag(target); }, ); }), ), AppStyle.divider, Padding( padding: AppStyle.edgeInsetsA12, child: Obx(() { return ListTile( title: Text('备注设置', style: Theme.of(context).textTheme.bodyLarge), visualDensity: VisualDensity.compact, shape: RoundedRectangleBorder( borderRadius: AppStyle.radius8, ), contentPadding: AppStyle.edgeInsetsL16.copyWith(right: 8), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ Text( controller.followUser.value?.remark?.isNotEmpty == true ? controller.followUser.value!.remark! : '无', style: Theme.of(context) .textTheme .bodyMedium! .copyWith(color: Colors.grey), ), AppStyle.hGap4, const Icon( Icons.chevron_right, color: Colors.grey, ), ], ), onTap: () { final textController = TextEditingController( text: controller.followUser.value?.remark); Get.dialog( AlertDialog( title: const Text("修改备注"), content: TextField( controller: textController, decoration: const InputDecoration( border: OutlineInputBorder(), hintText: "请输入备注名"), autofocus: true, onSubmitted: (value) { controller.updateRemark(value.trim()); Get.back(); }, ), actions: [ TextButton( onPressed: () => Get.back(), child: const Text("取消"), ), TextButton( onPressed: () { controller.updateRemark(textController.text.trim()); Get.back(); }, child: const Text("确定"), ), ], ), ); }, ); }), ), AppStyle.divider, // 平台迁移:输入链接解析 Padding( padding: AppStyle.edgeInsetsA12, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: const [ Icon(Remix.link), SizedBox(width: 8), Text('平台迁移(输入直播链接进行解析)'), ], ), AppStyle.vGap8, Row( children: [ Expanded( child: TextField( controller: controller.migrationUrlController, decoration: const InputDecoration( hintText: '粘贴主播在新平台的直播间链接,如 https://... ', border: OutlineInputBorder(), isDense: true, contentPadding: EdgeInsets.symmetric( horizontal: 12, vertical: 10, ), ), onSubmitted: (_) => controller.parseAndMigrate(), ), ), AppStyle.vGap8, IconButton( tooltip: '粘贴', onPressed: controller.pasteFromClipboard, icon: const Icon(Remix.clipboard_line), ), ElevatedButton.icon( onPressed: controller.parseAndMigrate, icon: const Icon(Remix.arrow_right_line), label: const Text('迁移'), ), ], ), AppStyle.vGap12, Text( "other todo ...", style: TextStyle(color: Colors.grey), ) ], ), ), ], ), ); } } ================================================ FILE: simple_live_app/lib/modules/follow_user/follow_info_setting/readme.md ================================================ ## 关注信息管理界面 - 修改主播名 - 迁移直播间功能 - 修改标签 ================================================ FILE: simple_live_app/lib/modules/follow_user/follow_user_controller.dart ================================================ // ignore_for_file: invalid_use_of_protected_member import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/event_bus.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/models/db/follow_user.dart'; import 'package:simple_live_app/models/db/follow_user_tag.dart'; import 'package:simple_live_app/routes/app_navigation.dart'; import 'package:simple_live_app/services/follow_service.dart'; class FollowUserController extends BasePageController { StreamSubscription? onUpdatedIndexedStream; StreamSubscription? onUpdatedListStream; /// 0:全部 1:直播中 2:未直播 var filterMode = FollowUserTag(id: "0", tag: "全部", userId: []).obs; RxList tagList = [ FollowUserTag(id: "0", tag: "全部", userId: []), FollowUserTag(id: "1", tag: "直播中", userId: []), FollowUserTag(id: "2", tag: "未开播", userId: []), ].obs; // 用户自定义标签 RxList userTagList = [].obs; // 用户自定义显示顺序 - default:watchDuration Rx sortMethod = SortMethod.watchDuration.obs; // 排序方式 var sortMap = { SortMethod.watchDuration: "观看时长", SortMethod.siteId: "直播平台", SortMethod.recently: "最近添加", SortMethod.userNameASC: "用户名A-Z", SortMethod.userNameDESC: "用户名Z-A", }; // 关注列表样式 var followStyleMap = {true: "紧凑模式", false: "卡片模式"}; @override void onInit() { onUpdatedIndexedStream = EventBus.instance.listen( EventBus.kBottomNavigationBarClicked, (index) { if (index == 1) { scrollToTopOrRefresh(); } }, ); onUpdatedListStream = FollowService.instance.updatedListStream.listen( (event) { updateTagList(); filterData(); }, ); sortMethod = AppSettingsController.instance.followSortMethod; super.onInit(); } @override Future refreshData() async { await FollowService.instance.loadData(); updateTagList(); super.refreshData(); } @override Future> getData(int page, int pageSize) async { if (page > 1) { return Future.value([]); } if (filterMode.value.tag == "全部") { return FollowService.instance.followList.value; } else if (filterMode.value.tag == "直播中") { return FollowService.instance.liveList.value; } else if (filterMode.value.tag == "未开播") { return FollowService.instance.notLiveList.value; } else { FollowService.instance.filterDataByTag(filterMode.value); return FollowService.instance.curTagFollowList.value; } } void updateTagList() { userTagList.assignAll(FollowService.instance.followTagList); tagList.value = tagList.take(3).toList(); for (var i in userTagList) { if (!tagList.contains(i)) { tagList.add(i); } } } void filterData() { if (filterMode.value.tag == "全部") { list.assignAll(FollowService.instance.followList.value); } else if (filterMode.value.tag == "直播中") { list.assignAll(FollowService.instance.liveList.value); } else if (filterMode.value.tag == "未开播") { list.assignAll(FollowService.instance.notLiveList.value); } else { FollowService.instance.filterDataByTag(filterMode.value); list.assignAll(FollowService.instance.curTagFollowList); } } // 用户自定义关注样式 Future showFollowStyleDialog() async { var res = await Utils.showMapOptionDialog( title: "关注样式切换", followStyleMap, AppSettingsController.instance.followStyleNotGrid.value, ); if (res != null) { AppSettingsController.instance.setFollowStyleNotGrid(res); } } // 用户自定义顺序dialog Future showSortDialog() async { var res = await Utils.showMapOptionDialog(sortMap, sortMethod.value, title: "排序方式"); if (res != null) { sortMethod.value = res; AppSettingsController.instance.setFollowSortMethod(sortMethod.value); if (filterMode.value.tag == "未开播" || filterMode.value.tag == "全部" || filterMode.value.tag == "直播中") { FollowService.instance.liveListSort(); } filterData(); } } void setFilterMode(FollowUserTag tag) { filterMode.value = tag; filterData(); } void removeFollow(FollowUser follow) async { var result = await Utils.showAlertDialog("确定要取消关注${follow.userName}吗?", title: "取消关注"); if (!result) { return; } // 取消关注同时删除标签内的 userId if (follow.tag != "全部") { var tag = tagList.firstWhereOrNull((tag) => tag.tag == follow.tag); if (tag != null) { tag.userId.remove(follow.id); updateTag(tag); } } await FollowService.instance.removeFollowUser(follow.id); refreshData(); } void updateFollow(FollowUser follow) { FollowService.instance.addFollow(follow); } void setFollowTag(FollowUser follow, FollowUserTag targetTag) { FollowService.instance.setFollowTag(follow, targetTag); filterData(); } Future updateTag(FollowUserTag followUserTag) async { await FollowService.instance.updateFollowUserTag(followUserTag); } // 弹出底部菜单栏 void showBottomMenu(FollowUser item) { Get.bottomSheet( SafeArea( child: Wrap( children: [ ListTile( leading: const Icon(Remix.price_tag_3_line), title: const Text('设置标签'), onTap: () { Get.back(); setFollowTagDialog(item); }, ), ListTile( leading: const Icon(Remix.information_line), title: const Text('查看详情'), onTap: () { Get.back(); AppNavigator.toFollowInfo(item); }, ), ], ), ), backgroundColor: Get.theme.cardColor, ); } void setFollowTagDialog(FollowUser follow) { /// 控制单选ui List copiedList = [ tagList.first, ...tagList.skip(3), ]; Rx checkTag = tagList.indexOf(filterMode.value) < 3 ? copiedList.first.obs : filterMode.value.obs; final ScrollController scrollController = ScrollController(); Get.dialog( AlertDialog( contentPadding: const EdgeInsets.all(16.0), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), ), content: Column( mainAxisSize: MainAxisSize.min, children: [ // 标题栏 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '设置标签', style: TextStyle( fontSize: 18, ), ), IconButton( icon: const Icon( Icons.check, ), onPressed: () { setFollowTag(follow, checkTag.value); Get.back(); }, ), ], ), const Divider(), Obx( () { int selectedIndex = copiedList.indexOf(checkTag.value); WidgetsBinding.instance.addPostFrameCallback((_) { if (selectedIndex >= 0) { scrollController.animateTo( selectedIndex * 60.0, // 假设每项高度为 60 duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); } }); return SizedBox( height: 300, width: 300, child: RadioGroup( groupValue: checkTag.value, onChanged: (value) { checkTag.value = value!; }, child: ListView.builder( controller: scrollController, itemCount: copiedList.length, itemBuilder: (context, index) { var tagItem = copiedList[index]; return Container( decoration: BoxDecoration( border: Border( bottom: BorderSide( color: Colors.grey.shade300, width: 1.0), ), ), child: RadioListTile( title: Text(tagItem.tag), value: tagItem, ), ); }, ), ), ); }, ), ], ), ), ); } @override void onClose() { onUpdatedIndexedStream?.cancel(); super.onClose(); } } ================================================ FILE: simple_live_app/lib/modules/follow_user/follow_user_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/modules/follow_user/follow_user_controller.dart'; import 'package:simple_live_app/routes/app_navigation.dart'; import 'package:simple_live_app/routes/route_path.dart'; import 'package:simple_live_app/services/follow_service.dart'; import 'package:simple_live_app/widgets/filter_button.dart'; import 'package:simple_live_app/widgets/follow_user_item.dart'; import 'package:simple_live_app/widgets/keep_alive_wrapper.dart'; import 'package:simple_live_app/widgets/live_room_card.dart'; import 'package:simple_live_app/widgets/page_grid_view.dart'; import 'package:simple_live_core/simple_live_core.dart'; class FollowUserPage extends GetView { const FollowUserPage({super.key}); @override Widget build(BuildContext context) { var count = MediaQuery.of(context).size.width ~/ 500; if (count < 1) count = 1; var c = MediaQuery.of(context).size.width ~/ 200; if (c < 2) { c = 2; } return Scaffold( appBar: AppBar( title: const Text("关注用户"), actions: [ PopupMenuButton( itemBuilder: (context) { return const [ PopupMenuItem( value: 0, child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Remix.trophy_line), AppStyle.hGap12, Text("赛事订阅"), ], ), ), PopupMenuItem( value: 1, child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Remix.blender_line), AppStyle.hGap12, Text("模式切换"), ], ), ), PopupMenuItem( value: 2, child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Remix.sort_asc), AppStyle.hGap12, Text("按序排列"), ], ), ), PopupMenuItem( value: 4, child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Remix.heart_line), AppStyle.hGap12, Text("关注设置"), ], ), ), ]; }, onSelected: (value) { if (value == 4) { Get.toNamed(RoutePath.kSettingsFollow); } else if (value == 0) { SmartDialog.showToast("此功能暂未开放!敬请期待!"); } else if (value == 1) { controller.showFollowStyleDialog(); } else if (value == 2) { controller.showSortDialog(); } }, ), ], leading: Obx( () => FollowService.instance.updating.value ? const IconButton( onPressed: null, icon: SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, ), ), ) : IconButton( onPressed: () { controller.refreshData(); }, icon: const Icon(Icons.refresh), ), ), ), body: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: AppStyle.edgeInsetsL8, child: Row( children: [ Expanded( child: Obx( () => SingleChildScrollView( scrollDirection: Axis.horizontal, child: Wrap( spacing: 12, children: controller.tagList.map( (option) { return FilterButton( text: option.tag, selected: controller.filterMode.value == option, onTap: () { controller.setFilterMode(option); }, ); }, ).toList(), ), ), ), ), ], ), ), Obx( () => Expanded( child: AppSettingsController.instance.followStyleNotGrid.value ? PageGridView( crossAxisSpacing: 12, crossAxisCount: count, pageController: controller, firstRefresh: true, showPCRefreshButton: false, itemBuilder: (_, i) { var item = controller.list[i]; var site = Sites.allSites[item.siteId]!; return FollowUserItem( item: item, onRemove: () { controller.removeFollow(item); }, onTap: () { AppNavigator.toLiveRoomDetail( site: site, roomId: item.roomId); }, onLongPress: () { // 长按弹出操作:设置标签或查看详情 controller.showBottomMenu(item); }, ); }, ) : KeepAliveWrapper( child: PageGridView( pageController: controller, padding: AppStyle.edgeInsetsA12, firstRefresh: true, mainAxisSpacing: 12, crossAxisSpacing: 12, crossAxisCount: c, itemBuilder: (_, i) { var item = controller.list[i]; // 或许直接继承字段更好,标记工作 LiveRoomItem liveRoomItem = LiveRoomItem( roomId: item.roomId, title: item.title.value, cover: item.cover.value, userName: item.userName, online: item.online.value, ); var site = Sites.allSites[item.siteId]!; return LiveRoomCard( site, liveRoomItem, onLongPress: () { controller.showBottomMenu(item); }, ); }, ), ), ), ), ], ), ); } } ================================================ FILE: simple_live_app/lib/modules/home/home_controller.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/event_bus.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/modules/home/home_list_controller.dart'; import 'package:simple_live_app/routes/route_path.dart'; class HomeController extends GetxController with GetSingleTickerProviderStateMixin { late TabController tabController; HomeController() { tabController = TabController(length: Sites.supportSites.length, vsync: this); } StreamSubscription? streamSubscription; @override void onInit() { streamSubscription = EventBus.instance.listen( EventBus.kBottomNavigationBarClicked, (index) { if (index == 0) { refreshOrScrollTop(); } }, ); for (var site in Sites.supportSites) { Get.put(HomeListController(site), tag: site.id); } super.onInit(); } void refreshOrScrollTop() { var tabIndex = tabController.index; BasePageController controller; controller = Get.find(tag: Sites.supportSites[tabIndex].id); controller.scrollToTopOrRefresh(); } void toSearch() { Get.toNamed(RoutePath.kSearch); } @override void onClose() { streamSubscription?.cancel(); super.onClose(); } } ================================================ FILE: simple_live_app/lib/modules/home/home_list_controller.dart ================================================ import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_core/simple_live_core.dart'; class HomeListController extends BasePageController { final Site site; HomeListController(this.site); @override Future> getData(int page, int pageSize) async { var result = await site.liveSite.getRecommendRooms(page: page); return result.items; } } ================================================ FILE: simple_live_app/lib/modules/home/home_list_view.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/modules/home/home_list_controller.dart'; import 'package:simple_live_app/widgets/keep_alive_wrapper.dart'; import 'package:simple_live_app/widgets/live_room_card.dart'; import 'package:simple_live_app/widgets/page_grid_view.dart'; class HomeListView extends StatelessWidget { final String tag; const HomeListView(this.tag, {super.key}); HomeListController get controller => Get.find(tag: tag); @override Widget build(BuildContext context) { var c = MediaQuery.of(context).size.width ~/ 200; if (c < 2) { c = 2; } return KeepAliveWrapper( child: PageGridView( pageController: controller, padding: AppStyle.edgeInsetsA12, firstRefresh: true, mainAxisSpacing: 12, crossAxisSpacing: 12, crossAxisCount: c, itemBuilder: (_, i) { var item = controller.list[i]; return LiveRoomCard(controller.site, item); }, ), ); } } ================================================ FILE: simple_live_app/lib/modules/home/home_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/modules/home/home_controller.dart'; import 'package:simple_live_app/modules/home/home_list_view.dart'; class HomePage extends GetView { const HomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( titleSpacing: 8, title: TabBar( controller: controller.tabController, labelPadding: AppStyle.edgeInsetsH20, isScrollable: true, indicatorSize: TabBarIndicatorSize.label, tabAlignment: TabAlignment.center, tabs: Sites.supportSites .map( (e) => Tab( //text: e.name, child: Row( children: [ Image.asset( e.logo, width: 24, ), AppStyle.hGap8, Text(e.name), ], ), ), ) .toList(), ), actions: [ IconButton( onPressed: controller.toSearch, icon: const Icon(Icons.search), ) ], ), body: TabBarView( controller: controller.tabController, children: Sites.supportSites .map( (e) => HomeListView( e.id, ), ) .toList(), ), ); } } ================================================ FILE: simple_live_app/lib/modules/indexed/indexed_controller.dart ================================================ import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/event_bus.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/modules/category/category_controller.dart'; import 'package:simple_live_app/modules/category/category_page.dart'; import 'package:simple_live_app/modules/home/home_controller.dart'; import 'package:simple_live_app/modules/home/home_page.dart'; import 'package:simple_live_app/modules/follow_user/follow_user_controller.dart'; import 'package:simple_live_app/modules/follow_user/follow_user_page.dart'; import 'package:simple_live_app/modules/mine/mine_page.dart'; class IndexedController extends GetxController { RxList items = RxList([]); var index = 0.obs; RxList pages = RxList([ const SizedBox(), const SizedBox(), const SizedBox(), const SizedBox(), ]); void setIndex(int i) { if (pages[i] is SizedBox) { switch (items[i].index) { case 0: Get.put(HomeController()); pages[i] = const HomePage(); break; case 1: Get.put(FollowUserController()); pages[i] = const FollowUserPage(); break; case 2: Get.put(CategoryController()); pages[i] = const CategoryPage(); break; case 3: pages[i] = const MinePage(); break; default: } } else { if (index.value == i) { EventBus.instance .emit(EventBus.kBottomNavigationBarClicked, items[i].index); } } index.value = i; } @override void onInit() { Future.delayed(Duration.zero, showFirstRun); items.value = AppSettingsController.instance.homeSort .map((key) => Constant.allHomePages[key]!) .toList(); setIndex(0); super.onInit(); } void showFirstRun() async { var settingsController = Get.find(); if (settingsController.firstRun) { settingsController.setNoFirstRun(); await Utils.showStatement(); Utils.checkUpdate(); } } } ================================================ FILE: simple_live_app/lib/modules/indexed/indexed_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'indexed_controller.dart'; class IndexedPage extends GetView { const IndexedPage({super.key}); @override Widget build(BuildContext context) { return OrientationBuilder( builder: (context, orientation) { return Scaffold( body: Row( children: [ Visibility( visible: orientation == Orientation.landscape, child: Obx( () => NavigationRail( selectedIndex: controller.index.value, onDestinationSelected: controller.setIndex, labelType: NavigationRailLabelType.none, destinations: controller.items .map( (item) => NavigationRailDestination( icon: Icon(item.iconData), label: Text(item.title), padding: AppStyle.edgeInsetsV8, ), ) .toList(), ), ), ), Expanded( child: Obx( () => Container( decoration: BoxDecoration( border: Border( left: orientation == Orientation.landscape ? BorderSide( color: Colors.grey.withAlpha(50), width: 1, ) : BorderSide.none, ), ), child: IndexedStack( index: controller.index.value, children: controller.pages, ), ), ), ), ], ), bottomNavigationBar: Visibility( visible: orientation == Orientation.portrait, child: Obx( () => NavigationBar( selectedIndex: controller.index.value, onDestinationSelected: controller.setIndex, height: 56, labelBehavior: NavigationDestinationLabelBehavior.alwaysHide, destinations: controller.items .map( (item) => NavigationDestination( icon: Icon(item.iconData), label: item.title, ), ) .toList(), ), ), ), ); }, ); } } ================================================ FILE: simple_live_app/lib/modules/live_room/danmu/danmaku_mask.dart ================================================ import 'dart:async'; import 'dart:isolate'; import 'dart:math'; // 通过滑动窗口、分桶、哈希去重的弹幕屏蔽器 // 原理类似于mask,单位时间内已有弹幕占据过滤矩阵,过滤重复弹幕 // 通过滑动窗口,实现弹幕time to live策略 // 直接使用会造成开屏时卡UI线程 class DanmakuMask { final int baseWindowMs; final int bucketCount; final bool useNormalization; final bool useFrequencyControl; final int maxFrequency; final bool adaptiveWindow; late int _windowMs; late int _bucketSizeMs; int _currentBucket = 0; int _lastShiftMs = 0; late final List> _buckets; final Map _freqMap = {}; DanmakuMask({ this.baseWindowMs = 5000, this.bucketCount = 5, this.useNormalization = false, this.useFrequencyControl = false, this.maxFrequency = 3, this.adaptiveWindow = false, }) { _windowMs = baseWindowMs; _bucketSizeMs = _windowMs ~/ bucketCount; _buckets = List.generate(bucketCount, (_) => {}); } String _normalize(String text) { if (!useNormalization) return text; return text .trim() .replaceAll(RegExp(r"\s+"), "") .replaceAll(RegExp(r"[~!!??,.,。]"), "") .toLowerCase(); } void _shiftIfNeeded(int nowMs) { while (nowMs - _lastShiftMs >= _bucketSizeMs) { _lastShiftMs += _bucketSizeMs; _currentBucket = (_currentBucket + 1) % bucketCount; final expiredBucket = _buckets[_currentBucket]; for (final hash in expiredBucket) { _freqMap[hash] = (_freqMap[hash] ?? 1) - 1; if (_freqMap[hash]! <= 0) { _freqMap.remove(hash); } } expiredBucket.clear(); } } void _adaptWindow() { if (!adaptiveWindow) return; final totalItems = _buckets.fold(0, (sum, b) => sum + b.length); if (totalItems > 300) { _windowMs = max(baseWindowMs ~/ 2, 1500); } else if (totalItems < 50) { _windowMs = baseWindowMs; } _bucketSizeMs = _windowMs ~/ bucketCount; } // 替换为List传递消息,减少线程间通信开销 List allowList(List texts, int nowMs) { _shiftIfNeeded(nowMs); _adaptWindow(); final results = []; for (String text in texts) { final normalizedText = _normalize(text); final hash = normalizedText.hashCode; var isAllowed = true; for (final bucket in _buckets) { if (bucket.contains(hash)) { isAllowed = false; break; } } if (isAllowed && useFrequencyControl) { final freq = _freqMap[hash] ?? 0; if (freq >= maxFrequency) { isAllowed = false; } } if (isAllowed) { _buckets[_currentBucket].add(hash); _freqMap[hash] = (_freqMap[hash] ?? 0) + 1; } results.add(isAllowed); } return results; } void reset() { for (var b in _buckets) { b.clear(); } _freqMap.clear(); _currentBucket = 0; _lastShiftMs = 0; _windowMs = baseWindowMs; _bucketSizeMs = _windowMs ~/ bucketCount; } // 基于动态规划的 Levenshtein 距离 计算,用于文本相似度计算,暂时搁置 int _editDistance(String a, String b) { final m = a.length; final n = b.length; List> dp = List.generate(m + 1, (_) => List.filled(n + 1, 0)); for (int i = 0; i <= m; i++) { dp[i][0] = i; } for (int j = 0; j <= n; j++) { dp[0][j] = j; } for (int i = 1; i < m + 1; i++) { for (int j = 1; j < n + 1; j++) { if (a[i - 1] == b[j - 1]) { dp[i][j] = dp[i - 1][j - 1]; } else { dp[i][j] = 1 + min( dp[i - 1][j - 1], min(dp[i][j - 1], dp[i - 1][j]), ); } } } return dp[m][n]; } } // Isolate class IsolateDanmakuMask { late final SendPort _isolateSendPort; final _mainReceivePort = ReceivePort(); late final Isolate _isolate; final Map> _pendingRequests = {}; int _requestId = 0; IsolateDanmakuMask._(); static Future create({ int baseWindowMs = 15000, int bucketCount = 15, bool useNormalization = false, bool useFrequencyControl = false, int maxFrequency = 3, bool adaptiveWindow = false, }) async { final mask = IsolateDanmakuMask._(); await mask._init( baseWindowMs: baseWindowMs, bucketCount: bucketCount, useNormalization: useNormalization, useFrequencyControl: useFrequencyControl, maxFrequency: maxFrequency, adaptiveWindow: adaptiveWindow, ); return mask; } Future _init({ required int baseWindowMs, required int bucketCount, required bool useNormalization, required bool useFrequencyControl, required int maxFrequency, required bool adaptiveWindow, }) async { final initCompleter = Completer(); _mainReceivePort.listen((message) { if (message is SendPort) { _isolateSendPort = message; if (!initCompleter.isCompleted) { initCompleter.complete(); } } else if (message is List) { final id = message[0] as int; final result = message[1]; _pendingRequests.remove(id)?.complete(result); } }); final params = { 'baseWindowMs': baseWindowMs, 'bucketCount': bucketCount, 'useNormalization': useNormalization, 'useFrequencyControl': useFrequencyControl, 'maxFrequency': maxFrequency, 'adaptiveWindow': adaptiveWindow, }; _isolate = await Isolate.spawn( _danmakuIsolateEntryPoint, [_mainReceivePort.sendPort, params], ); return initCompleter.future; } Future> allowList(List texts, int nowMs) { final id = _requestId++; final completer = Completer>(); _pendingRequests[id] = completer; _isolateSendPort.send(['allowList', id, texts, nowMs]); return completer.future; } void reset() { _isolateSendPort.send(['reset']); } void dispose() { _isolateSendPort.send(['dispose']); _mainReceivePort.close(); _isolate.kill(priority: Isolate.immediate); } } void _danmakuIsolateEntryPoint(List initialMessage) { final mainSendPort = initialMessage[0] as SendPort; final params = initialMessage[1] as Map; final danmakuMask = DanmakuMask( baseWindowMs: params['baseWindowMs'], bucketCount: params['bucketCount'], useNormalization: params['useNormalization'], useFrequencyControl: params['useFrequencyControl'], maxFrequency: params['maxFrequency'], adaptiveWindow: params['adaptiveWindow'], ); final isolateReceivePort = ReceivePort(); mainSendPort.send(isolateReceivePort.sendPort); isolateReceivePort.listen((message) { if (message is! List) return; final command = message[0] as String; switch (command) { case 'allowList': final id = message[1] as int; final texts = (message[2] as List).cast(); final nowMs = message[3] as int; final List allowedList = danmakuMask.allowList(texts, nowMs); mainSendPort.send([id, allowedList]); break; case 'reset': danmakuMask.reset(); break; case 'dispose': isolateReceivePort.close(); break; } }); } ================================================ FILE: simple_live_app/lib/modules/live_room/live_room_controller.dart ================================================ import 'dart:async'; import 'dart:io'; import 'package:canvas_danmaku/canvas_danmaku.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:media_kit/media_kit.dart'; import 'package:share_plus/share_plus.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/event_bus.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/app/utils/sandbox.dart'; import 'package:simple_live_app/models/db/follow_user.dart'; import 'package:simple_live_app/models/db/history.dart'; import 'package:simple_live_app/modules/live_room/player/player_controller.dart'; import 'package:simple_live_app/modules/settings/danmu_settings_page.dart'; import 'package:simple_live_app/services/db_service.dart'; import 'package:simple_live_app/services/follow_service.dart'; import 'package:simple_live_app/services/history_service.dart'; import 'package:simple_live_app/src/rust/api/danmaku_mask.dart'; import 'package:simple_live_app/widgets/desktop_refresh_button.dart'; import 'package:simple_live_app/widgets/follow_user_item.dart'; import 'package:simple_live_core/simple_live_core.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; class LiveRoomController extends PlayerController with WidgetsBindingObserver { final Site pSite; final String pRoomId; late LiveDanmaku liveDanmaku; late DanmakuMask rustDanmakuMask; List danmakuBuffer = []; Timer? danmakuTimer; bool _isProcessingBuffer = false; LiveRoomController({ required this.pSite, required this.pRoomId, }) { rxSite = pSite.obs; rxRoomId = pRoomId.obs; liveDanmaku = site.liveSite.getDanmaku(); // 抖音应该默认是竖屏的 if (site.id == "douyin") { isVertical.value = true; } } late Rx rxSite; Site get site => rxSite.value; late Rx rxRoomId; String get roomId => rxRoomId.value; Rx detail = Rx(null); var online = 0.obs; var followed = false.obs; var liveStatus = false.obs; RxList superChats = RxList(); /// 滚动控制 final ScrollController scrollController = ScrollController(); /// 聊天信息 RxList messages = RxList(); /// 清晰度数据 RxList qualites = RxList(); /// 当前清晰度 var currentQuality = -1; var currentQualityInfo = "".obs; /// 线路数据 RxList playUrls = RxList(); Map? playHeaders; /// 当前线路 var currentLineIndex = -1; var currentLineInfo = "".obs; /// 退出倒计时 var countdown = 60.obs; Timer? autoExitTimer; /// 设置的自动关闭时间(分钟) var autoExitMinutes = 60.obs; ///是否延迟自动关闭 var delayAutoExit = false.obs; /// 是否启用自动关闭 var autoExitEnable = false.obs; /// 是否禁用自动滚动聊天栏 /// - 当用户向上滚动聊天栏时,不再自动滚动 var disableAutoScroll = false.obs; /// 是否处于后台 var isBackground = false; /// 直播间加载失败 var loadError = false.obs; Error? error; @override void onInit() { WidgetsBinding.instance.addObserver(this); if (FollowService.instance.followList.isEmpty) { FollowService.instance.loadData(); } initAutoExit(); showDanmakuState.value = AppSettingsController.instance.danmuEnable.value; followed.value = FollowService.instance.getFollowExist("${site.id}_$roomId"); loadData(); scrollController.addListener(scrollListener); _initDanmakuMask(); super.onInit(); } void _initDanmakuMask() async { rustDanmakuMask = DanmakuMask( baseWindowMs: AppSettingsController.instance.danmuWindowMs.value * 1000, bucketCount: AppSettingsController.instance.danmuWindowMs.value, useNormalization: AppSettingsController.instance.danmuTextNormalization.value, useFrequencyControl: AppSettingsController.instance.danmuFrequencyControl.value, maxFrequency: AppSettingsController.instance.danmuMaxFrequency.value, adaptiveWindow: false, ); danmakuTimer = Timer.periodic( const Duration(milliseconds: 500), (timer) { _processDanmakuBuffer(); }, ); } // 缓存降低跨线程消息开销 估算弹幕延迟在800ms左右 void _processDanmakuBuffer() async { if (_isProcessingBuffer) return; if (danmakuBuffer.isEmpty) return; _isProcessingBuffer = true; try { final batch = List.from(danmakuBuffer); danmakuBuffer.clear(); final batchMessages = batch.map((e) => e.message).toList(); final nowMs = DateTime.now().millisecondsSinceEpoch; final allowedResults = await rustDanmakuMask.allowListBatch(texts: batchMessages, nowMs: BigInt.from(nowMs)); final filteredBatch = []; for (int i = 0; i < batch.length; i++) { if (allowedResults[i] == 1) { filteredBatch.add(batch[i]); } } if (filteredBatch.isEmpty) return; messages.addAll(filteredBatch); if (messages.length > 200 && !disableAutoScroll.value) { messages.removeRange(0, messages.length - 200); } WidgetsBinding.instance.addPostFrameCallback( (_) => chatScrollToBottom(), ); if (!liveStatus.value || isBackground) { return; } addDanmaku(filteredBatch .map((msg) => DanmakuContentItem( msg.message, color: Color.fromARGB( 255, msg.color.r, msg.color.g, msg.color.b, ), )) .toList()); } finally { _isProcessingBuffer = false; } } void scrollListener() { if (scrollController.position.userScrollDirection == ScrollDirection.forward) { disableAutoScroll.value = true; } } /// 初始化自动关闭倒计时 void initAutoExit() { if (AppSettingsController.instance.autoExitEnable.value) { autoExitEnable.value = true; autoExitMinutes.value = AppSettingsController.instance.autoExitDuration.value; setAutoExit(); } else { autoExitMinutes.value = AppSettingsController.instance.roomAutoExitDuration.value; } } void setAutoExit() { if (!autoExitEnable.value) { autoExitTimer?.cancel(); return; } autoExitTimer?.cancel(); countdown.value = autoExitMinutes.value * 60; autoExitTimer = Timer.periodic(const Duration(seconds: 1), (timer) async { countdown.value -= 1; if (countdown.value <= 0) { timer = Timer(const Duration(seconds: 10), () async { await WakelockPlus.disable(); exit(0); }); autoExitTimer?.cancel(); var delay = await Utils.showAlertDialog("定时关闭已到时,是否延迟关闭?", title: "延迟关闭", confirm: "延迟", cancel: "关闭", selectable: true); if (delay) { timer.cancel(); delayAutoExit.value = true; showAutoExitSheet(); setAutoExit(); } else { delayAutoExit.value = false; await WakelockPlus.disable(); exit(0); } } }); } // 弹窗逻辑 void refreshRoom() { //messages.clear(); superChats.clear(); liveDanmaku.stop(); loadData(); } /// 聊天栏始终滚动到底部 void chatScrollToBottom() { if (scrollController.hasClients) { // 如果手动上拉过,就不自动滚动到底部 if (disableAutoScroll.value) { return; } scrollController.jumpTo(scrollController.position.maxScrollExtent); } } /// 初始化弹幕接收事件 void initDanmau() { liveDanmaku.onMessage = onWSMessage; liveDanmaku.onClose = onWSClose; liveDanmaku.onReady = onWSReady; } /// 接收到WebSocket信息 void onWSMessage(LiveMessage msg) async { if (msg.type == LiveMessageType.chat) { // 关键词屏蔽检查 for (var keyword in AppSettingsController.instance.shieldList) { Pattern? pattern; if (Utils.isRegexFormat(keyword)) { String removedSlash = Utils.removeRegexFormat(keyword); try { pattern = RegExp(removedSlash); } catch (e) { // should avoid this during add keyword Log.d("关键词:$keyword 正则格式错误"); } } else { pattern = keyword; } if (pattern != null && msg.message.contains(pattern)) { Log.d("关键词:$keyword\n已屏蔽消息内容:${msg.message}"); return; } } // messages.length>n 预加载部分弹幕后启用去重功能 if (AppSettingsController.instance.danmakuMaskEnable.value&& messages.length > 50) { danmakuBuffer.add(msg); } else { if (messages.length > 200 && !disableAutoScroll.value) { messages.removeAt(0); } messages.add(msg); WidgetsBinding.instance.addPostFrameCallback( (_) => chatScrollToBottom(), ); if (!liveStatus.value || isBackground) { return; } addDanmaku([ DanmakuContentItem( msg.message, color: Color.fromARGB( 255, msg.color.r, msg.color.g, msg.color.b, ), ), ]); } } else if (msg.type == LiveMessageType.online) { online.value = msg.data; } else if (msg.type == LiveMessageType.superChat) { superChats.add(msg.data); } } /// 添加一条系统消息 void addSysMsg(String msg) { messages.add( LiveMessage( type: LiveMessageType.chat, userName: "LiveSysMessage", message: msg, color: LiveMessageColor.white, ), ); } /// 接收到WebSocket关闭信息 void onWSClose(String msg) { addSysMsg(msg); } /// WebSocket准备就绪 void onWSReady() { addSysMsg("弹幕服务器连接正常"); } /// 加载直播间信息 void loadData() async { try { SmartDialog.showLoading(msg: ""); loadError.value = false; addSysMsg("正在读取直播间信息"); detail.value = await site.liveSite.getRoomDetail(roomId: roomId); if (site.id == Constant.kDouyin) { // 1.6.0之前收藏的WebRid // 1.6.0收藏的RoomID // 1.6.0之后改回WebRid if (detail.value!.roomId != roomId) { var oldId = roomId; rxRoomId.value = detail.value!.roomId; if (followed.value) { // 更新关注列表 DBService.instance.deleteFollow("${site.id}_$oldId"); DBService.instance.addFollow( FollowUser( id: "${site.id}_$roomId", roomId: roomId, siteId: site.id, userName: detail.value!.userName, face: detail.value!.userAvatar, addTime: DateTime.now(), ), ); } else { followed.value = DBService.instance.getFollowExist("${site.id}_$roomId"); } } } getSuperChatMessage(); addHistory(); // 确认房间关注状态 followed.value = FollowService.instance.getFollowExist("${site.id}_$roomId"); online.value = detail.value!.online; liveStatus.value = detail.value!.status || detail.value!.isRecord; if (liveStatus.value) { getPlayQualites(); addSysMsg("开始连接弹幕服务器"); initDanmau(); liveDanmaku.start(detail.value?.danmakuData); } if (detail.value!.isRecord) { addSysMsg("当前主播未开播,正在轮播录像"); } } catch (e) { Log.logPrint(e); //SmartDialog.showToast(e.toString()); loadError.value = true; if(e is Error){ error = e; } } finally { SmartDialog.dismiss(status: SmartStatus.loading); } } /// 初始化播放器 void getPlayQualites() async { currentQuality = -1; try { var playQualites = await site.liveSite.getPlayQualites(detail: detail.value!); if (playQualites.isEmpty) { SmartDialog.showToast("无法读取播放清晰度"); return; } qualites.assignAll(playQualites); var qualityLevel = await getQualityLevel(); if (qualityLevel == 2) { //最高 currentQuality = 0; } else if (qualityLevel == 0) { //最低 currentQuality = playQualites.length - 1; } else { //中间值 int middle = (playQualites.length / 2).floor(); currentQuality = middle; } await getPlayUrl(); } catch (e) { Log.logPrint(e); SmartDialog.showToast("无法读取播放清晰度"); } } Future getQualityLevel() async { var qualityLevel = AppSettingsController.instance.qualityLevel.value; try { var connectivityResult = await (Connectivity().checkConnectivity()); if (connectivityResult.first == ConnectivityResult.mobile) { qualityLevel = AppSettingsController.instance.qualityLevelCellular.value; } } catch (e) { Log.logPrint(e); } return qualityLevel; } Future getPlayUrl() async { currentQualityInfo.value = qualites[currentQuality].quality; currentLineInfo.value = ""; currentLineIndex = -1; var playUrl = await site.liveSite .getPlayUrls(detail: detail.value!, quality: qualites[currentQuality]); if (playUrl.urls.isEmpty) { SmartDialog.showToast("无法读取播放地址"); return; } playUrls.assignAll(playUrl.urls); // 深拷贝 playHeaders = playUrl.headers; currentLineIndex = 0; currentLineInfo.value = "线路${currentLineIndex + 1}"; //重置错误次数 mediaErrorRetryCount = 0; initPlaylist(); } void changePlayLine(int index) { currentLineIndex = index; //重置错误次数 mediaErrorRetryCount = 0; setPlayer(); } void initPlaylist() async { currentLineInfo.value = "线路${currentLineIndex + 1}"; errorMsg.value = ""; final mediaList = playUrls.map((url) { var finalUrl = url; if (AppSettingsController.instance.playerForceHttps.value) { finalUrl = finalUrl.replaceAll("http://", "https://"); } return Media(finalUrl, httpHeaders: playHeaders); }).toList(); // 初始化播放器并设置 ao 参数 await initializePlayer(); await player.open(Playlist(mediaList)); } void setPlayer() async { currentLineInfo.value = "线路${currentLineIndex + 1}"; errorMsg.value = ""; await player.jump(currentLineIndex); } @override void mediaEnd() async { super.mediaEnd(); if (mediaErrorRetryCount < 2) { Log.d("播放结束,尝试第${mediaErrorRetryCount + 1}次刷新"); if (mediaErrorRetryCount == 1) { //延迟一秒再刷新 await Future.delayed(const Duration(seconds: 1)); } mediaErrorRetryCount += 1; //刷新一次 setPlayer(); return; } Log.d("播放结束"); // 遍历线路,如果全部链接都断开就是直播结束了 if (playUrls.length - 1 == currentLineIndex) { liveStatus.value = false; } else { changePlayLine(currentLineIndex + 1); //setPlayer(); } } int mediaErrorRetryCount = 0; @override void mediaError(String error) async { super.mediaEnd(); if (mediaErrorRetryCount < 2) { Log.d("播放失败,尝试第${mediaErrorRetryCount + 1}次刷新"); if (mediaErrorRetryCount == 1) { //延迟一秒再刷新 await Future.delayed(const Duration(seconds: 1)); } mediaErrorRetryCount += 1; //刷新一次 setPlayer(); return; } if (playUrls.length - 1 == currentLineIndex) { errorMsg.value = "播放失败"; SmartDialog.showToast("播放失败:$error"); } else { //currentLineIndex += 1; //setPlayer(); changePlayLine(currentLineIndex + 1); } } /// 读取SC void getSuperChatMessage() async { try { var sc = await site.liveSite.getSuperChatMessage(roomId: detail.value!.roomId); superChats.addAll(sc); } catch (e) { Log.logPrint(e); addSysMsg("SC读取失败"); } } /// 移除掉已到期的SC void removeSuperChats() async { var now = DateTime.now().millisecondsSinceEpoch; superChats.removeWhere((x) => x.endTime.millisecondsSinceEpoch < now); } /// 添加历史记录 void addHistory() { if (detail.value == null) { return; } var id = "${site.id}_$roomId"; History history = History( id: id, roomId: roomId, siteId: site.id, userName: detail.value?.userName ?? "", face: detail.value?.userAvatar ?? "", updateTime: DateTime.now(), ); HistoryService.instance.start(history); } /// 关注用户 void followUser() { if (detail.value == null) { return; } var id = "${site.id}_$roomId"; var historyDuration = HistoryService.instance.getHistoryDuration(followUserId: id); FollowService.instance.addFollow( FollowUser( id: id, roomId: roomId, siteId: site.id, userName: detail.value?.userName ?? "", face: detail.value?.userAvatar ?? "", addTime: DateTime.now(), watchDuration: historyDuration, ), ); followed.value = true; EventBus.instance.emit(Constant.kUpdateFollow, id); } /// 取消关注用户 void removeFollowUser() async { if (detail.value == null) { return; } if (!await Utils.showAlertDialog("确定要取消关注该用户吗?", title: "取消关注")) { return; } var id = "${site.id}_$roomId"; FollowService.instance.removeFollowUser(id); followed.value = false; EventBus.instance.emit(Constant.kUpdateFollow, id); } void share() { if (detail.value == null) { return; } SharePlus.instance.share(ShareParams(text: detail.value!.url)); } void copyUrl() { if (detail.value == null) { return; } Utils.copyToClipboard(detail.value!.url); SmartDialog.showToast("已复制直播间链接"); } Future visitWebLive() async { Uri uri = Uri.parse(detail.value!.url); if (await canLaunchUrl(uri) || runningInSandbox()) { await launchUrl(uri); } else { throw '无法打开网页 $uri'; } } /// 底部打开播放器设置 void showDanmuSettingsSheet() { Utils.showBottomSheet( title: "弹幕设置", child: ListView( padding: AppStyle.edgeInsetsA12, children: [ DanmuSettingsView( danmakuController: danmakuController, onTapDanmuShield: () { Get.back(); showDanmuShield(); }, ), ], ), ); } void showVolumeSlider(BuildContext targetContext) { SmartDialog.showAttach( targetContext: targetContext, alignment: Alignment.topCenter, displayTime: const Duration(seconds: 3), maskColor: const Color(0x00000000), builder: (context) { return Container( decoration: BoxDecoration( borderRadius: AppStyle.radius12, color: Theme.of(context).cardColor, ), padding: AppStyle.edgeInsetsA4, child: Obx( () => SizedBox( width: 200, child: Slider( min: 0, max: 100, value: AppSettingsController.instance.playerVolume.value, onChanged: (newValue) { player.setVolume(newValue); AppSettingsController.instance.setPlayerVolume(newValue); }, ), ), ), ); }, ); } void showQualitySheet() { Utils.showBottomSheet( title: "切换清晰度", child: RadioGroup( groupValue: currentQuality, onChanged: (e) async { Get.back(); currentQuality = e ?? 0; await getPlayUrl(); }, child: ListView.builder( itemCount: qualites.length, itemBuilder: (_, i) { var item = qualites[i]; return RadioListTile( value: i, title: Text(item.quality), ); }, ), ), ); } void showPlayUrlsSheet() { Utils.showBottomSheet( title: "切换线路", child: RadioGroup( groupValue: currentLineIndex, onChanged: (e) { Get.back(); //currentLineIndex = i; //setPlayer(); changePlayLine(e ?? 0); }, child: ListView.builder( itemCount: playUrls.length, itemBuilder: (_, i) { return RadioListTile( value: i, title: Text("线路${i + 1}"), secondary: Text( playUrls[i].contains(".flv") ? "FLV" : "HLS", ), ); }, ), ), ); } void showPlayerSettingsSheet() { Utils.showBottomSheet( title: "画面尺寸", child: Obx( () => ListView( padding: AppStyle.edgeInsetsV12, children: [ RadioGroup( groupValue: AppSettingsController.instance.scaleMode.value, onChanged: (e) { AppSettingsController.instance.setScaleMode(e ?? 0); updateScaleMode(); }, child: Column( children: [ RadioListTile( value: 0, title: const Text("适应"), visualDensity: VisualDensity.compact, ), RadioListTile( value: 1, title: const Text("拉伸"), visualDensity: VisualDensity.compact, ), RadioListTile( value: 2, title: const Text("铺满"), visualDensity: VisualDensity.compact, ), RadioListTile( value: 3, title: const Text("16:9"), visualDensity: VisualDensity.compact, ), RadioListTile( value: 4, title: const Text("4:3"), visualDensity: VisualDensity.compact, ), ], ), ), ], ), ), ); } void showDanmuShield() { TextEditingController keywordController = TextEditingController(); void addKeyword() { if (keywordController.text.isEmpty) { SmartDialog.showToast("请输入关键词"); return; } AppSettingsController.instance .addShieldList(keywordController.text.trim()); keywordController.text = ""; } Utils.showBottomSheet( title: "关键词屏蔽", child: ListView( padding: AppStyle.edgeInsetsA12, children: [ TextField( controller: keywordController, decoration: InputDecoration( contentPadding: AppStyle.edgeInsetsH12, border: const OutlineInputBorder(), hintText: "请输入关键词", suffixIcon: TextButton.icon( onPressed: addKeyword, icon: const Icon(Icons.add), label: const Text("添加"), ), ), onSubmitted: (e) { addKeyword(); }, ), AppStyle.vGap12, Obx( () => Text( "已添加${AppSettingsController.instance.shieldList.length}个关键词(点击移除)", style: Get.textTheme.titleSmall, ), ), AppStyle.vGap12, Obx( () => Wrap( runSpacing: 12, spacing: 12, children: AppSettingsController.instance.shieldList .map( (item) => InkWell( borderRadius: AppStyle.radius24, onTap: () { AppSettingsController.instance.removeShieldList(item); }, child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: AppStyle.radius24, ), padding: AppStyle.edgeInsetsH12.copyWith( top: 4, bottom: 4, ), child: Text( item, style: Get.textTheme.bodyMedium, ), ), ), ) .toList(), ), ), ], ), ); } void showFollowUserSheet() { Utils.showBottomSheet( title: "关注列表", child: Obx( () => Stack( children: [ RefreshIndicator( onRefresh: FollowService.instance.loadData, child: ListView.builder( itemCount: FollowService.instance.liveList.length, itemBuilder: (_, i) { var item = FollowService.instance.liveList[i]; return Obx( () => FollowUserItem( item: item, playing: rxSite.value.id == item.siteId && rxRoomId.value == item.roomId, onTap: () { Get.back(); resetRoom( Sites.allSites[item.siteId]!, item.roomId, ); }, ), ); }, ), ), if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) Positioned( right: 12, bottom: 12, child: Obx( () => DesktopRefreshButton( refreshing: FollowService.instance.updating.value, onPressed: FollowService.instance.loadData, ), ), ), ], ), ), ); } void showAutoExitSheet() { if (AppSettingsController.instance.autoExitEnable.value && !delayAutoExit.value) { SmartDialog.showToast("已设置了全局定时关闭"); return; } Utils.showBottomSheet( title: "定时关闭", child: ListView( children: [ Obx( () => SwitchListTile( title: Text( "启用定时关闭", style: Get.textTheme.titleMedium, ), value: autoExitEnable.value, onChanged: (e) { autoExitEnable.value = e; setAutoExit(); //controller.setAutoExitEnable(e); }, ), ), Obx( () => ListTile( enabled: autoExitEnable.value, title: Text( "自动关闭时间:${autoExitMinutes.value ~/ 60}小时${autoExitMinutes.value % 60}分钟", style: Get.textTheme.titleMedium, ), trailing: const Icon(Icons.chevron_right), onTap: () async { var value = await showTimePicker( context: Get.context!, initialTime: TimeOfDay( hour: autoExitMinutes.value ~/ 60, minute: autoExitMinutes.value % 60, ), initialEntryMode: TimePickerEntryMode.inputOnly, builder: (_, child) { return MediaQuery( data: Get.mediaQuery.copyWith( alwaysUse24HourFormat: true, ), child: child!, ); }, ); if (value == null || (value.hour == 0 && value.minute == 0)) { return; } var duration = Duration(hours: value.hour, minutes: value.minute); autoExitMinutes.value = duration.inMinutes; AppSettingsController.instance .setRoomAutoExitDuration(autoExitMinutes.value); //setAutoExitDuration(duration.inMinutes); setAutoExit(); }, ), ), ], ), ); } void openNaviteAPP() async { var naviteUrl = ""; var webUrl = ""; if (site.id == Constant.kBiliBili) { naviteUrl = "bilibili://live/${detail.value?.roomId}"; webUrl = "https://live.bilibili.com/${detail.value?.roomId}"; } else if (site.id == Constant.kDouyin) { var args = detail.value?.danmakuData as DouyinDanmakuArgs; naviteUrl = "snssdk1128://webcast_room?room_id=${args.roomId}"; webUrl = "https://live.douyin.com/${args.webRid}"; } else if (site.id == Constant.kHuya) { var args = detail.value?.danmakuData as HuyaDanmakuArgs; naviteUrl = "yykiwi://homepage/index.html?banneraction=https%3A%2F%2Fdiy-front.cdn.huya.com%2Fzt%2Ffrontpage%2Fcc%2Fupdate.html%3Fhyaction%3Dlive%26channelid%3D${args.subSid}%26subid%3D${args.subSid}%26liveuid%3D${args.subSid}%26screentype%3D1%26sourcetype%3D0%26fromapp%3Dhuya_wap%252Fclick%252Fopen_app_guide%26&fromapp=huya_wap/click/open_app_guide"; webUrl = "https://www.huya.com/${detail.value?.roomId}"; } else if (site.id == Constant.kDouyu) { naviteUrl = "douyulink://?type=90001&schemeUrl=douyuapp%3A%2F%2Froom%3FliveType%3D0%26rid%3D${detail.value?.roomId}"; webUrl = "https://www.douyu.com/${detail.value?.roomId}"; } try { await launchUrlString(naviteUrl, mode: LaunchMode.externalApplication); } catch (e) { Log.logPrint(e); SmartDialog.showToast("无法打开APP,将使用浏览器打开"); await launchUrlString(webUrl, mode: LaunchMode.externalApplication); } } void resetRoom(Site site, String roomId) async { if (this.site == site && this.roomId == roomId) { return; } rxSite.value = site; rxRoomId.value = roomId; // 清除全部消息 liveDanmaku.stop(); messages.clear(); superChats.clear(); danmakuController?.clear(); // 重新设置LiveDanmaku liveDanmaku = site.liveSite.getDanmaku(); rustDanmakuMask.reset(); // 停止播放 await player.stop(); // 刷新信息 loadData(); HistoryService.instance.reset("${site.id}_$roomId"); } void copyErrorDetail() { Utils.copyToClipboard('''直播平台:${rxSite.value.name} 房间号:${rxRoomId.value} 错误信息: ${error?.toString()} ---------------- ${error?.stackTrace}'''); SmartDialog.showToast("已复制错误信息"); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.paused) { var height = MediaQuery.of(Get.context!).padding.top; Log.d("当前状态栏高度$height"); Log.d("进入后台"); //进入后台,关闭弹幕 danmakuController?.clear(); isBackground = true; } else //返回前台 if (state == AppLifecycleState.resumed) { // update(); var height = MediaQuery.of(Get.context!).padding.top; Log.d("当前状态栏高度$height"); Log.d("返回前台"); danmakuController?.resume; isBackground = false; } } @override void onClose() { WidgetsBinding.instance.removeObserver(this); scrollController.removeListener(scrollListener); autoExitTimer?.cancel(); danmakuTimer?.cancel(); HistoryService.instance.stop(); liveDanmaku.stop(); danmakuController = null; rustDanmakuMask.dispose(); super.onClose(); } } ================================================ FILE: simple_live_app/lib/modules/live_room/live_room_page.dart ================================================ import 'dart:io'; import 'package:floating/floating.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:lottie/lottie.dart'; import 'package:media_kit_video/media_kit_video.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/modules/live_room/live_room_controller.dart'; import 'package:simple_live_app/modules/live_room/player/player_controls.dart'; import 'package:simple_live_app/services/follow_service.dart'; import 'package:simple_live_app/widgets/desktop_refresh_button.dart'; import 'package:simple_live_app/widgets/follow_user_item.dart'; import 'package:simple_live_app/widgets/keep_alive_wrapper.dart'; import 'package:simple_live_app/widgets/net_image.dart'; import 'package:simple_live_app/widgets/settings/settings_action.dart'; import 'package:simple_live_app/widgets/settings/settings_card.dart'; import 'package:simple_live_app/widgets/settings/settings_number.dart'; import 'package:simple_live_app/widgets/settings/settings_switch.dart'; import 'package:simple_live_app/widgets/superchat_card.dart'; import 'package:simple_live_core/simple_live_core.dart'; class LiveRoomPage extends GetView { const LiveRoomPage({super.key}); @override Widget build(BuildContext context) { final page = Obx( () { if (controller.loadError.value) { return Scaffold( appBar: AppBar( title: const Text("直播间加载失败"), ), body: Padding( padding: AppStyle.edgeInsetsA12, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.center, children: [ LottieBuilder.asset( 'assets/lotties/error.json', height: 140, repeat: false, ), const Text( "直播间加载失败", textAlign: TextAlign.center, ), AppStyle.vGap4, Text( controller.error?.toString() ?? "未知错误", textAlign: TextAlign.center, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12, color: Colors.grey), ), AppStyle.vGap4, Text( "${controller.rxSite.value.id} - ${controller.rxRoomId.value}", textAlign: TextAlign.center, style: const TextStyle(fontSize: 12, color: Colors.grey), ), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ TextButton.icon( onPressed: controller.copyErrorDetail, icon: const Icon(Remix.file_copy_line), label: const Text("复制信息"), ), TextButton.icon( onPressed: controller.refreshRoom, icon: const Icon(Remix.refresh_line), label: const Text("刷新"), ), ], ) ], ), ), ); } if (controller.fullScreenState.value) { return PopScope( canPop: false, onPopInvokedWithResult: (e, r) { controller.exitFull(); }, child: Scaffold( body: buildMediaPlayer(), ), ); } else { return buildPageUI(); } }, ); if (!Platform.isAndroid) { return page; } return PiPSwitcher( floating: controller.pip, childWhenDisabled: page, childWhenEnabled: buildMediaPlayer(), ); } Widget buildPageUI() { return OrientationBuilder( builder: (context, orientation) { return Scaffold( appBar: AppBar( title: Obx( () => Text(controller.detail.value?.title ?? "直播间"), ), actions: buildAppbarActions(context), ), body: orientation == Orientation.portrait ? buildPhoneUI(context) : buildTabletUI(context), ); }, ); } Widget buildPhoneUI(BuildContext context) { return Column( children: [ AspectRatio( aspectRatio: 16 / 9, child: buildMediaPlayer(), ), buildUserProfile(context), buildMessageArea(), buildBottomActions(context), ], ); } Widget buildTabletUI(BuildContext context) { return Column( children: [ Expanded( child: Row( children: [ Expanded( child: buildMediaPlayer(), ), SizedBox( width: 300, child: Column( children: [ buildUserProfile(context), buildMessageArea(), ], ), ), ], ), ), Container( decoration: BoxDecoration( color: Theme.of(context).cardColor, border: Border( top: BorderSide( color: Colors.grey.withAlpha(25), ), ), ), padding: AppStyle.edgeInsetsV4.copyWith( bottom: AppStyle.bottomBarHeight + 4, ), child: Row( children: [ TextButton.icon( style: TextButton.styleFrom( textStyle: const TextStyle(fontSize: 14), ), onPressed: controller.refreshRoom, icon: const Icon(Remix.refresh_line), label: const Text("刷新"), ), Obx( () => controller.followed.value ? TextButton.icon( style: TextButton.styleFrom( textStyle: const TextStyle(fontSize: 14), ), onPressed: controller.removeFollowUser, icon: const Icon(Remix.heart_fill), label: const Text("取消关注"), ) : TextButton.icon( style: TextButton.styleFrom( textStyle: const TextStyle(fontSize: 14), ), onPressed: controller.followUser, icon: const Icon(Remix.heart_line), label: const Text("关注"), ), ), const Expanded(child: Center()), TextButton.icon( style: TextButton.styleFrom( textStyle: const TextStyle(fontSize: 14), ), onPressed: controller.share, icon: const Icon(Remix.share_line), label: const Text("分享"), ), (Platform.isWindows || Platform.isLinux) ? TextButton.icon( style: TextButton.styleFrom( textStyle: const TextStyle(fontSize: 14), ), onPressed: controller.visitWebLive, icon: const Icon(Remix.chrome_fill), label: const Text("浏览器打开"), ) : TextButton.icon( style: TextButton.styleFrom( textStyle: const TextStyle(fontSize: 14), ), onPressed: controller.copyUrl, icon: const Icon(Remix.file_copy_line), label: const Text("复制链接"), ), ], ), ), //buildBottomActions(context), ], ); } Widget buildMediaPlayer() { var boxFit = BoxFit.contain; double? aspectRatio; if (AppSettingsController.instance.scaleMode.value == 0) { boxFit = BoxFit.contain; } else if (AppSettingsController.instance.scaleMode.value == 1) { boxFit = BoxFit.fill; } else if (AppSettingsController.instance.scaleMode.value == 2) { boxFit = BoxFit.cover; } else if (AppSettingsController.instance.scaleMode.value == 3) { boxFit = BoxFit.contain; aspectRatio = 16 / 9; } else if (AppSettingsController.instance.scaleMode.value == 4) { boxFit = BoxFit.contain; aspectRatio = 4 / 3; } return Stack( children: [ Video( key: controller.globalPlayerKey, controller: controller.videoController, pauseUponEnteringBackgroundMode: AppSettingsController.instance.playerAutoPause.value, resumeUponEnteringForegroundMode: AppSettingsController.instance.playerAutoPause.value, controls: (state) { return playerControls(state, controller); }, aspectRatio: aspectRatio, fit: boxFit, // 自己实现 wakelock: false, ), Obx( () => Visibility( visible: !controller.liveStatus.value, child: const Center( child: Text( "未开播", style: TextStyle(fontSize: 16, color: Colors.white), ), ), ), ), ], ); } Widget buildUserProfile(BuildContext context) { return Container( decoration: BoxDecoration( color: Theme.of(context).cardColor, border: Border( top: BorderSide( color: Colors.grey.withAlpha(25), ), bottom: BorderSide( color: Colors.grey.withAlpha(25), ), ), ), padding: AppStyle.edgeInsetsA8.copyWith( left: 12, right: 12, ), child: Obx( () => Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Container( decoration: BoxDecoration( border: Border.all(color: Colors.grey.withAlpha(50)), borderRadius: AppStyle.radius24, ), child: NetImage( controller.detail.value?.userAvatar ?? "", width: 48, height: 48, borderRadius: 24, ), ), AppStyle.hGap12, Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( controller.detail.value?.userName ?? "", maxLines: 1, overflow: TextOverflow.ellipsis, ), AppStyle.vGap4, Row( children: [ Image.asset( controller.site.logo, width: 20, ), AppStyle.hGap4, Text( controller.site.name, style: const TextStyle( fontSize: 12, color: Colors.grey, ), ), ], ), ], ), ), AppStyle.hGap12, Row( mainAxisSize: MainAxisSize.min, children: [ const Icon( Remix.fire_fill, size: 20, color: Colors.orange, ), AppStyle.hGap4, Text( Utils.onlineToString( controller.detail.value?.online ?? 0, ), style: const TextStyle(fontSize: 14), ), ], ), ], ), ), ); } Widget buildBottomActions(BuildContext context) { return Container( decoration: BoxDecoration( color: Theme.of(context).cardColor, border: Border( top: BorderSide( color: Colors.grey.withAlpha(25), ), ), ), padding: EdgeInsets.only(bottom: AppStyle.bottomBarHeight), child: Row( children: [ Expanded( child: Obx( () => controller.followed.value ? TextButton.icon( style: TextButton.styleFrom( textStyle: const TextStyle(fontSize: 14), ), onPressed: controller.removeFollowUser, icon: const Icon(Remix.heart_fill), label: const Text("取消关注"), ) : TextButton.icon( style: TextButton.styleFrom( textStyle: const TextStyle(fontSize: 14), ), onPressed: controller.followUser, icon: const Icon(Remix.heart_line), label: const Text("关注"), ), ), ), Expanded( child: TextButton.icon( style: TextButton.styleFrom( textStyle: const TextStyle(fontSize: 14), ), onPressed: controller.refreshRoom, icon: const Icon(Remix.refresh_line), label: const Text("刷新"), ), ), Expanded( child: TextButton.icon( style: TextButton.styleFrom( textStyle: const TextStyle(fontSize: 14), ), onPressed: controller.share, icon: const Icon(Remix.share_line), label: const Text("分享"), ), ), ], ), ); } Widget buildMessageArea() { return Expanded( child: DefaultTabController( length: 4, child: Column( children: [ TabBar( indicatorSize: TabBarIndicatorSize.tab, labelPadding: EdgeInsets.zero, indicatorWeight: 1.0, tabs: [ const Tab( text: "聊天", ), Tab( child: Obx( () => Text( controller.superChats.isNotEmpty ? "SC(${controller.superChats.length})" : "SC", ), ), ), const Tab( text: "关注", ), const Tab( text: "设置", ), ], ), Expanded( child: TabBarView( children: [ Obx( () => Stack( children: [ ListView.separated( controller: controller.scrollController, separatorBuilder: (_, i) => Obx( () => SizedBox( // *2与原来的EdgeInsets.symmetric(vertical: )做兼容 height: AppSettingsController .instance.chatTextGap.value * 2, ), ), padding: AppStyle.edgeInsetsA12, itemCount: controller.messages.length, itemBuilder: (_, i) { var item = controller.messages[i]; return buildMessageItem(item); }, ), Visibility( visible: controller.disableAutoScroll.value, child: Positioned( right: 12, bottom: 12, child: ElevatedButton.icon( onPressed: () { controller.disableAutoScroll.value = false; controller.chatScrollToBottom(); }, icon: const Icon(Icons.expand_more), label: const Text("最新"), ), ), ), ], ), ), buildSuperChats(), buildFollowList(), buildSettings(), ], ), ), ], ), ), ); } Widget buildMessageItem(LiveMessage message) { if (message.userName == "LiveSysMessage") { return Obx( () => SelectableText( message.message, style: TextStyle( color: Colors.grey, fontSize: AppSettingsController.instance.chatTextSize.value, ), ), ); } return Obx( () => AppSettingsController.instance.chatBubbleStyle.value ? Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Flexible( child: Container( decoration: BoxDecoration( color: Colors.blueGrey.withAlpha(25), //borderRadius: AppStyle.radius8, borderRadius: const BorderRadius.only( topRight: Radius.circular(12), bottomLeft: Radius.circular(12), bottomRight: Radius.circular(12), ), ), padding: AppStyle.edgeInsetsA4.copyWith(left: 12, right: 12), child: SelectableText.rich( TextSpan( text: "${message.userName}:", style: TextStyle( color: Colors.grey, fontSize: AppSettingsController.instance.chatTextSize.value, ), children: [ TextSpan( text: message.message, style: TextStyle( color: Get.isDarkMode ? Colors.white : AppColors.black333, ), ) ], ), ), ), ), ], ) : SelectableText.rich( TextSpan( text: "${message.userName}:", style: TextStyle( color: Colors.grey, fontSize: AppSettingsController.instance.chatTextSize.value, ), children: [ TextSpan( text: message.message, style: TextStyle( color: Get.isDarkMode ? Colors.white : AppColors.black333, ), ) ], ), ), ); } Widget buildSuperChats() { return KeepAliveWrapper( child: Obx( () => ListView.separated( padding: AppStyle.edgeInsetsA12, itemCount: controller.superChats.length, separatorBuilder: (_, i) => AppStyle.vGap12, itemBuilder: (_, i) { var item = controller.superChats[i]; return SuperChatCard( item, onExpire: () { controller.removeSuperChats(); }, ); }, ), ), ); } Widget buildSettings() { return ListView( padding: AppStyle.edgeInsetsA12, children: [ Obx( () => Visibility( visible: controller.autoExitEnable.value, child: ListTile( leading: const Icon(Icons.timer_outlined), visualDensity: VisualDensity.compact, title: Text("${parseDuration(controller.countdown.value)}后自动关闭"), ), ), ), Padding( padding: AppStyle.edgeInsetsA12, child: Text( "聊天区", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( mainAxisSize: MainAxisSize.min, children: [ Obx( () => SettingsNumber( title: "文字大小", value: AppSettingsController.instance.chatTextSize.value.toInt(), min: 8, max: 36, onChanged: (e) { AppSettingsController.instance .setChatTextSize(e.toDouble()); }, ), ), AppStyle.divider, Obx( () => SettingsNumber( title: "上下间隔", value: AppSettingsController.instance.chatTextGap.value.toInt(), min: 0, max: 12, onChanged: (e) { AppSettingsController.instance.setChatTextGap(e.toDouble()); }, ), ), AppStyle.divider, Obx( () => SettingsSwitch( title: "气泡样式", value: AppSettingsController.instance.chatBubbleStyle.value, onChanged: (e) { AppSettingsController.instance.setChatBubbleStyle(e); }, ), ), ], ), ), Padding( padding: AppStyle.edgeInsetsA12, child: Text( "更多设置", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( mainAxisSize: MainAxisSize.min, children: [ SettingsAction( title: "关键词屏蔽", onTap: controller.showDanmuShield, ), AppStyle.divider, SettingsAction( title: "弹幕设置", onTap: controller.showDanmuSettingsSheet, ), AppStyle.divider, SettingsAction( title: "定时关闭", onTap: controller.showAutoExitSheet, ), AppStyle.divider, SettingsAction( title: "画面尺寸", onTap: controller.showPlayerSettingsSheet, ), ], ), ), ], ); } Widget buildFollowList() { return Obx( () => Stack( children: [ RefreshIndicator( onRefresh: FollowService.instance.loadData, child: ListView.builder( itemCount: FollowService.instance.liveList.length, itemBuilder: (_, i) { var item = FollowService.instance.liveList[i]; return Obx( () => FollowUserItem( item: item, playing: controller.rxSite.value.id == item.siteId && controller.rxRoomId.value == item.roomId, onTap: () { controller.resetRoom( Sites.allSites[item.siteId]!, item.roomId, ); }, ), ); }, ), ), if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) Positioned( right: 12, bottom: 12, child: Obx( () => DesktopRefreshButton( refreshing: FollowService.instance.updating.value, onPressed: FollowService.instance.loadData, ), ), ), ], ), ); } List buildAppbarActions(BuildContext context) { return [ IconButton( onPressed: () { showMore(); }, icon: const Icon(Icons.more_horiz), ), ]; } void showMore() { showModalBottomSheet( context: Get.context!, constraints: const BoxConstraints( maxWidth: 600, ), isScrollControlled: true, builder: (_) => Container( padding: EdgeInsets.only( bottom: AppStyle.bottomBarHeight, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.refresh), title: const Text("刷新"), trailing: const Icon(Icons.chevron_right), onTap: () { controller.refreshRoom(); }, ), ListTile( leading: const Icon(Icons.play_circle_outline), trailing: const Icon(Icons.chevron_right), title: const Text("切换清晰度"), onTap: () { Get.back(); controller.showQualitySheet(); }, ), ListTile( leading: const Icon(Icons.switch_video_outlined), title: const Text("切换线路"), trailing: const Icon(Icons.chevron_right), onTap: () { Get.back(); controller.showPlayUrlsSheet(); }, ), ListTile( leading: const Icon(Icons.aspect_ratio_outlined), title: const Text("画面尺寸"), trailing: const Icon(Icons.chevron_right), onTap: () { Get.back(); controller.showPlayerSettingsSheet(); }, ), ListTile( leading: const Icon(Icons.camera_alt_outlined), title: const Text("截图"), trailing: const Icon(Icons.chevron_right), onTap: () { controller.saveScreenshot(); }, ), Visibility( visible: Platform.isAndroid, child: ListTile( leading: const Icon(Icons.picture_in_picture), title: const Text("小窗播放"), trailing: const Icon(Icons.chevron_right), onTap: () { Get.back(); controller.enablePIP(); }, ), ), ListTile( leading: const Icon(Icons.timer_outlined), title: const Text("定时关闭"), trailing: const Icon(Icons.chevron_right), onTap: () { Get.back(); controller.showAutoExitSheet(); }, ), ListTile( leading: const Icon(Icons.share_sharp), title: const Text("分享直播间"), trailing: const Icon(Icons.chevron_right), onTap: () { Get.back(); controller.share(); }, ), ListTile( leading: const Icon(Icons.copy), title: const Text("复制链接"), trailing: const Icon(Icons.chevron_right), onTap: () { Get.back(); controller.copyUrl(); }, ), ListTile( leading: const Icon(Icons.open_in_new), title: const Text("APP 中打开"), trailing: const Icon(Icons.chevron_right), onTap: () { Get.back(); controller.openNaviteAPP(); }, ), ListTile( leading: const Icon(Icons.info_outline_rounded), title: const Text("播放信息"), trailing: const Icon(Icons.chevron_right), onTap: () { Get.back(); controller.showDebugInfo(); }, ), ], ), ), ); } String parseDuration(int sec) { // 转为时分秒 var h = sec ~/ 3600; var m = (sec % 3600) ~/ 60; var s = sec % 60; if (h > 0) { return "${h.toString().padLeft(2, '0')}小时${m.toString().padLeft(2, '0')}分钟${s.toString().padLeft(2, '0')}秒"; } if (m > 0) { return "${m.toString().padLeft(2, '0')}分钟${s.toString().padLeft(2, '0')}秒"; } return "${s.toString().padLeft(2, '0')}秒"; } } ================================================ FILE: simple_live_app/lib/modules/live_room/player/player_controller.dart ================================================ import 'dart:async'; import 'dart:io'; import 'package:auto_orientation_v2/auto_orientation_v2.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:file_picker/file_picker.dart'; import 'package:floating/floating.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:flutter_image_gallery_saver/flutter_image_gallery_saver.dart'; import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; import 'package:canvas_danmaku/canvas_danmaku.dart'; import 'package:path_provider/path_provider.dart'; import 'package:simple_live_app/app/event_bus.dart'; import 'package:simple_live_app/services/window_service.dart'; import 'package:volume_controller/volume_controller.dart'; import 'package:screen_brightness/screen_brightness.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/custom_throttle.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:window_manager/window_manager.dart'; mixin PlayerMixin { GlobalKey globalPlayerKey = GlobalKey(); GlobalKey globalDanmuKey = GlobalKey(); /// 播放器实例 late final player = Player( configuration: PlayerConfiguration( title: "Slive Player", logLevel: AppSettingsController.instance.logEnable.value ? MPVLogLevel.debug : MPVLogLevel.error, ), ); /// 初始化播放器并设置 ao 参数 Future initializePlayer() async { var pp = player.platform as NativePlayer; // 设置音频输出驱动 if (AppSettingsController.instance.customPlayerOutput.value) { await pp.setProperty( 'ao', AppSettingsController.instance.audioOutputDriver.value, ); } else if (Platform.isLinux) { await pp.setProperty('ao', 'alsa'); } // media_kit 仓库更新导致的问题,临时解决办法 if (Platform.isAndroid) { // 通过错误参数强制media_kit不seek, 解决了加载-pause-seek 在直播流上的开屏问题 await pp.setProperty('force-seekable', 'yes'); } // 低内存管理 // // 根据:https://mpv.io/manual/stable/#cache // --cache=// --cache-secs= // --demuxer-seekable-cache= // --demuxer-max-back-bytes= // --demuxer-donate-buffer== // // 内存换空间, 同时通过调整参数禁用mpv回放缓存(直播暂时不需要) // hls流/令牌流/.. 根据mdk-sdk作者回复, rtsp 在 ffmpeg存在内存泄露, 这意味着我们只能等待修复 await pp.setProperty("cache", "no"); await pp.setProperty("cache-secs", "0"); await pp.setProperty('demuxer-seekable-cache', 'no'); await pp.setProperty('demuxer-donate-buffer', 'no'); await pp.setProperty("demuxer-max-back-bytes", "0"); // 在所有平台上正确启用双重缓存,覆写mpv设置 if (AppSettingsController.instance.videoDoubleBuffering.value) { final directory = await getTemporaryDirectory(); await pp.setProperty("cache", "yes"); await pp.setProperty("cache-secs", "3"); await pp.setProperty('demuxer-seekable-cache', 'yes'); await pp.setProperty('demuxer-donate-buffer', 'yes'); await pp.setProperty("demuxer-cache-dir", directory.path); } // bili/douyin流存在时间戳跳变问题 // 真机建议-空间换内存-暂时不需要 // windows: // icc-cache-dir = "~~/cache/icc"; // gpu-shader-cache-dir = "~~/cache/shader" // watch-later-dir = "~~/cache/watch_later" } /// 视频控制器 late final videoController = VideoController( player, configuration: AppSettingsController.instance.customPlayerOutput.value ? VideoControllerConfiguration( vo: AppSettingsController.instance.videoOutputDriver.value, hwdec: AppSettingsController.instance.videoHardwareDecoder.value, ) : AppSettingsController.instance.playerCompatMode.value ? const VideoControllerConfiguration( vo: 'mediacodec_embed', hwdec: 'mediacodec', ) : VideoControllerConfiguration( enableHardwareAcceleration: AppSettingsController.instance.hardwareDecode.value, androidAttachSurfaceAfterVideoParameters: false, ), ); } mixin PlayerStateMixin on PlayerMixin { ///音量控制条计时器 Timer? hidevolumeTimer; /// 是否进入桌面端小窗 RxBool smallWindowState = false.obs; /// 是否显示弹幕 RxBool showDanmakuState = false.obs; /// 是否显示控制器 RxBool showControlsState = false.obs; /// 是否显示设置窗口 RxBool showSettingState = false.obs; /// 是否显示弹幕设置窗口 RxBool showDanmakuSettingState = false.obs; /// 是否处于锁定控制器状态 RxBool lockControlsState = false.obs; /// 是否处于全屏状态 RxBool fullScreenState = false.obs; /// 显示手势Tip RxBool showGestureTip = false.obs; /// 手势Tip文本 RxString gestureTipText = "".obs; /// 显示提示底部Tip RxBool showBottomTip = false.obs; /// 是否显示OSD统计信息 RxBool showOSDStats = false.obs; /// 提示底部Tip文本 RxString bottomTipText = "".obs; /// 自动隐藏控制器计时器 Timer? hideControlsTimer; /// 自动隐藏提示计时器 Timer? hideSeekTipTimer; /// 是否为竖屏直播间 var isVertical = false.obs; Widget? danmakuView; var showQualites = false.obs; var showLines = false.obs; /// 隐藏控制器 void hideControls() { showControlsState.value = false; hideControlsTimer?.cancel(); } void setLockState() { lockControlsState.value = !lockControlsState.value; if (lockControlsState.value) { showControlsState.value = false; } else { showControlsState.value = true; } } /// 显示控制器 void showControls() { showControlsState.value = true; resetHideControlsTimer(); } /// 开始隐藏控制器计时 /// - 当点击控制器上时功能时需要重新计时 void resetHideControlsTimer() { hideControlsTimer?.cancel(); hideControlsTimer = Timer( const Duration( seconds: 5, ), hideControls, ); } void updateScaleMode() { var boxFit = BoxFit.contain; double? aspectRatio; if (player.state.width != null && player.state.height != null) { aspectRatio = player.state.width! / player.state.height!; } if (AppSettingsController.instance.scaleMode.value == 0) { boxFit = BoxFit.contain; } else if (AppSettingsController.instance.scaleMode.value == 1) { boxFit = BoxFit.fill; } else if (AppSettingsController.instance.scaleMode.value == 2) { boxFit = BoxFit.cover; } else if (AppSettingsController.instance.scaleMode.value == 3) { boxFit = BoxFit.contain; aspectRatio = 16 / 9; } else if (AppSettingsController.instance.scaleMode.value == 4) { boxFit = BoxFit.contain; aspectRatio = 4 / 3; } globalPlayerKey.currentState?.update( aspectRatio: aspectRatio, fit: boxFit, ); } } mixin PlayerDanmakuMixin on PlayerStateMixin { /// 弹幕控制器 late DanmakuController? danmakuController; void initDanmakuController(DanmakuController e) { danmakuController = e; } void updateDanmuOption(DanmakuOption? option) { if (option == null) return; danmakuController?.updateOption(option); } void disposeDanmakuController() { danmakuController?.clear(); } void addDanmaku(List items) { if (!showDanmakuState.value) { return; } for (var item in items) { danmakuController?.addDanmaku(item); } } } mixin PlayerSystemMixin on PlayerMixin, PlayerStateMixin, PlayerDanmakuMixin { final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); final screenBrightness = ScreenBrightness(); final VolumeController volumeController = VolumeController.instance; final pip = Floating(); StreamSubscription? _pipSubscription; /// 初始化一些系统状态 void initSystem() async { if (Platform.isAndroid || Platform.isIOS) { volumeController.showSystemUI = false; } // 屏幕常亮 //WakelockPlus.enable(); // 开始隐藏计时 resetHideControlsTimer(); } /// 释放一些系统状态 Future resetSystem() async { _pipSubscription?.cancel(); //pip.dispose(); await SystemChrome.setEnabledSystemUIMode( SystemUiMode.edgeToEdge, overlays: SystemUiOverlay.values, ); await setPortraitOrientation(); if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) { // 亮度重置,桌面平台可能会报错,暂时不处理桌面平台的亮度 try { await screenBrightness.resetApplicationScreenBrightness(); } catch (e) { Log.logPrint(e); } } await WakelockPlus.disable(); } /// 进入全屏 void enterFullScreen() async { fullScreenState.value = true; if (Platform.isAndroid || Platform.isIOS) { //全屏 SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); if (!isVertical.value) { //横屏 setLandscapeOrientation(); } } else { bool isMaximized = await windowManager.isMaximized(); if (isMaximized) { await windowManager.setFullScreen(true); await windowManager.setTitleBarStyle(TitleBarStyle.hidden); } await windowManager.setFullScreen(true); } //danmakuController?.clear(); } /// 退出全屏 void exitFull() async { if (Platform.isAndroid || Platform.isIOS) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: SystemUiOverlay.values); setPortraitOrientation(); } else { bool isMaximized = await windowManager.isMaximized(); if (isMaximized) { await windowManager.setFullScreen(false); await windowManager.setTitleBarStyle(TitleBarStyle.normal); } windowManager.setFullScreen(false); } fullScreenState.value = false; //danmakuController?.clear(); } Size? _lastWindowSize; Offset? _lastWindowPosition; ///小窗模式() void enterSmallWindow() async { if (!(Platform.isAndroid || Platform.isIOS)) { fullScreenState.value = true; smallWindowState.value = true; WindowService.instance.isPIP = smallWindowState.value; // 读取窗口大小 _lastWindowSize = await windowManager.getSize(); _lastWindowPosition = await windowManager.getPosition(); windowManager.setTitleBarStyle(TitleBarStyle.hidden); // 获取视频窗口大小 var width = player.state.width ?? 16; var height = player.state.height ?? 9; // 横屏还是竖屏 if (height > width) { var aspectRatio = width / height; windowManager.setSize(Size(400, 400 / aspectRatio)); } else { var aspectRatio = height / width; windowManager.setSize(Size(280 / aspectRatio, 280)); } windowManager.setAlwaysOnTop(true); } } ///退出小窗模式() void exitSmallWindow() { if (!(Platform.isAndroid || Platform.isIOS)) { fullScreenState.value = false; smallWindowState.value = false; WindowService.instance.isPIP = smallWindowState.value; windowManager.setTitleBarStyle(TitleBarStyle.normal); windowManager.setSize(_lastWindowSize!); windowManager.setPosition(_lastWindowPosition!); windowManager.setAlwaysOnTop(false); //windowManager.setAlignment(Alignment.center); } } /// 设置横屏 Future setLandscapeOrientation() async { if (await beforeIOS16()) { AutoOrientation.landscapeAutoMode(); } else { SystemChrome.setPreferredOrientations([ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]); } } /// 设置竖屏 Future setPortraitOrientation() async { if (await beforeIOS16()) { AutoOrientation.portraitAutoMode(); } else { await SystemChrome.setPreferredOrientations(DeviceOrientation.values); } } /// 是否是IOS16以下 Future beforeIOS16() async { if (Platform.isIOS) { var info = await deviceInfo.iosInfo; var version = info.systemVersion; var versionInt = int.tryParse(version.split('.').first) ?? 0; return versionInt < 16; } else { return false; } } Future saveScreenshot() async { final imageSaver = ImageGallerySaver(); try { SmartDialog.showLoading(msg: "正在保存截图"); //检查相册权限,仅iOS需要 var permission = await Utils.checkPhotoPermission(); if (!permission) { SmartDialog.showToast("没有相册权限"); SmartDialog.dismiss(status: SmartStatus.loading); return; } var imageData = await player.screenshot(); if (imageData == null) { SmartDialog.showToast("截图失败,数据为空"); SmartDialog.dismiss(status: SmartStatus.loading); return; } if (Platform.isIOS || Platform.isAndroid) { await imageSaver.saveImage( imageData, ); SmartDialog.showToast("已保存截图至相册"); } else { //选择保存文件夹 var path = await FilePicker.platform.saveFile( allowedExtensions: ["jpg"], type: FileType.image, fileName: "${DateTime.now().millisecondsSinceEpoch}.jpg", ); if (path == null) { SmartDialog.showToast("取消保存"); SmartDialog.dismiss(status: SmartStatus.loading); return; } var file = File(path); await file.writeAsBytes(imageData); SmartDialog.showToast("已保存截图至${file.path}"); } } catch (e) { Log.logPrint(e); SmartDialog.showToast("截图失败"); } finally { SmartDialog.dismiss(status: SmartStatus.loading); } } /// 开启小窗播放前弹幕状态 bool danmakuStateBeforePIP = false; Future enablePIP() async { if (!Platform.isAndroid) { return; } if (await pip.isPipAvailable == false) { SmartDialog.showToast("设备不支持小窗播放"); return; } danmakuStateBeforePIP = showDanmakuState.value; //关闭并清除弹幕 if (AppSettingsController.instance.pipHideDanmu.value && danmakuStateBeforePIP) { showDanmakuState.value = false; } danmakuController?.clear(); //关闭控制器 showControlsState.value = false; //监听事件 var width = player.state.width ?? 0; var height = player.state.height ?? 0; Rational ratio = const Rational.landscape(); if (height > width) { ratio = const Rational.vertical(); } else { ratio = const Rational.landscape(); } await pip.enable( ImmediatePiP( aspectRatio: ratio, ), ); _pipSubscription ??= pip.pipStatusStream.listen((event) { if (event == PiPStatus.disabled) { // 返回前台时恢复弹幕 danmakuController?.resume(); showDanmakuState.value = danmakuStateBeforePIP; } Log.w(event.toString()); }); } } mixin PlayerGestureControlMixin on PlayerStateMixin, PlayerMixin, PlayerSystemMixin { /// 单击显示/隐藏控制器 void onTap() { if (showControlsState.value) { hideControls(); } else { showControls(); } } //桌面端操控 void onEnter(PointerEnterEvent event) { if (!showControlsState.value) { showControls(); } } void onExit(PointerExitEvent event) { if (showControlsState.value) { hideControls(); } } void onHover(PointerHoverEvent event, BuildContext context) { final screenHeight = MediaQuery.of(context).size.height; final targetPosition = screenHeight * 0.25; // 计算屏幕顶部25%的位置 if (event.position.dy <= targetPosition || event.position.dy >= targetPosition * 3) { if (!showControlsState.value) { showControls(); } } } /// 双击全屏/退出全屏 void onDoubleTap(TapDownDetails details) { if (lockControlsState.value) { return; } if (fullScreenState.value) { exitFull(); } else { enterFullScreen(); } } bool verticalDragging = false; bool leftVerticalDrag = false; var _currentVolume = 0.0; var _currentBrightness = 1.0; var verStartPosition = 0.0; DelayedThrottle? throttle; /// 竖向手势开始 void onVerticalDragStart(DragStartDetails details) async { if (lockControlsState.value && fullScreenState.value) { return; } final dy = details.globalPosition.dy; // 开始位置必须是中间2/4的位置 if (dy < Get.height * 0.25 || dy > Get.height * 0.75) { return; } verStartPosition = dy; leftVerticalDrag = details.globalPosition.dx < Get.width / 2; throttle = DelayedThrottle(200); verticalDragging = true; if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) { showGestureTip.value = true; } if (Platform.isAndroid || Platform.isIOS) { _currentVolume = await volumeController.getVolume(); } if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) { _currentBrightness = await screenBrightness.application; } } /// 竖向手势更新 void onVerticalDragUpdate(DragUpdateDetails e) async { if (lockControlsState.value && fullScreenState.value) { return; } if (verticalDragging == false) return; if (!Platform.isAndroid && !Platform.isIOS) { return; } //String text = ""; //double value = 0.0; Log.logPrint("$verStartPosition/${e.globalPosition.dy}"); if (leftVerticalDrag) { setGestureBrightness(e.globalPosition.dy); } else { setGestureVolume(e.globalPosition.dy); } } int lastVolume = -1; // it's ok to be -1 void setGestureVolume(double dy) { double value = 0.0; double seek; if (dy > verStartPosition) { value = ((dy - verStartPosition) / (Get.height * 0.5)); seek = _currentVolume - value; if (seek < 0) { seek = 0; } } else { value = ((dy - verStartPosition) / (Get.height * 0.5)); seek = value.abs() + _currentVolume; if (seek > 1) { seek = 1; } } int volume = _convertVolume((seek * 100).round()); if (volume == lastVolume) { return; } lastVolume = volume; // update UI outside throttle to make it more fluent gestureTipText.value = "音量 $volume%"; throttle?.invoke(() async => await _realSetVolume(volume)); } // 0 to 100, 5 step each int _convertVolume(int volume) { return (volume / 5).round() * 5; } Future _realSetVolume(int volume) async { Log.logPrint(volume); volumeController.setVolume(volume / 100); } void setGestureBrightness(double dy) { double value = 0.0; if (dy > verStartPosition) { value = ((dy - verStartPosition) / (Get.height * 0.5)); var seek = _currentBrightness - value; if (seek < 0) { seek = 0; } screenBrightness.setApplicationScreenBrightness(seek); gestureTipText.value = "亮度 ${(seek * 100).toInt()}%"; Log.logPrint(value); } else { value = ((dy - verStartPosition) / (Get.height * 0.5)); var seek = value.abs() + _currentBrightness; if (seek > 1) { seek = 1; } screenBrightness.setApplicationScreenBrightness(seek); gestureTipText.value = "亮度 ${(seek * 100).toInt()}%"; Log.logPrint(value); } } /// 竖向手势完成 void onVerticalDragEnd(DragEndDetails details) async { if (lockControlsState.value && fullScreenState.value) { return; } throttle = null; verticalDragging = false; leftVerticalDrag = false; showGestureTip.value = false; } } class PlayerController extends BaseController with PlayerMixin, PlayerStateMixin, PlayerDanmakuMixin, PlayerSystemMixin, PlayerGestureControlMixin { @override void onInit() { initSystem(); initStream(); //设置音量 player.setVolume(AppSettingsController.instance.playerVolume.value); super.onInit(); } StreamSubscription? _errorSubscription; StreamSubscription? _completedSubscription; StreamSubscription? _widthSubscription; StreamSubscription? _heightSubscription; StreamSubscription? _logSubscription; StreamSubscription? _playingSubscription; StreamSubscription? _escSubscription; void initStream() { _errorSubscription = player.stream.error.listen((event) { Log.d("播放器错误:$event"); // 跳过无音频输出的错误 // Could not open/initialize audio device -> no sound. if (event.contains('no sound.')) { return; } //SmartDialog.showToast(event); mediaError(event); }); _playingSubscription = player.stream.playing.listen((event) { if (event) { WakelockPlus.enable(); Log.d("Playing"); } }); _completedSubscription = player.stream.completed.listen((event) { if (event) { mediaEnd(); } }); _logSubscription = player.stream.log.listen((event) { Log.d("播放器日志:$event"); }); _widthSubscription = player.stream.width.listen((event) { Log.d( 'width:$event W:${(player.state.width)} H:${(player.state.height)}'); if (player.state.width == null) { return; } else { // 可获取直播流size时且不为全屏模式时判断是否进入全屏模式 isVertical.value = player.state.height! > player.state.width!; if (AppSettingsController.instance.autoFullScreen.value && !fullScreenState.value) { enterFullScreen(); } } }); _heightSubscription = player.stream.height.listen((event) { Log.d( 'height:$event W:${(player.state.width)} H:${(player.state.height)}'); isVertical.value = (player.state.height ?? 9) > (player.state.width ?? 16); }); _escSubscription = EventBus.instance.listen(EventBus.kEscapePressed, (event) { exitFull(); }); } void disposeStream() { _errorSubscription?.cancel(); _completedSubscription?.cancel(); _widthSubscription?.cancel(); _heightSubscription?.cancel(); _logSubscription?.cancel(); _pipSubscription?.cancel(); _playingSubscription?.cancel(); _escSubscription?.cancel(); } void mediaEnd() { WakelockPlus.disable(); } void mediaError(String error) { // 弱网调整:用户自责 // WakelockPlus.disable(); } Future toggleOSDStats() async { showOSDStats.value = !showOSDStats.value; if (player.platform is NativePlayer) { await (player.platform as NativePlayer).command([ 'script-binding', 'stats/display-page-1-toggle', ]); } } void showDebugInfo() { Utils.showBottomSheet( title: "播放信息", child: ListView( children: [ Obx(() => SwitchListTile( title: const Text("OSD 显示"), value: showOSDStats.value, onChanged: (value) => toggleOSDStats())), ListTile( title: const Text("Resolution"), subtitle: Text('${player.state.width}x${player.state.height}'), onTap: () { Clipboard.setData( ClipboardData( text: "Resolution\n${player.state.width}x${player.state.height}", ), ); }, ), ListTile( title: const Text("VideoParams"), subtitle: Text(player.state.videoParams.toString()), onTap: () { Clipboard.setData( ClipboardData( text: "VideoParams\n${player.state.videoParams}", ), ); }, ), ListTile( title: const Text("AudioParams"), subtitle: Text(player.state.audioParams.toString()), onTap: () { Clipboard.setData( ClipboardData( text: "AudioParams\n${player.state.audioParams}", ), ); }, ), ListTile( title: const Text("Media"), subtitle: Text(player.state.playlist.toString()), onTap: () { Clipboard.setData( ClipboardData( text: "Media\n${player.state.playlist}", ), ); }, ), ListTile( title: const Text("AudioTrack"), subtitle: Text(player.state.track.audio.toString()), onTap: () { Clipboard.setData( ClipboardData( text: "AudioTrack\n${player.state.track.audio}", ), ); }, ), ListTile( title: const Text("VideoTrack"), subtitle: Text(player.state.track.video.toString()), onTap: () { Clipboard.setData( ClipboardData( text: "VideoTrack\n${player.state.track.audio}", ), ); }, ), ListTile( title: const Text("AudioBitrate"), subtitle: Text(player.state.audioBitrate.toString()), onTap: () { Clipboard.setData( ClipboardData( text: "AudioBitrate\n${player.state.audioBitrate}", ), ); }, ), ListTile( title: const Text("Volume"), subtitle: Text(player.state.volume.toString()), onTap: () { Clipboard.setData( ClipboardData( text: "Volume\n${player.state.volume}", ), ); }, ), ], ), ); } @override void onClose() async { Log.w("播放器关闭"); if (smallWindowState.value) { exitSmallWindow(); } disposeStream(); disposeDanmakuController(); await resetSystem(); await player.dispose(); super.onClose(); } } ================================================ FILE: simple_live_app/lib/modules/live_room/player/player_controls.dart ================================================ import 'dart:io'; import 'package:canvas_danmaku/canvas_danmaku.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:media_kit_video/media_kit_video.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/modules/live_room/live_room_controller.dart'; import 'package:simple_live_app/modules/settings/appstyle_settings/appstyle_setting_contorller.dart'; import 'package:simple_live_app/modules/settings/danmu_settings_page.dart'; import 'package:simple_live_app/services/follow_service.dart'; import 'package:simple_live_app/widgets/desktop_refresh_button.dart'; import 'package:simple_live_app/widgets/follow_user_item.dart'; import 'package:window_manager/window_manager.dart'; Widget playerControls( VideoState videoState, LiveRoomController controller, ) { return Obx(() { if (controller.fullScreenState.value) { return buildFullControls( videoState, controller, ); } return buildControls( videoState.context.orientation == Orientation.portrait, videoState, controller, ); }); } Widget buildFullControls( VideoState videoState, LiveRoomController controller, ) { var padding = MediaQuery.of(videoState.context).padding; GlobalKey volumeButtonkey = GlobalKey(); return DragToMoveArea( child: Stack( children: [ Container(), buildDanmuView(videoState, controller), Center( child: // 中间 StreamBuilder( stream: videoState.widget.controller.player.stream.buffering, initialData: videoState.widget.controller.player.state.buffering, builder: (_, s) => Visibility( visible: s.data ?? false, child: const Center( child: CircularProgressIndicator(), ), ), ), ), Positioned.fill( child: GestureDetector( onTap: controller.onTap, onDoubleTapDown: controller.onDoubleTap, onLongPress: () { if (controller.lockControlsState.value) { return; } showFollowUser(controller); }, onVerticalDragStart: controller.onVerticalDragStart, onVerticalDragUpdate: controller.onVerticalDragUpdate, onVerticalDragEnd: controller.onVerticalDragEnd, child: MouseRegion( onHover: (PointerHoverEvent event) { controller.onHover(event, videoState.context); }, child: Container( width: double.infinity, height: double.infinity, color: Colors.transparent, // child: Visibility( // //拖拽区域 // visible: controller.smallWindowState.value, // child: DragToMoveArea( // child: Container( // width: double.infinity, // height: double.infinity, // color: Colors.transparent, // )), // ), ), ), ), ), // 顶部 Obx( () => AnimatedPositioned( left: 0, right: 0, top: (controller.showControlsState.value && !controller.lockControlsState.value) ? 0 : -(48 + padding.top), duration: const Duration(milliseconds: 200), child: Container( height: 48 + padding.top, padding: EdgeInsets.only( left: padding.left + 12, right: padding.right + 12, top: padding.top, ), decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [ Colors.transparent, Colors.black87, ], ), ), child: Row( children: [ IconButton( onPressed: () { if (controller.smallWindowState.value) { controller.exitSmallWindow(); } else { controller.exitFull(); } }, icon: const Icon( Icons.arrow_back, color: Colors.white, size: 24, ), ), AppStyle.hGap12, Expanded( child: Text( "${controller.detail.value?.title} - ${controller.detail.value?.userName}", maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(color: Colors.white, fontSize: 16), ), ), AppStyle.hGap12, IconButton( onPressed: () { controller.saveScreenshot(); }, icon: const Icon( Icons.camera_alt_outlined, color: Colors.white, size: 24, ), ), IconButton( onPressed: () { showFollowUser(controller); }, icon: const Icon( Remix.play_list_2_line, color: Colors.white, size: 24, ), ), Visibility( visible: Platform.isAndroid, child: IconButton( onPressed: () { controller.enablePIP(); }, icon: const Icon( Icons.picture_in_picture, color: Colors.white, size: 24, ), ), ), IconButton( onPressed: () { showPlayerSettings(controller); }, icon: const Icon( Icons.more_horiz, color: Colors.white, size: 24, ), ), ], ), ), ), ), // 底部 Obx( () => AnimatedPositioned( left: 0, right: 0, bottom: (controller.showControlsState.value && !controller.lockControlsState.value) ? 0 : -(80 + padding.bottom), duration: const Duration(milliseconds: 200), child: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.black87, ], ), ), padding: EdgeInsets.only( left: padding.left + 12, right: padding.right + 12, bottom: padding.bottom, ), child: Row( children: [ IconButton( onPressed: () { controller.refreshRoom(); }, icon: const Icon( Remix.refresh_line, color: Colors.white, ), ), Offstage( offstage: controller.showDanmakuState.value, child: IconButton( onPressed: () => controller.showDanmakuState.value = !controller.showDanmakuState.value, icon: const ImageIcon( AssetImage('assets/icons/icon_danmaku_open.png'), size: 24, color: Colors.white, ), ), ), Offstage( offstage: !controller.showDanmakuState.value, child: IconButton( onPressed: () => controller.showDanmakuState.value = !controller.showDanmakuState.value, icon: const ImageIcon( AssetImage('assets/icons/icon_danmaku_close.png'), size: 24, color: Colors.white, ), ), ), IconButton( onPressed: () { showDanmakuSettings(controller); }, icon: const ImageIcon( AssetImage('assets/icons/icon_danmaku_setting.png'), size: 24, color: Colors.white, ), ), const Expanded(child: Center()), Visibility( visible: !Platform.isAndroid && !Platform.isIOS, child: IconButton( key: volumeButtonkey, onPressed: () { controller .showVolumeSlider(volumeButtonkey.currentContext!); }, icon: const Icon( Icons.volume_down, size: 24, color: Colors.white, ), ), ), TextButton( onPressed: () { showQualitesInfo(controller); }, child: Obx( () => Text( controller.currentQualityInfo.value, style: const TextStyle(color: Colors.white, fontSize: 15), ), ), ), TextButton( onPressed: () { showLinesInfo(controller); }, child: Text( controller.currentLineInfo.value, style: const TextStyle(color: Colors.white, fontSize: 15), ), ), IconButton( onPressed: () { if (controller.smallWindowState.value) { controller.exitSmallWindow(); } else { controller.exitFull(); } }, icon: const Icon( Remix.fullscreen_exit_fill, color: Colors.white, ), ), ], ), ), ), ), // 右侧锁定 Obx( () => AnimatedPositioned( top: 0, bottom: 0, right: controller.showControlsState.value ? padding.right + 12 : -(64 + padding.right), duration: const Duration(milliseconds: 200), child: buildLockButton(controller), ), ), // 左侧锁定 Obx( () => AnimatedPositioned( top: 0, bottom: 0, left: controller.showControlsState.value ? padding.left + 12 : -(64 + padding.right), duration: const Duration(milliseconds: 200), child: buildLockButton(controller), ), ), Obx( () => Offstage( offstage: !controller.showGestureTip.value, child: Center( child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey.shade900, borderRadius: BorderRadius.circular(12), ), child: Text( controller.gestureTipText.value, style: const TextStyle(fontSize: 18, color: Colors.white), ), ), ), ), ), ], ), ); } Widget buildLockButton(LiveRoomController controller) { return Center( child: InkWell( onTap: () { controller.setLockState(); }, child: Container( decoration: BoxDecoration( color: Colors.black45, borderRadius: AppStyle.radius8, ), width: 40, height: 40, child: Center( child: Icon( controller.lockControlsState.value ? Icons.lock_outline_rounded : Icons.lock_open_outlined, color: Colors.white, size: 20, ), ), ), ), ); } Widget buildControls( bool isPortrait, VideoState videoState, LiveRoomController controller, ) { GlobalKey volumeButtonkey = GlobalKey(); return Stack( children: [ Container(), buildDanmuView(videoState, controller), // 中间 Center( child: StreamBuilder( stream: videoState.widget.controller.player.stream.buffering, initialData: videoState.widget.controller.player.state.buffering, builder: (_, s) => Visibility( visible: s.data ?? false, child: const Center( child: CircularProgressIndicator(), ), ), ), ), Positioned.fill( child: GestureDetector( onTap: controller.onTap, onDoubleTapDown: controller.onDoubleTap, onVerticalDragStart: controller.onVerticalDragStart, onVerticalDragUpdate: controller.onVerticalDragUpdate, onVerticalDragEnd: controller.onVerticalDragEnd, //onLongPress: controller.showDebugInfo, child: MouseRegion( onEnter: controller.onEnter, child: Container( width: double.infinity, height: double.infinity, color: Colors.transparent, ), ), ), ), Obx( () => AnimatedPositioned( left: 0, right: 0, bottom: controller.showControlsState.value ? 0 : -48, duration: const Duration(milliseconds: 200), child: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.black87, ], ), ), child: Row( children: [ IconButton( onPressed: () { controller.refreshRoom(); }, icon: const Icon( Remix.refresh_line, color: Colors.white, ), ), Offstage( offstage: controller.showDanmakuState.value, child: IconButton( onPressed: () => controller.showDanmakuState.value = !controller.showDanmakuState.value, icon: const ImageIcon( AssetImage('assets/icons/icon_danmaku_open.png'), size: 24, color: Colors.white, ), ), ), Offstage( offstage: !controller.showDanmakuState.value, child: IconButton( onPressed: () => controller.showDanmakuState.value = !controller.showDanmakuState.value, icon: const ImageIcon( AssetImage('assets/icons/icon_danmaku_close.png'), size: 24, color: Colors.white, ), ), ), IconButton( onPressed: () { controller.showDanmuSettingsSheet(); }, icon: const ImageIcon( AssetImage('assets/icons/icon_danmaku_setting.png'), size: 24, color: Colors.white, ), ), const Expanded(child: Center()), Visibility( visible: !Platform.isAndroid && !Platform.isIOS, child: IconButton( key: volumeButtonkey, onPressed: () { controller.showVolumeSlider( volumeButtonkey.currentContext!, ); }, icon: const Icon( Icons.volume_down, size: 24, color: Colors.white, ), ), ), Offstage( offstage: isPortrait, child: TextButton( onPressed: () { controller.showQualitySheet(); }, child: Obx( () => Text( controller.currentQualityInfo.value, style: const TextStyle(color: Colors.white, fontSize: 15), ), ), ), ), Offstage( offstage: isPortrait, child: TextButton( onPressed: () { controller.showPlayUrlsSheet(); }, child: Text( controller.currentLineInfo.value, style: const TextStyle(color: Colors.white, fontSize: 15), ), ), ), Visibility( visible: !Platform.isAndroid && !Platform.isIOS, child: IconButton( onPressed: () { controller.enterSmallWindow(); }, icon: const Icon( Icons.picture_in_picture, color: Colors.white, size: 24, ), ), ), IconButton( onPressed: () { controller.enterFullScreen(); }, icon: const Icon( Remix.fullscreen_line, color: Colors.white, ), ), ], ), ), ), ), Obx( () => Offstage( offstage: !controller.showGestureTip.value, child: Center( child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey.shade900, borderRadius: BorderRadius.circular(12), ), child: Text( controller.gestureTipText.value, style: const TextStyle(fontSize: 18, color: Colors.white), ), ), ), ), ), ], ); } Widget buildDanmuView(VideoState videoState, LiveRoomController controller) { var padding = MediaQuery.of(videoState.context).padding; controller.danmakuView ??= DanmakuScreen( createdController: controller.initDanmakuController, option: DanmakuOption( fontSize: AppSettingsController.instance.danmuSize.value, area: AppSettingsController.instance.danmuArea.value, duration: AppSettingsController.instance.danmuSpeed.value, opacity: AppSettingsController.instance.danmuOpacity.value, strokeWidth: AppSettingsController.instance.danmuStrokeWidth.value, fontWeight: AppSettingsController.instance.danmuFontWeight.value, fontFamily: AppStyleSettingController.instance.curFontName.value, ), ); return Positioned.fill( top: padding.top, bottom: padding.bottom, child: Obx( () => Offstage( offstage: !controller.showDanmakuState.value, child: Padding( padding: controller.fullScreenState.value ? EdgeInsets.only( top: AppSettingsController.instance.danmuTopMargin.value, bottom: AppSettingsController.instance.danmuBottomMargin.value, ) : EdgeInsets.zero, child: controller.danmakuView!, ), ), ), ); } void showLinesInfo(LiveRoomController controller) { if (controller.isVertical.value) { controller.showPlayUrlsSheet(); return; } Utils.showRightDialog( title: "线路", useSystem: true, child: ListView.builder( padding: EdgeInsets.zero, itemCount: controller.playUrls.length, itemBuilder: (_, i) { return ListTile( selected: controller.currentLineIndex == i, title: Text.rich( TextSpan( text: "线路${i + 1}", children: [ WidgetSpan( child: Container( decoration: BoxDecoration( borderRadius: AppStyle.radius4, border: Border.all( color: Colors.grey, ), ), padding: AppStyle.edgeInsetsH4, margin: AppStyle.edgeInsetsL8, child: Text( controller.playUrls[i].contains(".flv") ? "FLV" : "HLS", style: const TextStyle( fontSize: 12, ), ), )), ], ), style: const TextStyle(fontSize: 14), ), minLeadingWidth: 16, onTap: () { Utils.hideRightDialog(); //controller.currentLineIndex = i; //controller.setPlayer(); controller.changePlayLine(i); }, ); }, ), ); } void showQualitesInfo(LiveRoomController controller) { if (controller.isVertical.value) { controller.showQualitySheet(); return; } Utils.showRightDialog( title: "清晰度", useSystem: true, child: ListView.builder( padding: EdgeInsets.zero, itemCount: controller.qualites.length, itemBuilder: (_, i) { var item = controller.qualites[i]; return ListTile( selected: controller.currentQuality == i, title: Text( item.quality, style: const TextStyle(fontSize: 14), ), minLeadingWidth: 16, onTap: () { Utils.hideRightDialog(); controller.currentQuality = i; controller.getPlayUrl(); }, ); }, ), ); } void showDanmakuSettings(LiveRoomController controller) { if (controller.isVertical.value) { controller.showDanmuSettingsSheet(); return; } Utils.showRightDialog( title: "弹幕设置", width: 400, useSystem: true, child: ListView( padding: AppStyle.edgeInsetsA12, children: [ DanmuSettingsView( danmakuController: controller.danmakuController, ), ], ), ); } void showPlayerSettings(LiveRoomController controller) { if (controller.isVertical.value) { controller.showPlayerSettingsSheet(); return; } Utils.showRightDialog( title: "设置", width: 320, useSystem: true, child: Obx( () => ListView( padding: AppStyle.edgeInsetsV12, children: [ Padding( padding: AppStyle.edgeInsetsH16, child: Text( "画面尺寸", style: Get.textTheme.titleMedium, ), ), RadioGroup( groupValue: AppSettingsController.instance.scaleMode.value, onChanged: (e) { AppSettingsController.instance.setScaleMode(e ?? 0); controller.updateScaleMode(); }, child: Column( children: [ RadioListTile( value: 0, contentPadding: AppStyle.edgeInsetsH4, title: const Text("适应"), visualDensity: VisualDensity.compact, ), RadioListTile( value: 1, contentPadding: AppStyle.edgeInsetsH4, title: const Text("拉伸"), visualDensity: VisualDensity.compact, ), RadioListTile( value: 2, contentPadding: AppStyle.edgeInsetsH4, title: const Text("铺满"), visualDensity: VisualDensity.compact, ), RadioListTile( value: 3, contentPadding: AppStyle.edgeInsetsH4, title: const Text("16:9"), visualDensity: VisualDensity.compact, ), RadioListTile( value: 4, contentPadding: AppStyle.edgeInsetsH4, title: const Text("4:3"), visualDensity: VisualDensity.compact, ), ], ), ), ], ), ), ); } void showFollowUser(LiveRoomController controller) { if (controller.isVertical.value) { controller.showFollowUserSheet(); return; } Utils.showRightDialog( title: "关注列表", width: 400, useSystem: true, child: Obx( () => Stack( children: [ RefreshIndicator( onRefresh: FollowService.instance.loadData, child: ListView.builder( itemCount: FollowService.instance.liveList.length, itemBuilder: (_, i) { var item = FollowService.instance.liveList[i]; return Obx( () => FollowUserItem( item: item, playing: controller.rxSite.value.id == item.siteId && controller.rxRoomId.value == item.roomId, onTap: () { Utils.hideRightDialog(); controller.resetRoom( Sites.allSites[item.siteId]!, item.roomId, ); }, ), ); }, ), ), if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) Positioned( right: 12, bottom: 12, child: Obx( () => DesktopRefreshButton( refreshing: FollowService.instance.updating.value, onPressed: FollowService.instance.loadData, ), ), ), ], ), ), ); } ================================================ FILE: simple_live_app/lib/modules/mine/account/account_controller.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/routes/route_path.dart'; import 'package:simple_live_app/services/bilibili_account_service.dart'; import 'package:simple_live_app/services/douyin_account_service.dart'; class AccountController extends GetxController { void bilibiliTap() async { if (BiliBiliAccountService.instance.logined.value) { var result = await Utils.showAlertDialog("确定要退出哔哩哔哩账号吗?", title: "退出登录"); if (result) { BiliBiliAccountService.instance.logout(); } } else { //AppNavigator.toBiliBiliLogin(); bilibiliLogin(); } } void bilibiliLogin() { Utils.showBottomSheet( title: "登录哔哩哔哩", child: Column( mainAxisSize: MainAxisSize.min, children: [ Visibility( visible: Platform.isAndroid || Platform.isIOS, child: ListTile( leading: const Icon(Icons.account_circle_outlined), title: const Text("Web登录"), subtitle: const Text("填写用户名密码登录"), trailing: const Icon(Icons.chevron_right), onTap: () { Get.back(); Get.toNamed(RoutePath.kBiliBiliWebLogin); }, ), ), ListTile( leading: const Icon(Icons.qr_code), title: const Text("扫码登录"), subtitle: const Text("使用哔哩哔哩APP扫描二维码登录"), trailing: const Icon(Icons.chevron_right), onTap: () { Get.back(); Get.toNamed(RoutePath.kBiliBiliQRLogin); }, ), ListTile( leading: const Icon(Icons.edit_outlined), title: const Text("Cookie登录"), subtitle: const Text("手动输入Cookie登录"), trailing: const Icon(Icons.chevron_right), onTap: () { Get.back(); doCookieLogin(); }, ), ], ), ); } void doCookieLogin() async { var cookie = await Utils.showEditTextDialog( "", title: "请输入Cookie", hintText: "请输入Cookie", ); if (cookie == null || cookie.isEmpty) { return; } BiliBiliAccountService.instance.setCookie(cookie); await BiliBiliAccountService.instance.loadUserInfo(); } // 需要用户手动复制抖音的Cookie void douyinTap() async { if (DouyinAccountService.instance.logined.value) { var result = await Utils.showAlertDialog("确定要清除抖音Cookie吗?", title: "清除Cookie"); if (result) { DouyinAccountService.instance.logout(); } } else { final cookie = await Utils.showEditTextDialog( "", title: "请输入抖音Cookie", hintText: "__ac_nonce=...;__ac_signature=...;sessionid=...;", ); if (cookie == null || cookie.isEmpty) return; DouyinAccountService.instance.setCookie(cookie); // 检查输入的cookie是否有效 await DouyinAccountService.instance.loadUserInfo(); } } } ================================================ FILE: simple_live_app/lib/modules/mine/account/account_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/modules/mine/account/account_controller.dart'; import 'package:simple_live_app/services/bilibili_account_service.dart'; import 'package:simple_live_app/services/douyin_account_service.dart'; class AccountPage extends GetView { const AccountPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("账号管理"), ), body: ListView( children: [ const Padding( padding: AppStyle.edgeInsetsA12, child: Text( "哔哩哔哩账号需要登录才能看高清晰度的直播,其他平台暂无此限制。", textAlign: TextAlign.center, ), ), Obx( () => ListTile( leading: Image.asset( 'assets/images/bilibili_2.png', width: 36, height: 36, ), title: const Text("哔哩哔哩"), subtitle: Text(BiliBiliAccountService.instance.name.value), trailing: BiliBiliAccountService.instance.logined.value ? const Icon(Icons.logout) : const Icon(Icons.chevron_right), onTap: controller.bilibiliTap, ), ), ListTile( leading: Image.asset( 'assets/images/douyu.png', width: 36, height: 36, ), title: const Text("斗鱼直播"), subtitle: const Text("无需登录"), enabled: false, trailing: const Icon(Icons.chevron_right), ), ListTile( leading: Image.asset( 'assets/images/huya.png', width: 36, height: 36, ), title: const Text("虎牙直播"), subtitle: const Text("无需登录"), enabled: false, trailing: const Icon(Icons.chevron_right), ), Obx( () => ListTile( leading: Image.asset( 'assets/images/douyin.png', width: 36, height: 36, ), title: const Text("抖音直播"), subtitle: Text(DouyinAccountService.instance.name.value), trailing: DouyinAccountService.instance.logined.value ? const Icon(Icons.logout) : const Icon(Icons.chevron_right), onTap: controller.douyinTap, ), ), ], ), ); } } ================================================ FILE: simple_live_app/lib/modules/mine/account/bilibili/qr_login_controller.dart ================================================ import 'dart:async'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/requests/http_client.dart'; import 'package:simple_live_app/services/bilibili_account_service.dart'; enum QRStatus { loading, unscanned, scanned, expired, failed, } class BiliBiliQRLoginController extends GetxController { @override void onInit() { loadQRCode(); super.onInit(); } Timer? timer; var qrcodeUrl = "".obs; var qrcodeKey = ""; /// 二维码状态 /// - [0] 加载中 /// - [1] 未扫描 /// - [2] 已扫描,待确认 /// - [3] 二维码已经失效 /// - [4] 登录失败 Rx qrStatus = QRStatus.loading.obs; void loadQRCode() async { try { qrStatus.value = QRStatus.loading; var result = await HttpClient.instance.getJson( "https://passport.bilibili.com/x/passport-login/web/qrcode/generate", ); if (result["code"] != 0) { throw result["message"]; } qrcodeKey = result["data"]["qrcode_key"]; qrcodeUrl.value = result["data"]["url"]; qrStatus.value = QRStatus.unscanned; startPoll(); } catch (e) { Log.logPrint(e); SmartDialog.showToast(e.toString()); qrStatus.value = QRStatus.failed; } } void startPoll() { timer = Timer.periodic( const Duration(seconds: 3), (timer) { pollQRStatus(); }, ); } void pollQRStatus() async { try { var response = await HttpClient.instance.get( "https://passport.bilibili.com/x/passport-login/web/qrcode/poll", queryParameters: { "qrcode_key": qrcodeKey, }, ); if (response.data["code"] != 0) { throw response.data["message"]; } var data = response.data["data"]; var code = data["code"]; if (code == 0) { var cookies = []; response.headers["set-cookie"]?.forEach((element) { var cookie = element.split(";")[0]; cookies.add(cookie); }); if (cookies.isNotEmpty) { var cookieStr = cookies.join(";"); Log.i(cookieStr); BiliBiliAccountService.instance.setCookie(cookieStr); await BiliBiliAccountService.instance.loadUserInfo(); Get.back(); } } else if (code == 86038) { qrStatus.value = QRStatus.expired; qrcodeKey = ""; timer?.cancel(); } else if (code == 86090) { qrStatus.value = QRStatus.scanned; } } catch (e) { Log.logPrint(e); SmartDialog.showToast(e.toString()); } } @override void onClose() { timer?.cancel(); super.onClose(); } } ================================================ FILE: simple_live_app/lib/modules/mine/account/bilibili/qr_login_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/modules/mine/account/bilibili/qr_login_controller.dart'; class BiliBiliQRLoginPage extends GetView { const BiliBiliQRLoginPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("哔哩哔哩账号登录")), body: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.center, children: [ Center( child: Obx( () { if (controller.qrStatus.value == QRStatus.loading) { return const CircularProgressIndicator(); } if (controller.qrStatus.value == QRStatus.failed) { return Column( mainAxisSize: MainAxisSize.min, children: [ const Text("二维码加载失败"), TextButton( onPressed: controller.loadQRCode, child: const Text("重试"), ), ], ); } if (controller.qrStatus.value == QRStatus.failed) { return Column( mainAxisSize: MainAxisSize.min, children: [ const Text("二维码已失效"), TextButton( onPressed: controller.loadQRCode, child: const Text("刷新二维码"), ), ], ); } return Column( children: [ ClipRRect( borderRadius: AppStyle.radius12, child: QrImageView( data: controller.qrcodeUrl.value, version: QrVersions.auto, backgroundColor: Colors.white, size: 200.0, padding: AppStyle.edgeInsetsA12, ), ), AppStyle.vGap8, Visibility( visible: controller.qrStatus.value == QRStatus.scanned, child: const Text("已扫描,请在手机上确认登录"), ), ], ); }, ), ), const Padding( padding: AppStyle.edgeInsetsA24, child: Text( "请使用哔哩哔哩手机客户端扫描二维码登录", textAlign: TextAlign.center, ), ), ], ), ); } } ================================================ FILE: simple_live_app/lib/modules/mine/account/bilibili/web_login_controller.dart ================================================ import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/routes/route_path.dart'; import 'package:simple_live_app/services/bilibili_account_service.dart'; class BiliBiliWebLoginController extends BaseController { InAppWebViewController? webViewController; final CookieManager cookieManager = CookieManager.instance(); void onWebViewCreated(InAppWebViewController controller) { webViewController = controller; webViewController!.loadUrl( urlRequest: URLRequest( url: WebUri("https://passport.bilibili.com/login"), ), ); } void toQRLogin() async { await Get.toNamed(RoutePath.kBiliBiliQRLogin); Get.back(); } void onLoadStop(InAppWebViewController controller, Uri? uri) async { if (uri == null) { return; } if (uri.host == "m.bilibili.com") { logined(); } } Future logined() async { try { var cookies = await cookieManager.getCookies(url: WebUri("https://bilibili.com")); if (cookies.isEmpty) { return false; } var cookieStr = cookies.map((e) => "${e.name}=${e.value}").join(";"); Log.i(cookieStr); BiliBiliAccountService.instance.setCookie(cookieStr); await BiliBiliAccountService.instance.loadUserInfo(); Get.back(); return true; } catch (e) { return false; } } } ================================================ FILE: simple_live_app/lib/modules/mine/account/bilibili/web_login_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/modules/mine/account/bilibili/web_login_controller.dart'; class BiliBiliWebLoginPage extends GetView { const BiliBiliWebLoginPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("哔哩哔哩账号登录"), actions: [ TextButton.icon( onPressed: controller.toQRLogin, icon: const Icon(Icons.qr_code), label: const Text("二维码登录"), ), ], ), body: InAppWebView( onWebViewCreated: controller.onWebViewCreated, onLoadStop: controller.onLoadStop, initialSettings: InAppWebViewSettings( userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/118.0.0.0", useShouldOverrideUrlLoading: true, ), shouldOverrideUrlLoading: (webController, navigationAction) async { var uri = navigationAction.request.url; if (uri == null) { return NavigationActionPolicy.ALLOW; } if (uri.host == "m.bilibili.com" || uri.host == "www.bilibili.com") { await controller.logined(); return NavigationActionPolicy.CANCEL; } return NavigationActionPolicy.ALLOW; }, ), ); } } ================================================ FILE: simple_live_app/lib/modules/mine/history/history_controller.dart ================================================ import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/models/db/history.dart'; import 'package:simple_live_app/services/db_service.dart'; class HistoryController extends BasePageController { @override Future> getData(int page, int pageSize) { if (page > 1) { return Future.value([]); } return Future.value(DBService.instance.getHistories()); } void clean() async { var result = await Utils.showAlertDialog("确定要清空观看记录吗?", title: "清空观看记录"); if (!result) { return; } await DBService.instance.historyBox.clear(); refreshData(); } void removeItem(History item) async { await DBService.instance.historyBox.delete(item.id); refreshData(); } } ================================================ FILE: simple_live_app/lib/modules/mine/history/history_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/modules/mine/history/history_controller.dart'; import 'package:simple_live_app/routes/app_navigation.dart'; import 'package:simple_live_app/widgets/net_image.dart'; import 'package:simple_live_app/widgets/page_grid_view.dart'; class HistoryPage extends GetView { const HistoryPage({super.key}); @override Widget build(BuildContext context) { var rowCount = MediaQuery.of(context).size.width ~/ 500; if (rowCount < 1) rowCount = 1; return Scaffold( appBar: AppBar( title: const Text("观看记录"), actions: [ TextButton.icon( onPressed: controller.clean, icon: const Icon(Icons.delete_outline), label: const Text("清空"), ), ], ), body: PageGridView( crossAxisSpacing: 12, crossAxisCount: rowCount, pageController: controller, firstRefresh: true, itemBuilder: (_, i) { var item = controller.list[i]; var site = Sites.allSites[item.siteId]!; return Dismissible( key: ValueKey(item.id), direction: DismissDirection.endToStart, background: Container( color: Colors.red, padding: AppStyle.edgeInsetsA12, alignment: Alignment.centerRight, child: const Icon( Icons.delete, color: Colors.white, ), ), confirmDismiss: (direction) async { return await Utils.showAlertDialog("确定要删除此记录吗?", title: "删除记录"); }, onDismissed: (_) { controller.removeItem(item); }, child: ListTile( leading: NetImage( item.face, width: 48, height: 48, borderRadius: 24, ), title: Text(item.userName), subtitle: Row( children: [ Expanded( child: Row( children: [ Image.asset( site.logo, width: 20, ), AppStyle.hGap4, Text( site.name, style: const TextStyle( fontSize: 12, color: Colors.grey, ), ), ], ), ), Text( Utils.parseTime(item.updateTime), style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], ), onTap: () { AppNavigator.toLiveRoomDetail(site: site, roomId: item.roomId); }, onLongPress: () async { var result = await Utils.showAlertDialog("确定要删除此记录吗?", title: "删除记录"); if (!result) { return; } controller.removeItem(item); }, ), ); }, ), ); } } ================================================ FILE: simple_live_app/lib/modules/mine/mine_page.dart ================================================ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/routes/route_path.dart'; import 'package:simple_live_app/services/signalr_service.dart'; import 'package:url_launcher/url_launcher_string.dart'; class MinePage extends StatelessWidget { const MinePage({super.key}); @override Widget build(BuildContext context) { return AnnotatedRegion( value: Get.isDarkMode ? SystemUiOverlayStyle.light.copyWith( systemNavigationBarColor: Colors.transparent, ) : SystemUiOverlayStyle.dark.copyWith( systemNavigationBarColor: Colors.transparent, ), child: SafeArea( child: ListView( padding: AppStyle.edgeInsetsA4, children: [ AppStyle.vGap12, ListTile( leading: Image.asset( 'assets/images/logo.png', width: 56, height: 56, ), title: const Text( "Slive", style: TextStyle(height: 1.0), ), subtitle: const Text("我就默默看你表演"), trailing: const Icon(Icons.chevron_right), onTap: () { Get.dialog(AboutDialog( applicationIcon: Image.asset( 'assets/images/logo.png', width: 48, height: 48, ), applicationName: "Slive", applicationVersion: "我就默默看你表演", applicationLegalese: "Ver ${Utils.packageInfo.version}", )); }, ), Divider( indent: 12, endIndent: 12, color: Colors.grey.withAlpha(25), ), _buildCard( context, children: [ ListTile( leading: const Icon(Remix.history_line), title: const Text("观看记录"), trailing: const Icon( Icons.chevron_right, color: Colors.grey, ), onTap: () { Get.toNamed(RoutePath.kHistory); }, ), ], ), Divider( indent: 12, endIndent: 12, color: Colors.grey.withAlpha(25), ), ListTile( leading: const Icon(Remix.account_circle_line), title: const Text("账号管理"), trailing: const Icon( Icons.chevron_right, color: Colors.grey, ), onTap: () { Get.toNamed(RoutePath.kSettingsAccount); }, ), Divider( indent: 12, endIndent: 12, color: Colors.grey.withAlpha(25), ), ListTile( leading: const Icon(Icons.devices), title: const Text("数据同步"), trailing: const Icon( Icons.chevron_right, color: Colors.grey, ), onTap: () { Get.toNamed(RoutePath.kSync); }, ), Divider( indent: 12, endIndent: 12, color: Colors.grey.withAlpha(25), ), ListTile( leading: const Icon(Remix.link), title: const Text("链接解析"), trailing: const Icon( Icons.chevron_right, color: Colors.grey, ), onTap: () { Get.toNamed(RoutePath.kTools); }, ), Divider( indent: 12, endIndent: 12, color: Colors.grey.withAlpha(25), ), _buildCard( context, children: [ ListTile( leading: const Icon(Remix.moon_line), title: const Text("外观设置"), trailing: const Icon( Icons.chevron_right, color: Colors.grey, ), onTap: () { Get.toNamed(RoutePath.kAppstyleSetting); }, ), ListTile( leading: const Icon(Remix.home_2_line), title: const Text("主页设置"), trailing: const Icon( Icons.chevron_right, color: Colors.grey, ), onTap: () { Get.toNamed(RoutePath.kSettingsIndexed); }, ), ListTile( leading: const Icon(Remix.play_circle_line), title: const Text("直播设置"), trailing: const Icon( Icons.chevron_right, color: Colors.grey, ), onTap: () { Get.toNamed(RoutePath.kSettingsPlay); }, ), ListTile( leading: const Icon(Remix.text), title: const Text("弹幕设置"), trailing: const Icon( Icons.chevron_right, color: Colors.grey, ), onTap: () { Get.toNamed(RoutePath.kSettingsDanmu); }, ), ListTile( leading: const Icon(Remix.timer_2_line), title: const Text("定时关闭"), trailing: const Icon( Icons.chevron_right, color: Colors.grey, ), onTap: () { Get.toNamed(RoutePath.kSettingsAutoExit); }, ), ListTile( leading: const Icon(Remix.apps_line), title: const Text("其他设置"), trailing: const Icon( Icons.chevron_right, color: Colors.grey, ), onTap: () { Get.toNamed(RoutePath.kSettingsOther); }, ), if (kDebugMode) ListTile( leading: const Icon(Remix.apps_line), title: const Text("测试"), trailing: const Icon( Icons.chevron_right, color: Colors.grey, ), onTap: () async { SignalRService signalRService = SignalRService(); await signalRService.connect(); //Get.toNamed(RoutePath.kTest); var room = await signalRService.createRoom(); Log.logPrint(room); }, ), ], ), Divider( indent: 12, endIndent: 12, color: Colors.grey.withAlpha(25), ), _buildCard( context, children: [ const ListTile( leading: Icon(Remix.error_warning_line), title: Text("免责声明"), trailing: Icon( Icons.chevron_right, color: Colors.grey, ), onTap: Utils.showStatement, ), ListTile( leading: const Icon(Remix.github_line), title: const Text("开源主页"), trailing: const Icon( Icons.chevron_right, color: Colors.grey, ), onTap: () { launchUrlString( "https://github.com/slotsun/dart_simple_live", mode: LaunchMode.externalApplication, ); }, ), ListTile( leading: const Icon(Remix.upload_2_line), title: const Text("检查更新"), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ Text("Ver ${Utils.packageInfo.version}"), AppStyle.hGap4, const Icon( Icons.chevron_right, color: Colors.grey, ), ], ), onTap: () { Utils.checkUpdate(showMsg: true); }, ), ], ), ], ), ), ); } Widget _buildCard(BuildContext context, {required List children}) { return Theme( data: Theme.of(context).copyWith( listTileTheme: ListTileThemeData( shape: RoundedRectangleBorder(borderRadius: AppStyle.radius8), ), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: children, ), ); } } ================================================ FILE: simple_live_app/lib/modules/mine/parse/parse_controller.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/app/utils/url_parse.dart'; import 'package:simple_live_app/routes/app_navigation.dart'; class ParseController extends GetxController { final TextEditingController roomJumpToController = TextEditingController(); final TextEditingController getUrlController = TextEditingController(); void jumpToRoom(String e) async { if (e.isEmpty) { SmartDialog.showToast("链接不能为空"); return; } // 隐藏键盘 FocusManager.instance.primaryFocus?.unfocus(); var parseResult = await UrlParse.instance.parse(e); if (parseResult.isEmpty || parseResult.first == "") { SmartDialog.showToast("无法解析此链接"); return; } // 延迟200ms跳转,等待键盘隐藏 Future.delayed(const Duration(milliseconds: 200), () { Site site = parseResult[1]; AppNavigator.toLiveRoomDetail(site: site, roomId: parseResult.first); }); } void getPlayUrl(String e) async { if (e.isEmpty) { SmartDialog.showToast("链接不能为空"); return; } var parseResult = await UrlParse.instance.parse(e); if (parseResult.isEmpty && parseResult.first == "") { SmartDialog.showToast("无法解析此链接"); return; } Site site = parseResult[1]; try { SmartDialog.showLoading(msg: ""); var detail = await site.liveSite.getRoomDetail(roomId: parseResult.first); var qualites = await site.liveSite.getPlayQualites(detail: detail); SmartDialog.dismiss(status: SmartStatus.loading); if (qualites.isEmpty) { SmartDialog.showToast("读取直链失败,无法读取清晰度"); return; } var result = await Get.dialog(SimpleDialog( title: const Text("选择清晰度"), children: qualites .map( (e) => ListTile( title: Text( e.quality, textAlign: TextAlign.center, ), onTap: () { Get.back(result: e); }, ), ) .toList(), )); if (result == null) { return; } SmartDialog.showLoading(msg: ""); var playUrl = await site.liveSite.getPlayUrls(detail: detail, quality: result); SmartDialog.dismiss(status: SmartStatus.loading); await Get.dialog(SimpleDialog( title: const Text("选择线路"), children: playUrl.urls .map( (e) => ListTile( title: Text( "线路${playUrl.urls.indexOf(e) + 1}", ), subtitle: Text( e, maxLines: 1, overflow: TextOverflow.ellipsis, ), onTap: () { Clipboard.setData(ClipboardData(text: e)); Get.back(); SmartDialog.showToast("已复制直链"); }, ), ) .toList(), )); } catch (e) { SmartDialog.showToast("读取直链失败"); } finally { SmartDialog.dismiss(status: SmartStatus.loading); } } } ================================================ FILE: simple_live_app/lib/modules/mine/parse/parse_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/modules/mine/parse/parse_controller.dart'; class ParsePage extends GetView { const ParsePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("链接解析"), ), body: ListView( padding: AppStyle.edgeInsetsA12, children: [ buildCard( context: context, child: ExpansionTile( title: const Text("直播间跳转"), childrenPadding: AppStyle.edgeInsetsH12, initiallyExpanded: true, children: [ TextField( minLines: 3, maxLines: 3, controller: controller.roomJumpToController, textInputAction: TextInputAction.go, decoration: InputDecoration( border: const OutlineInputBorder(), hintText: "输入或粘贴哔哩哔哩直播/虎牙直播/斗鱼直播/抖音直播的链接", contentPadding: AppStyle.edgeInsetsA12, enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.grey.withAlpha(50), ), ), ), onSubmitted: controller.jumpToRoom, ), Container( margin: AppStyle.edgeInsetsB4, width: double.infinity, child: TextButton.icon( onPressed: () { controller .jumpToRoom(controller.roomJumpToController.text); }, icon: const Icon(Remix.play_circle_line), label: const Text("链接跳转"), ), ), ], ), ), buildCard( context: context, child: ExpansionTile( title: const Text("获取直链"), childrenPadding: AppStyle.edgeInsetsH12, initiallyExpanded: true, children: [ TextField( minLines: 3, maxLines: 3, controller: controller.getUrlController, textInputAction: TextInputAction.go, decoration: InputDecoration( border: const OutlineInputBorder(), hintText: "输入或粘贴哔哩哔哩直播/虎牙直播/斗鱼直播/抖音直播的链接", contentPadding: AppStyle.edgeInsetsA12, enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.grey.withAlpha(50), ), ), ), onSubmitted: controller.getPlayUrl, ), Container( margin: AppStyle.edgeInsetsB4, width: double.infinity, child: TextButton.icon( onPressed: () { controller.getPlayUrl(controller.getUrlController.text); }, icon: const Icon(Remix.link), label: const Text("获取直链"), ), ), ], ), ), const Padding( padding: AppStyle.edgeInsetsV12, child: SelectableText('''支持以下类型的链接解析: 哔哩哔哩: https://live.bilibili.com/xxxxx https://b23.tv/xxxxx 虎牙直播: https://www.huya.com/xxxxx 斗鱼直播: https://www.douyu.com/xxxxx https://www.douyu.com/topic/xxxx 抖音直播: https://live.douyin.com/xxxxx https://webcast.amemv.com/douyin/webcast/reflow/xxxxx ''', style: TextStyle(color: Colors.grey)), ), ], ), ); } Widget buildCard({required BuildContext context, required Widget child}) { return Container( decoration: BoxDecoration( color: Theme.of(context).cardColor, borderRadius: AppStyle.radius8, boxShadow: Get.isDarkMode ? [] : [ BoxShadow( blurRadius: 8, color: Colors.grey.withAlpha(50), ) ], ), margin: AppStyle.edgeInsetsB12, child: Theme( data: Theme.of(context).copyWith( dividerColor: Colors.transparent, ), child: child, ), ); } } ================================================ FILE: simple_live_app/lib/modules/other/debug_log_page.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/log.dart'; class DebugLogPage extends StatelessWidget { const DebugLogPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Log"), actions: [ IconButton( onPressed: () async { var msg = Log.debugLogs .map((x) => "${x.datetime}\r\n${x.content}") .join('\r\n\r\n'); var dir = await getApplicationDocumentsDirectory(); var logFile = File( '${dir.path}/${DateTime.now().millisecondsSinceEpoch}.log'); await logFile.writeAsString(msg); SharePlus.instance .share(ShareParams(files: [XFile(logFile.path)])); }, icon: const Icon(Icons.save), ), IconButton( onPressed: () { Log.debugLogs.clear(); }, icon: const Icon(Icons.clear_all), ), ], ), body: Obx( () => ListView.separated( itemCount: Log.debugLogs.length, separatorBuilder: (_, i) => const Divider(), padding: AppStyle.edgeInsetsA12, itemBuilder: (_, i) { var item = Log.debugLogs[i]; return SelectableText( "${item.datetime.toString()}\r\n${item.content}", style: TextStyle( color: item.color, fontSize: 12, ), ); }, ), ), ); } } ================================================ FILE: simple_live_app/lib/modules/search/douyin/douyin_search_controller.dart ================================================ import 'dart:io'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/routes/app_navigation.dart'; import 'package:simple_live_app/routes/route_path.dart'; import 'package:simple_live_core/simple_live_core.dart'; import 'package:url_launcher/url_launcher_string.dart'; class DouyinSearchController extends BaseController { InAppWebViewController? webViewController; void onWebViewCreated(InAppWebViewController controller) { webViewController = controller; } RxList list = [].obs; String keyword = ""; /// 搜索模式,0=直播间,1=主播 var searchMode = 0.obs; final Site site; DouyinSearchController( this.site, ); var searchUrl = "https://www.douyin.com/search/dnf?type=live"; void reloadWebView() { if (keyword.isEmpty) { return; } searchUrl = "https://www.douyin.com/search/${Uri.encodeComponent(keyword)}?type=live"; if (Platform.isAndroid || Platform.isIOS) { webViewController!.loadUrl( urlRequest: URLRequest( url: WebUri(searchUrl), ), ); } } void onLoadStop(InAppWebViewController controller, Uri? uri) async { pageLoadding.value = false; } void onLoadStart(InAppWebViewController controller, Uri? uri) async { pageLoadding.value = true; } Future onCreateWindow(InAppWebViewController controller, CreateWindowAction createWindowAction) async { if (createWindowAction.request.url?.host == "live.douyin.com") { { var regExp = RegExp(r"live\.douyin\.com/([\d|\w]+)"); var id = regExp .firstMatch(createWindowAction.request.url.toString()) ?.group(1) ?? ""; AppNavigator.toLiveRoomDetail(site: site, roomId: id); return false; } } return false; } void openBrowser() { launchUrlString(searchUrl); Get.offAndToNamed(RoutePath.kTools); } } ================================================ FILE: simple_live_app/lib/modules/search/douyin/douyin_search_view.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/modules/search/douyin/douyin_search_controller.dart'; import 'package:simple_live_app/routes/app_navigation.dart'; import 'package:simple_live_app/widgets/keep_alive_wrapper.dart'; import 'package:simple_live_app/widgets/status/app_loadding_widget.dart'; class DouyinSearchView extends StatelessWidget { const DouyinSearchView({super.key}); DouyinSearchController get controller => Get.find(); @override Widget build(BuildContext context) { var roomRowCount = MediaQuery.of(context).size.width ~/ 200; if (roomRowCount < 2) roomRowCount = 2; var userRowCount = MediaQuery.of(context).size.width ~/ 500; if (userRowCount < 1) userRowCount = 1; return KeepAliveWrapper( child: Stack( children: [ SizedBox( width: double.infinity, height: double.infinity, child: Center( child: Padding( padding: AppStyle.edgeInsetsA12, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ const Text( "暂不支持抖音搜索,请打开浏览器搜索,然后复制直播间链接进行解析", textAlign: TextAlign.center, ), TextButton.icon( onPressed: controller.openBrowser, icon: const Icon(Icons.open_in_browser), label: const Text("打开浏览器"), ), ], ), ), ), ), if (Platform.isAndroid || Platform.isIOS) InAppWebView( onWebViewCreated: controller.onWebViewCreated, onLoadStop: controller.onLoadStop, onLoadStart: controller.onLoadStart, initialSettings: InAppWebViewSettings( useOnLoadResource: true, userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/118.0.0.0", useShouldOverrideUrlLoading: true, ), onCreateWindow: controller.onCreateWindow, shouldOverrideUrlLoading: (webController, navigationAction) async { var uri = navigationAction.request.url; if (uri == null) { return NavigationActionPolicy.ALLOW; } if (uri.host == "live.douyin.com") { var regExp = RegExp(r"live\.douyin\.com/([\d|\w]+)"); var id = regExp.firstMatch(uri.toString())?.group(1) ?? ""; AppNavigator.toLiveRoomDetail( site: controller.site, roomId: id); return NavigationActionPolicy.CANCEL; } return NavigationActionPolicy.ALLOW; }, ), Obx( () => Visibility( visible: controller.pageLoadding.value, child: const AppLoaddingWidget(), ), ), ], ), ); } } ================================================ FILE: simple_live_app/lib/modules/search/search_controller.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/modules/search/search_list_controller.dart'; class AppSearchController extends GetxController with GetSingleTickerProviderStateMixin { late TabController tabController; int index = 0; var searchMode = 0.obs; AppSearchController() { tabController = TabController(length: Sites.supportSites.length, vsync: this); tabController.animation?.addListener(() { var currentIndex = (tabController.animation?.value ?? 0).round(); if (index == currentIndex) { return; } index = currentIndex; // if (Sites.supportSites[index].id == Constant.kDouyin) { // return; // } var controller = Get.find(tag: Sites.supportSites[index].id); if (controller.list.isEmpty && !controller.pageEmpty.value && controller.keyword.isNotEmpty) { controller.refreshData(); } }); } StreamSubscription? streamSubscription; TextEditingController searchController = TextEditingController(); @override void onInit() { for (var site in Sites.supportSites) { // if (site.id == Constant.kDouyin) { // Get.put(DouyinSearchController(site)); // } else { Get.put( SearchListController(site), tag: site.id, ); //} } super.onInit(); } void doSearch() { if (searchController.text.isEmpty) { return; } for (var site in Sites.supportSites) { // if (site.id == Constant.kDouyin) { // var controller = Get.find(); // controller.keyword = searchController.text; // controller.searchMode.value = searchMode.value; // controller.reloadWebView(); // } else { var controller = Get.find(tag: site.id); controller.clear(); controller.keyword = searchController.text; controller.searchMode.value = searchMode.value; //} } // if (Sites.supportSites[index].id != Constant.kDouyin) { var controller = Get.find(tag: Sites.supportSites[index].id); controller.refreshData(); //} } @override void onClose() { streamSubscription?.cancel(); super.onClose(); } } ================================================ FILE: simple_live_app/lib/modules/search/search_list_controller.dart ================================================ import 'package:get/get.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/sites.dart'; class SearchListController extends BasePageController { String keyword = ""; /// 搜索模式,0=直播间,1=主播 var searchMode = 0.obs; final Site site; SearchListController( this.site, ); @override Future refreshData() async { if (keyword.isEmpty) { return; } return await super.refreshData(); } @override Future getData(int page, int pageSize) async { if (keyword.isEmpty) { return []; } if (searchMode.value == 1) { // 搜索主播 var result = await site.liveSite.searchAnchors(keyword, page: page); return result.items; } var result = await site.liveSite.searchRooms(keyword, page: page); return result.items; } void clear() { pageEmpty.value = false; list.clear(); } } ================================================ FILE: simple_live_app/lib/modules/search/search_list_view.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/modules/search/search_list_controller.dart'; import 'package:simple_live_app/routes/app_navigation.dart'; import 'package:simple_live_app/widgets/keep_alive_wrapper.dart'; import 'package:simple_live_app/widgets/live_room_card.dart'; import 'package:simple_live_app/widgets/net_image.dart'; import 'package:simple_live_app/widgets/page_grid_view.dart'; import 'package:simple_live_core/simple_live_core.dart'; class SearchListView extends StatelessWidget { final String tag; const SearchListView(this.tag, {super.key}); SearchListController get controller => Get.find(tag: tag); @override Widget build(BuildContext context) { var roomRowCount = MediaQuery.of(context).size.width ~/ 200; if (roomRowCount < 2) roomRowCount = 2; var userRowCount = MediaQuery.of(context).size.width ~/ 500; if (userRowCount < 1) userRowCount = 1; return KeepAliveWrapper( child: Obx( () => controller.searchMode.value == 0 ? PageGridView( pageController: controller, padding: AppStyle.edgeInsetsA12, firstRefresh: false, mainAxisSpacing: 12, crossAxisSpacing: 12, crossAxisCount: roomRowCount, showPageLoadding: true, itemBuilder: (_, i) { var item = controller.list[i] as LiveRoomItem; return LiveRoomCard(controller.site, item); }, ) : PageGridView( crossAxisSpacing: 12, crossAxisCount: userRowCount, pageController: controller, firstRefresh: true, itemBuilder: (_, i) { var item = controller.list[i] as LiveAnchorItem; return ListTile( leading: NetImage( item.avatar, width: 48, height: 48, borderRadius: 24, ), title: Text(item.userName), subtitle: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 8, height: 8, decoration: BoxDecoration( color: item.liveStatus ? Colors.green : Colors.grey, borderRadius: AppStyle.radius12, ), ), AppStyle.hGap4, Text( item.liveStatus ? "直播中" : "未开播", style: TextStyle( fontSize: 12, fontWeight: FontWeight.normal, color: item.liveStatus ? null : Colors.grey, ), ), ], ), onTap: () { AppNavigator.toLiveRoomDetail( site: controller.site, roomId: item.roomId); }, ); }, ), ), ); } } ================================================ FILE: simple_live_app/lib/modules/search/search_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/modules/search/search_controller.dart'; import 'package:simple_live_app/modules/search/search_list_view.dart'; class SearchPage extends GetView { const SearchPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( automaticallyImplyLeading: false, title: TextField( controller: controller.searchController, autofocus: true, decoration: InputDecoration( hintText: "搜点什么吧", border: OutlineInputBorder( borderRadius: AppStyle.radius24, ), contentPadding: AppStyle.edgeInsetsH12, prefixIcon: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( onPressed: Get.back, icon: const Icon(Icons.arrow_back), ), Obx( () => DropdownButton( underline: const SizedBox(), items: const [ DropdownMenuItem( value: 0, child: Text("房间"), ), DropdownMenuItem( value: 1, child: Text("主播"), ), ], value: controller.searchMode.value, onChanged: (e) { controller.searchMode.value = e ?? 0; controller.doSearch(); }, ), ), AppStyle.hGap8, ], ), suffixIcon: IconButton( onPressed: controller.doSearch, icon: const Icon(Icons.search), ), ), onSubmitted: (e) { controller.doSearch(); }, ), bottom: TabBar( controller: controller.tabController, padding: EdgeInsets.zero, tabAlignment: TabAlignment.center, tabs: Sites.supportSites .map( (e) => Tab( //text: e.name, child: Row( children: [ Image.asset( e.logo, width: 24, ), AppStyle.hGap8, Text(e.name), ], ), ), ) .toList(), labelPadding: AppStyle.edgeInsetsH20, isScrollable: true, indicatorSize: TabBarIndicatorSize.label, ), ), body: TabBarView( physics: const NeverScrollableScrollPhysics(), controller: controller.tabController, children: Sites.supportSites .map((e) => SearchListView( e.id, ) // (e) => e.id == Constant.kDouyin // ? const DouyinSearchView() // : SearchListView( // e.id, // ), ) .toList(), ), ); } } ================================================ FILE: simple_live_app/lib/modules/settings/appstyle_settings/appstyle_setting_contorller.dart ================================================ import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get_core/src/get_main.dart'; import 'package:get/get_instance/src/extension_instance.dart'; import 'package:get/get_navigation/src/extension_navigation.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart'; import 'package:get/get_state_manager/src/simple/get_controllers.dart'; import 'package:path_provider/path_provider.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/models/font_model.dart'; import 'package:simple_live_app/requests/http_client.dart'; import 'package:simple_live_app/services/local_storage_service.dart'; class AppStyleSettingController extends GetxController { static AppStyleSettingController get instance => Get.find(); var themeMode = 0.obs; var isDynamic = false.obs; var styleColor = 0xff3498db.obs; Rx curFontName = Rx(null); Rx curFontModel = Rx(null); final RxList fontList = [].obs; final RxMap fontMap = {}.obs; Rx fontState = DownloadState.notDownloaded.obs; Future init() async { styleColor.value = LocalStorageService.instance .getValue(LocalStorageService.kStyleColor, 0xff3498db); isDynamic.value = LocalStorageService.instance .getValue(LocalStorageService.kIsDynamic, false); await fetchFonts(); await userFontInit(); } Future fontDelete() async{ var dir = await getApplicationSupportDirectory(); final fontDir = Directory("${dir.path}/fonts/${curFontModel.value!.id}"); try { // 删除整个目录(包括目录本身和所有内容) await fontDir.delete(recursive: true); await fontDir.create(recursive: true); var download = await fontDownloadCheck(curFontModel.value!.id); if(download == false){ fontState.value = DownloadState.notDownloaded; } SmartDialog.showToast("已删除${curFontModel.value!.name}字体"); Log.d('目录${fontDir.path}已清空并重新创建'); } catch (e,s) { Log.e('操作失败: $e', s); } } void fontReset(){ if(Platform.isWindows){ curFontName.value = "Microsoft YaHei"; LocalStorageService.instance.setValue(LocalStorageService.kCustomFont, curFontName.value); }else{ curFontName.value = null; LocalStorageService.instance.removeValue(LocalStorageService.kCustomFont); } } void changeFontFamily() { curFontName.value = curFontModel.value?.id; LocalStorageService.instance .setValue(LocalStorageService.kCustomFont, curFontName.value); SmartDialog.showToast("已设置全局字体为${curFontModel.value?.name}"); } Future onFontSelected(FontModel fontModel) async { var fontName = fontModel.id; curFontModel.value = fontModel; if (await fontDownloadCheck(fontName)) { fontState.value = DownloadState.downloaded; // 存在则加载并应用 await loadFont(fontName); } else { fontState.value = DownloadState.notDownloaded; } } Future downloadFont() async { var dir = await getApplicationSupportDirectory(); var fontName = curFontModel.value!.id; final fontDir = Directory("${dir.path}/fonts/$fontName"); if (!await fontDir.exists()) { await fontDir.create(recursive: true); } Log.d("开始下载----$fontName"); const baseUrl = "https://gcore.jsdelivr.net/gh/SlotSun/fonts@master/"; fontState.value = DownloadState.downloading; for (var filePath in curFontModel.value!.files) { final fileName = filePath.split('/').last; final file = File("${fontDir.path}/$fileName"); if (!await file.exists()) { int retryCount = 0; const maxRetries = 3; while (retryCount < maxRetries) { try { // Download if not exists await HttpClient.instance.download( "$baseUrl$filePath", file.path, ); break; // Success } catch (e, s) { retryCount++; if (retryCount >= maxRetries) { Log.e("Failed to download font file after $maxRetries attempts: $filePath\n$e", s); fontState.value = DownloadState.notDownloaded; SmartDialog.showToast("下载失败,请检查网络后重试"); throw Exception("Failed to download $fileName: $e"); } Log.w("Download failed, retrying ($retryCount/$maxRetries): $fileName"); await Future.delayed(const Duration(seconds: 1)); } } } } fontState.value = DownloadState.downloaded; await loadFont(fontName); } Future userFontInit() async { var fontName = LocalStorageService.instance .getNullValue(LocalStorageService.kCustomFont, null); Log.d('获取当前字体$fontName'); if (fontName != null && fontName != "Microsoft YaHei") { // 确认本地是否存在此字体 if (await fontDownloadCheck(fontName)) { // 存在则加载并应用 await loadFont(fontName); } else { // 不存在则使用默认字体 fontName = null; } } curFontName.value = fontName; if (curFontName.value != null) { curFontModel.value = fontMap.keys.firstWhere( (element) => element.id == curFontName.value, orElse: () => fontMap.keys.first); } else { curFontModel.value = fontMap.keys.first; } // maybe curFontName = null fontState.value = await fontDownloadCheck(curFontModel.value!.id) ? DownloadState.downloaded : DownloadState.notDownloaded; Log.d("当前字体模型:${curFontModel.value!.id}"); } Future loadFont(String fontName) async { var dir = await getApplicationSupportDirectory(); final fontDir = Directory("${dir.path}/fonts/$fontName"); final loader = FontLoader(fontName); await for (final entity in fontDir.list()) { if (entity is File && entity.path.endsWith('.ttf')) { loader.addFont(entity.readAsBytes().then(ByteData.sublistView)); } } await loader.load(); Log.d('已加载$fontName 词库'); } Future fontDownloadCheck(String fontName) async { final dir = await getApplicationSupportDirectory(); final fontDir = Directory("${dir.path}/fonts/$fontName"); bool fontDownload = await fontDir.exists() && await fontDir.list().length >= 1; return fontDownload; } Future fetchFonts() async { try { final jsonStr = await rootBundle.loadString('assets/fonts/fonts-manifest.json'); final List list = json.decode(jsonStr); // 集合推导构造fontMap fontMap.assignAll( { for (final e in list) FontModel.fromJson(e): (e['name'] as String? ?? ''), }, ); } catch (e, s) { Log.e("Failed to fetch fonts manifest: $e", s); } } void setIsDynamic(bool e) { isDynamic.value = e; LocalStorageService.instance.setValue(LocalStorageService.kIsDynamic, e); } void changeTheme() { Get.dialog( SimpleDialog( title: const Text("设置主题"), children: [ RadioGroup( groupValue: themeMode.value, onChanged: (e) { Get.back(); setTheme(e ?? 0); }, child: Column( children: [ RadioListTile( title: const Text("跟随系统"), value: 0, ), RadioListTile( title: const Text("浅色模式"), value: 1, ), RadioListTile( title: const Text("深色模式"), value: 2, ), ], ), ), ], ), ); } void setTheme(int i) { themeMode.value = i; var mode = ThemeMode.values[i]; LocalStorageService.instance.setValue(LocalStorageService.kThemeMode, i); Get.changeThemeMode(mode); } void setStyleColor(int e) { styleColor.value = e; LocalStorageService.instance.setValue(LocalStorageService.kStyleColor, e); } } ================================================ FILE: simple_live_app/lib/modules/settings/appstyle_settings/appstyle_setting_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/modules/settings/appstyle_settings/appstyle_setting_contorller.dart'; import 'package:simple_live_app/widgets/settings/settings_card.dart'; import 'package:simple_live_app/widgets/settings/settings_menu.dart'; import 'package:simple_live_app/widgets/settings/settings_switch.dart'; class AppStyleSettingPage extends GetView { const AppStyleSettingPage({super.key}); Widget trailingBuild({required Widget widget}) { return Row( mainAxisSize: MainAxisSize.min, children: [ Tooltip( message: "重置为默认字体", child: IconButton( onPressed: controller.fontReset, icon: Icon(Icons.settings_backup_restore_outlined), ), ), AppStyle.hGap4, Visibility( visible: controller.fontState.value == DownloadState.downloaded, child: Tooltip( message: "删除字体", child: IconButton( onPressed: controller.fontDelete, icon: Icon(Icons.delete_outline_outlined), ), ), ), Visibility( visible: controller.fontState.value == DownloadState.downloaded, child: AppStyle.hGap4, ), widget, ], ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("外观设置"), ), body: ListView( padding: AppStyle.edgeInsetsA12, children: [ Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 0), child: Text( "显示主题", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Obx( () => RadioGroup( groupValue: controller.themeMode.value, onChanged: (e) { controller.setTheme(e ?? 0); }, child: Column( mainAxisSize: MainAxisSize.min, children: [ RadioListTile( title: const Text( "跟随系统", ), visualDensity: VisualDensity.compact, value: 0, contentPadding: AppStyle.edgeInsetsH12, ), RadioListTile( title: const Text( "浅色模式", ), visualDensity: VisualDensity.compact, value: 1, contentPadding: AppStyle.edgeInsetsH12, ), RadioListTile( title: const Text( "深色模式", ), visualDensity: VisualDensity.compact, value: 2, contentPadding: AppStyle.edgeInsetsH12, ), ], ), ), ), ), AppStyle.vGap12, Padding( padding: AppStyle.edgeInsetsA12, child: Text( "主题颜色", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Obx( () => Column( mainAxisSize: MainAxisSize.min, children: [ SettingsSwitch( value: controller.isDynamic.value, title: "动态取色", onChanged: (e) { controller.setIsDynamic(e); Get.forceAppUpdate(); }, ), if (!controller.isDynamic.value) AppStyle.divider, if (!controller.isDynamic.value) Padding( padding: AppStyle.edgeInsetsA12, child: Wrap( spacing: 8, runSpacing: 8, children: [ const Color(0xffEF5350), const Color(0xff3498db), const Color(0xffF06292), const Color(0xff9575CD), const Color(0xff26C6DA), const Color(0xff26A69A), const Color(0xffFFF176), const Color(0xffFF9800), ] .map( (e) => GestureDetector( onTap: () { controller.setStyleColor(e.v); Get.forceAppUpdate(); }, child: Container( width: 36, height: 36, decoration: BoxDecoration( color: e, borderRadius: AppStyle.radius4, border: Border.all( color: Colors.grey.withAlpha(50), width: 1, ), ), child: Obx( () => Center( child: Icon( Icons.check, color: controller.styleColor.value == e.v ? Colors.white : Colors.transparent, ), ), ), ), ), ) .toList(), ), ), ], ), ), ), AppStyle.vGap12, Padding( padding: AppStyle.edgeInsetsA12, child: Text( "字体设置", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Obx( () => SettingsMenu( title: controller.curFontModel.value!.name, value: controller.curFontModel.value!, valueMap: controller.fontMap, onChanged: (e) { controller.onFontSelected(e); }, trailing: Obx(() { switch (controller.fontState.value) { case DownloadState.notDownloaded: return trailingBuild( widget: Tooltip( message: "下载字体", child: IconButton( icon: const Icon(Icons.download_outlined), onPressed: () => controller.downloadFont(), ), ), ); case DownloadState.downloading: return trailingBuild( widget: SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ), ); case DownloadState.downloaded: return trailingBuild( widget: Tooltip( message: "应用字体", child: IconButton( icon: const Icon(Icons.check_circle_outline_outlined), onPressed: () => controller.changeFontFamily(), ), ), ); } }), ), ), ), ], ), ); } } extension ColorExt on Color { static int _floatToInt8(double x) { return (x * 255.0).round() & 0xff; } int get v => _floatToInt8(a) << 24 | _floatToInt8(r) << 16 | _floatToInt8(g) << 8 | _floatToInt8(b) << 0; } ================================================ FILE: simple_live_app/lib/modules/settings/auto_exit_settings_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/widgets/settings/settings_action.dart'; import 'package:simple_live_app/widgets/settings/settings_card.dart'; import 'package:simple_live_app/widgets/settings/settings_switch.dart'; class AutoExitSettingsPage extends GetView { const AutoExitSettingsPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("定时关闭设置"), ), body: ListView( padding: AppStyle.edgeInsetsA12, children: [ SettingsCard( child: Column( children: [ Obx( () => SettingsSwitch( value: controller.autoExitEnable.value, title: "启用定时关闭", onChanged: (e) { controller.setAutoExitEnable(e); }, ), ), Obx( () => Visibility( visible: controller.autoExitEnable.value, child: AppStyle.divider, ), ), Obx( () => Visibility( visible: controller.autoExitEnable.value, child: SettingsAction( title: "自动关闭时间", value: "${controller.autoExitDuration.value ~/ 60}小时${controller.autoExitDuration.value % 60}分钟", subtitle: "从进入直播间开始倒计时", onTap: () { setTimer(context); }, ), ), ), ], ), ), ], ), ); } void setTimer(BuildContext context) async { var value = await showTimePicker( context: context, initialTime: TimeOfDay( hour: controller.autoExitDuration.value ~/ 60, minute: controller.autoExitDuration.value % 60, ), initialEntryMode: TimePickerEntryMode.inputOnly, builder: (_, child) { return MediaQuery( data: MediaQuery.of(context).copyWith( alwaysUse24HourFormat: true, ), child: child!, ); }, ); if (value == null || (value.hour == 0 && value.minute == 0)) { return; } var duration = Duration(hours: value.hour, minutes: value.minute); controller.setAutoExitDuration(duration.inMinutes); } } ================================================ FILE: simple_live_app/lib/modules/settings/danmu_settings_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:canvas_danmaku/canvas_danmaku.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/routes/route_path.dart'; import 'package:simple_live_app/widgets/settings/settings_action.dart'; import 'package:simple_live_app/widgets/settings/settings_card.dart'; import 'package:simple_live_app/widgets/settings/settings_number.dart'; import 'package:simple_live_app/widgets/settings/settings_switch.dart'; class DanmuSettingsPage extends StatelessWidget { const DanmuSettingsPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("弹幕设置"), ), body: ListView( padding: AppStyle.edgeInsetsA12, children: const [ DanmuSettingsView(), ], ), ); } } class DanmuSettingsView extends GetView { final Function()? onTapDanmuShield; final DanmakuController? danmakuController; const DanmuSettingsView({ this.onTapDanmuShield, this.danmakuController, super.key, }); @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 0), child: Text( "弹幕筛选", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( mainAxisSize: MainAxisSize.min, children: [ SettingsAction( title: "关键词屏蔽", onTap: onTapDanmuShield ?? () => Get.toNamed(RoutePath.kSettingsDanmuShield), ), Obx( () => SettingsSwitch( title: "弹幕去重", subtitle: "测试性功能", value: controller.danmakuMaskEnable.value, onChanged: (e) { controller.setDanmakuMaskEnable(e); }, ), ), ], ), ), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 24), child: Text( "弹幕设置", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( mainAxisSize: MainAxisSize.min, children: [ Obx( () => SettingsSwitch( title: "默认开关", value: controller.danmuEnable.value, onChanged: (e) { controller.setDanmuEnable(e); }, ), ), AppStyle.divider, Obx( () => SettingsNumber( title: "显示区域", value: (controller.danmuArea.value * 100).toInt(), min: 10, max: 100, step: 10, unit: "%", onChanged: (e) { controller.setDanmuArea(e / 100.0); updateDanmuOption( danmakuController?.option.copyWith(area: e / 100.0), ); }, ), ), AppStyle.divider, Obx( () => SettingsNumber( title: "不透明度", value: (controller.danmuOpacity.value * 100).toInt(), min: 10, max: 100, step: 10, unit: "%", onChanged: (e) { controller.setDanmuOpacity(e / 100.0); updateDanmuOption( danmakuController?.option.copyWith(opacity: e / 100.0), ); }, ), ), AppStyle.divider, Obx( () => SettingsNumber( title: "字体大小", value: controller.danmuSize.toInt(), min: 8, max: 48, onChanged: (e) { controller.setDanmuSize(e.toDouble()); updateDanmuOption( danmakuController?.option .copyWith(fontSize: e.toDouble()), ); }, ), ), AppStyle.divider, Obx( () => SettingsNumber( title: "字体粗细", value: controller.danmuFontWeight.value, min: 0, max: 8, step: 1, displayValue: [ "极细", "很细", "细", "正常", "小粗", "偏粗", "粗", "很粗", "极粗" ][controller.danmuFontWeight.value] .toString(), onChanged: (e) { controller.setDanmuFontWeight(e); updateDanmuOption( danmakuController?.option.copyWith( fontWeight: e, ), ); }, ), ), AppStyle.divider, Obx( () => SettingsNumber( title: "滚动速度", subtitle: "弹幕持续时间(秒),越小速度越快", value: controller.danmuSpeed.toInt(), min: 4, max: 20, onChanged: (e) { controller.setDanmuSpeed(e.toDouble()); updateDanmuOption( danmakuController?.option.copyWith(duration: e.toDouble()), ); }, ), ), AppStyle.divider, Obx( () => SettingsNumber( title: "字体描边", value: controller.danmuStrokeWidth.toInt(), min: 0, max: 10, onChanged: (e) { controller.setDanmuStrokeWidth(e.toDouble()); updateDanmuOption( danmakuController?.option.copyWith(strokeWidth: e.toDouble()), ); }, ), ), AppStyle.divider, Obx( () => SettingsNumber( title: "顶部边距", subtitle: "曲面屏显示不全可设置此选项", value: controller.danmuTopMargin.toInt(), min: 0, max: 48, step: 4, onChanged: (e) { controller.setDanmuTopMargin(e.toDouble()); }, ), ), AppStyle.divider, Obx( () => SettingsNumber( title: "底部边距", subtitle: "曲面屏显示不全可设置此选项", value: controller.danmuBottomMargin.toInt(), min: 0, max: 48, step: 4, onChanged: (e) { controller.setDanmuBottomMargin(e.toDouble()); }, ), ), ], ), ), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 24), child: Text( "弹幕去重参数设置", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( mainAxisSize: MainAxisSize.min, children: [ Obx( () => SettingsNumber( title: "去重窗口大小(秒)", value: AppSettingsController.instance.danmuWindowMs.value, step: 1, max: 45, min: 10, onChanged: (e) { AppSettingsController.instance.setDanmuWindowMs(e); }, ), ), Obx( () => SettingsSwitch( value: AppSettingsController .instance.danmuTextNormalization.value, title: "文本归一化", onChanged: (e) { AppSettingsController.instance.setDanmuTextNormalization(e); }, ), ), Obx( () => SettingsSwitch( value: AppSettingsController .instance.danmuFrequencyControl.value, title: "弹幕显示频率", onChanged: (e) { AppSettingsController.instance.setDanmuFrequencyControl(e); }, ), ), Obx( () => Visibility( visible: AppSettingsController .instance.danmuFrequencyControl.value, child: SettingsNumber( title: "显示频率(次)", value: AppSettingsController .instance.danmuMaxFrequency.value, step: 1, max: 10, min: 1, onChanged: (e) { AppSettingsController.instance.setDanmuMaxFrequency(e); }, ), ), ), ], ), ), ], ); } void updateDanmuOption(DanmakuOption? option) { if (danmakuController == null || option == null) return; danmakuController!.updateOption(option); } } ================================================ FILE: simple_live_app/lib/modules/settings/danmu_shield/danmu_shield_controller.dart ================================================ import 'package:flutter/widgets.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; class DanmuShieldController extends BaseController { final TextEditingController textEditingController = TextEditingController(); final AppSettingsController settingsController = Get.find(); void add() { if (textEditingController.text.isEmpty) { SmartDialog.showToast("请输入关键词"); return; } settingsController.addShieldList(textEditingController.text.trim()); textEditingController.text = ""; } void remove(String item) { settingsController.removeShieldList(item); } } ================================================ FILE: simple_live_app/lib/modules/settings/danmu_shield/danmu_shield_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/modules/settings/danmu_shield/danmu_shield_controller.dart'; class DanmuShieldPage extends GetView { const DanmuShieldPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("弹幕屏蔽"), ), body: ListView( padding: AppStyle.edgeInsetsA12, children: [ TextField( controller: controller.textEditingController, decoration: InputDecoration( contentPadding: AppStyle.edgeInsetsH12, border: const OutlineInputBorder(), hintText: "请输入关键词或正则表达式", suffixIcon: TextButton.icon( onPressed: controller.add, icon: const Icon(Icons.add), label: const Text("添加"), ), ), onSubmitted: (e) { controller.add(); }, ), AppStyle.vGap4, Text( '以"/"开头和结尾将视作正则表达式, 如"/\\d+/"表示屏蔽所有数字', style: Get.textTheme.bodySmall, ), AppStyle.vGap12, Obx( () => Text( "已添加${controller.settingsController.shieldList.length}个关键词(点击移除)", style: Get.textTheme.titleSmall, ), ), AppStyle.vGap12, Obx( () => Wrap( runSpacing: 12, spacing: 12, children: controller.settingsController.shieldList .map( (item) => InkWell( borderRadius: AppStyle.radius24, onTap: () { controller.remove(item); }, child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: AppStyle.radius24, ), padding: AppStyle.edgeInsetsH12.copyWith( top: 4, bottom: 4, ), child: Text( item, style: Get.textTheme.bodyMedium, ), ), ), ) .toList(), ), ), ], ), ); } } ================================================ FILE: simple_live_app/lib/modules/settings/indexed_settings/indexed_settings_controller.dart ================================================ import 'package:get/get.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; class IndexedSettingsController extends GetxController { RxList siteSort = RxList(); RxList homeSort = RxList(); @override void onInit() { siteSort = AppSettingsController.instance.siteSort; homeSort = AppSettingsController.instance.homeSort; super.onInit(); } void updateSiteSort(int oldIndex, int newIndex) { if (oldIndex < newIndex) { newIndex -= 1; } final String item = siteSort.removeAt(oldIndex); siteSort.insert(newIndex, item); // ignore: invalid_use_of_protected_member AppSettingsController.instance.setSiteSort(siteSort.value); } void updateHomeSort(int oldIndex, int newIndex) { if (oldIndex < newIndex) { newIndex -= 1; } final String item = homeSort.removeAt(oldIndex); homeSort.insert(newIndex, item); // ignore: invalid_use_of_protected_member AppSettingsController.instance.setHomeSort(homeSort.value); } } ================================================ FILE: simple_live_app/lib/modules/settings/indexed_settings/indexed_settings_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/modules/settings/indexed_settings/indexed_settings_controller.dart'; import 'package:simple_live_app/widgets/settings/settings_card.dart'; class IndexedSettingsPage extends GetView { const IndexedSettingsPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("主页设置"), ), body: ListView( padding: AppStyle.edgeInsetsA12, children: [ Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 0), child: Text( "主页排序 (长按拖动排序,重启后生效)", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Obx( () => ReorderableListView( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), onReorder: controller.updateHomeSort, children: controller.homeSort.map( (key) { var e = Constant.allHomePages[key]!; return ListTile( key: ValueKey(e.title), title: Text(e.title), visualDensity: VisualDensity.compact, leading: Icon(e.iconData), trailing: const Icon(Icons.drag_handle), ); }, ).toList(), ), ), ), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 24), child: Text( "平台排序 (长按拖动排序,重启后生效)", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Obx( () => ReorderableListView( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), onReorder: controller.updateSiteSort, children: controller.siteSort .where((key) => Sites.allSites[key]?.name != 'Twitch') .map( (key) { var e = Sites.allSites[key]!; return ListTile( key: ValueKey(e.id), visualDensity: VisualDensity.compact, title: Text(e.name), leading: Image.asset( e.logo, width: 24, height: 24, ), trailing: const Icon(Icons.drag_handle), ); }, ).toList(), ), ), ), ], ), ); } } ================================================ FILE: simple_live_app/lib/modules/settings/other/other_settings_controller.dart ================================================ import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:path/path.dart' as p; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/services/firebase_service.dart'; import 'package:simple_live_app/services/local_storage_service.dart'; class OtherSettingsController extends BaseController { RxList logFiles = [].obs; var videoOutputDrivers = { "gpu": "gpu", "gpu-next": "gpu-next", "xv": "xv (X11 only)", "x11": "x11 (X11 only)", "vdpau": "vdpau (X11 only)", "direct3d": "direct3d (Windows only)", "sdl": "sdl", "dmabuf-wayland": "dmabuf-wayland", "vaapi": "vaapi", "null": "null", "libmpv": "libmpv", "mediacodec_embed": "mediacodec_embed (Android only)", }; var audioOutputDrivers = { "null": "null (No audio output)", "pulse": "pulse (Linux, uses PulseAudio)", "pipewire": "pipewire (Linux, via Pulse compatibility or native)", "alsa": "alsa (Linux only)", "oss": "oss (Linux only)", "jack": "jack (Linux/macOS, low-latency audio)", "directsound": "directsound (Windows only)", "wasapi": "wasapi (Windows only)", "winmm": "winmm (Windows only, legacy API)", "audiounit": "audiounit (iOS only)", "coreaudio": "coreaudio (macOS only)", "opensles": "opensles (Android only)", "audiotrack": "audiotrack (Android only)", "aaudio": "aaudio (Android only)", "pcm": "pcm (Cross-platform)", "sdl": "sdl (Cross-platform, via SDL library)", "openal": "openal (Cross-platform, OpenAL backend)", "libao": "libao (Cross-platform, uses libao library)", "auto": "auto (Not available)" }; var hardwareDecoder = { "no": "no", "auto": "auto", "auto-safe": "auto-safe", "yes": "yes", "auto-copy": "auto-copy", "d3d11va": "d3d11va", "d3d11va-copy": "d3d11va-copy", "videotoolbox": "videotoolbox", "videotoolbox-copy": "videotoolbox-copy", "vaapi": "vaapi", "vaapi-copy": "vaapi-copy", "nvdec": "nvdec", "nvdec-copy": "nvdec-copy", "drm": "drm", "drm-copy": "drm-copy", "vulkan": "vulkan", "vulkan-copy": "vulkan-copy", "dxva2": "dxva2", "dxva2-copy": "dxva2-copy", "vdpau": "vdpau", "vdpau-copy": "vdpau-copy", "mediacodec": "mediacodec", "mediacodec-copy": "mediacodec-copy", "cuda": "cuda", "cuda-copy": "cuda-copy", "crystalhd": "crystalhd", "rkmpp": "rkmpp" }; @override void onInit() { loadLogFiles(); super.onInit(); } void setFirebaseEnable(bool e){ AppSettingsController.instance.setFirebaseEnable(e); FirebaseService.setCrashlytics(e); } void setLogEnable(bool e) { AppSettingsController.instance.setLogEnable(e); if (e) { Log.initWriter(); Future.delayed(const Duration(milliseconds: 100), () { loadLogFiles(); }); } else { Log.disposeWriter(); } } void loadLogFiles() async { var supportDir = await getApplicationSupportDirectory(); var logDir = Directory("${supportDir.path}/log"); if (!await logDir.exists()) { await logDir.create(); } logFiles.clear(); await logDir.list().forEach((element) { var file = element as File; var name = p.basename(file.path); var time = file.lastModifiedSync(); var size = file.lengthSync(); logFiles.add(LogFileModel(name, file.path, time, size)); }); //logFiles 名称倒序 logFiles.sort((a, b) => b.time.compareTo(a.time)); } void cleanLog() async { if (AppSettingsController.instance.logEnable.value) { SmartDialog.showToast("请先关闭日志记录"); return; } var supportDir = await getApplicationSupportDirectory(); var logDir = Directory("${supportDir.path}/log"); if (await logDir.exists()) { await logDir.delete(recursive: true); } loadLogFiles(); } void shareLogFile(LogFileModel item) { SharePlus.instance.share(ShareParams(files: [XFile(item.path)])); } void saveLogFile(LogFileModel item) async { var filePath = await FilePicker.platform.saveFile( allowedExtensions: ['log'], type: FileType.custom, fileName: item.name, ); if (filePath != null) { var file = File(item.path); await file.copy(filePath); SmartDialog.showToast("保存成功"); } } void exportConfig() async { try { // 组装数据 var data = { "type": "simple_live", "platform": Platform.operatingSystem, "version": 1, "time": DateTime.now().millisecondsSinceEpoch, "config": LocalStorageService.instance.settingsBox.toMap(), "shield": LocalStorageService.instance.shieldBox.toMap(), }; var bytes = Uint8List.fromList(utf8.encode(jsonEncode(data))); // FilePicker 直接写入 var inlineSave = Platform.isAndroid || Platform.isIOS || kIsWeb; var path = await FilePicker.platform.saveFile( allowedExtensions: ['json'], type: FileType.custom, fileName: "simple_live_config.json", bytes: inlineSave ? bytes : null, ); if (path == null && !kIsWeb) { SmartDialog.showToast("保存取消"); return; } // 桌面平台需要手动写入 if (!inlineSave && path != null) { await File(path).writeAsBytes(bytes); } SmartDialog.showToast("保存成功"); } catch (e) { Log.logPrint(e); SmartDialog.showToast("导出失败:$e"); } } void importConfig() async { try { var file = await FilePicker.platform.pickFiles( allowedExtensions: ['json'], type: FileType.custom, ); if (file == null) { return; } var filePath = file.files.single.path!; var data = jsonDecode(await File(filePath).readAsString()); if (data["type"] != "simple_live") { SmartDialog.showToast("不支持的配置文件"); return; } // 检查platform if (data["platform"] != Platform.operatingSystem && !await Utils.showAlertDialog("导入配置文件平台不匹配,是否继续导入?", title: "平台不匹配")) { return; } LocalStorageService.instance.settingsBox.clear(); LocalStorageService.instance.shieldBox.clear(); LocalStorageService.instance.settingsBox.putAll(data["config"]); LocalStorageService.instance.shieldBox .putAll(data["shield"].cast()); SmartDialog.showToast("导入成功,重启生效"); } catch (e) { Log.logPrint(e); SmartDialog.showToast("导入失败:$e"); } } void resetDefaultConfig() { Utils.showAlertDialog("是否重置所有配置为默认值?").then((value) { if (value) { LocalStorageService.instance.settingsBox.clear(); LocalStorageService.instance.shieldBox.clear(); SmartDialog.showToast("重置成功,重启生效"); } }); } } class LogFileModel { late String name; late String path; late DateTime time; late int size; LogFileModel(this.name, this.path, this.time, this.size); } ================================================ FILE: simple_live_app/lib/modules/settings/other/other_settings_page.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/modules/settings/other/other_settings_controller.dart'; import 'package:simple_live_app/widgets/settings/settings_card.dart'; import 'package:simple_live_app/widgets/settings/settings_menu.dart'; import 'package:simple_live_app/widgets/settings/settings_switch.dart'; import 'package:url_launcher/url_launcher_string.dart'; class OtherSettingsPage extends GetView { const OtherSettingsPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("其他设置"), ), body: ListView( padding: AppStyle.edgeInsetsA12, children: [ SettingsCard( child: Padding( padding: AppStyle.edgeInsetsA4, child: Row( children: [ Expanded( child: TextButton.icon( onPressed: controller.exportConfig, label: const Text("导出配置"), icon: const Icon(Remix.export_line), ), ), Expanded( child: TextButton.icon( onPressed: controller.importConfig, label: const Text("导入配置"), icon: const Icon(Remix.import_line), ), ), Expanded( child: TextButton.icon( onPressed: controller.resetDefaultConfig, label: const Text("重置配置"), icon: const Icon(Remix.restart_line), ), ), ], ), ), ), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 24), child: Text( "播放器高级设置", style: Get.textTheme.titleSmall, ), ), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 0), child: Text.rich( TextSpan( text: "请勿随意修改以下设置,除非你知道自己在做什么。\n在修改以下设置前,你应该先查阅", children: [ WidgetSpan( child: GestureDetector( onTap: () { launchUrlString( "https://mpv.io/manual/stable/#video-output-drivers"); }, child: const Text( "MPV的文档", style: TextStyle( color: Colors.blue, fontSize: 12, decoration: TextDecoration.underline, ), ), ), ), ], ), style: const TextStyle(fontSize: 12, color: Colors.grey), ), ), SettingsCard( child: Column( children: [ Obx( () => SettingsSwitch( value: AppSettingsController.instance.customPlayerOutput.value, title: "自定义输出驱动与硬件加速", onChanged: (e) { AppSettingsController.instance.setCustomPlayerOutput(e); }, ), ), AppStyle.divider, Obx( () => SettingsMenu( title: "视频输出驱动(--vo)", value: AppSettingsController.instance.videoOutputDriver.value, valueMap: controller.videoOutputDrivers, onChanged: (e) { AppSettingsController.instance.setVideoOutputDriver(e); }, ), ), AppStyle.divider, Obx( () => SettingsMenu( title: "音频输出驱动(--ao)", value: AppSettingsController.instance.audioOutputDriver.value, valueMap: controller.audioOutputDrivers, onChanged: (e) { AppSettingsController.instance.setAudioOutputDriver(e); }, ), ), AppStyle.divider, Obx( () => SettingsMenu( title: "硬件解码器(--hwdec)", value: AppSettingsController .instance.videoHardwareDecoder.value, valueMap: controller.hardwareDecoder, onChanged: (e) { AppSettingsController.instance.setVideoHardwareDecoder(e); }, ), ), Obx( () => SettingsSwitch( value: AppSettingsController .instance.videoDoubleBuffering.value, title: "自定义开启双重缓存", onChanged: (e) { AppSettingsController.instance.setVideoDoubleBuffering(e); }, ), ), ], ), ), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 24), child: Text( "日志记录", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( children: [ Obx( () => SettingsSwitch( value: AppSettingsController.instance.logEnable.value, title: "开启日志记录", subtitle: "开启后将记录调试日志,可以将日志文件提供给开发者用于排查问题", onChanged: controller.setLogEnable, ), ), Visibility( visible: Platform.isAndroid, child: Obx( () => SettingsSwitch( value: AppSettingsController.instance.firebaseEnable.value, title: "开启崩溃分析", subtitle: "开启后应用崩溃时自动上传脱敏崩溃日志给开发者用于排查问题", onChanged: controller.setFirebaseEnable, ), ), ), ], ), ), ListTile( contentPadding: AppStyle.edgeInsetsL12, visualDensity: VisualDensity.compact, title: Text( "日志列表", style: Get.textTheme.titleSmall, ), trailing: TextButton.icon( onPressed: () { controller.cleanLog(); }, label: const Text("清空日志"), icon: const Icon(Icons.clear_all), ), ), SettingsCard( child: SizedBox( height: 300, child: Obx( () => ListView.separated( itemCount: controller.logFiles.length, separatorBuilder: (context, index) => AppStyle.divider, itemBuilder: (context, index) { var item = controller.logFiles[index]; return ListTile( visualDensity: VisualDensity.compact, contentPadding: AppStyle.edgeInsetsL12.copyWith(right: 4), title: Text(item.name), subtitle: Text(Utils.parseFileSize(item.size)), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ if (!Platform.isLinux) IconButton( onPressed: () { controller.shareLogFile(item); }, icon: const Icon(Icons.share), ), IconButton( onPressed: () { controller.saveLogFile(item); }, icon: const Icon(Icons.save), ), ], ), ); }, ), ), ), ), ], ), ); } } ================================================ FILE: simple_live_app/lib/modules/settings/play_settings_page.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/widgets/settings/settings_card.dart'; import 'package:simple_live_app/widgets/settings/settings_menu.dart'; import 'package:simple_live_app/widgets/settings/settings_number.dart'; import 'package:simple_live_app/widgets/settings/settings_switch.dart'; class PlaySettingsPage extends GetView { const PlaySettingsPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("直播间设置"), ), body: ListView( padding: AppStyle.edgeInsetsA12, children: [ Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 0), child: Text( "播放器", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( mainAxisSize: MainAxisSize.min, children: [ Obx( () => SettingsSwitch( title: "硬件解码", value: controller.hardwareDecode.value, subtitle: "播放失败可尝试关闭此选项", onChanged: (e) { controller.setHardwareDecode(e); }, ), ), if (Platform.isAndroid) AppStyle.divider, Obx( () => Visibility( visible: Platform.isAndroid, child: SettingsSwitch( title: "兼容模式", subtitle: "若播放卡顿可尝试打开此选项", value: controller.playerCompatMode.value, onChanged: (e) { controller.setPlayerCompatMode(e); }, ), ), ), // AppStyle.divider, // Obx( // () => SettingsNumber( // title: "缓冲区大小", // subtitle: "若播放卡顿可尝试调高此选项", // value: controller.playerBufferSize.value, // min: 32, // max: 1024, // step: 4, // unit: "MB", // onChanged: (e) { // controller.setPlayerBufferSize(e); // }, // ), // ), AppStyle.divider, Obx( () => SettingsSwitch( title: "进入后台自动暂停", value: controller.playerAutoPause.value, onChanged: (e) { controller.setPlayerAutoPause(e); }, ), ), AppStyle.divider, Obx( () => SettingsMenu( title: "画面尺寸", value: controller.scaleMode.value, valueMap: const { 0: "适应", 1: "拉伸", 2: "铺满", 3: "16:9", 4: "4:3", }, onChanged: (e) { controller.setScaleMode(e); }, ), ), AppStyle.divider, Obx( () => SettingsSwitch( title: "使用HTTPS链接", subtitle: "将http链接替换为https", value: controller.playerForceHttps.value, onChanged: (e) { controller.setPlayerForceHttps(e); }, ), ), AppStyle.divider, Obx( () => SettingsSwitch( title: "抖音HLS流优先", subtitle: "hls流可缓解部分直播间抖动问题,重启后生效", value: controller.douyinHlsFirst.value, onChanged: (e) { controller.setDouyinHlsFirst(e); }), ), ], ), ), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 24), child: Text( "直播间", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( mainAxisSize: MainAxisSize.min, children: [ Obx( () => SettingsSwitch( title: "进入直播间自动全屏", value: controller.autoFullScreen.value, onChanged: (e) { controller.setAutoFullScreen(e); }, ), ), AppStyle.divider, Obx( () => Visibility( visible: Platform.isAndroid, child: SettingsSwitch( title: "进入小窗隐藏弹幕", value: controller.pipHideDanmu.value, onChanged: (e) { controller.setPIPHideDanmu(e); }, ), ), ), ], ), ), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 24), child: Text( "清晰度", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( children: [ Obx( () => SettingsMenu( title: "默认清晰度", value: controller.qualityLevel.value, valueMap: const { 0: "最低", 1: "中等", 2: "最高", }, onChanged: (e) { controller.setQualityLevel(e); }, ), ), AppStyle.divider, Obx( () => SettingsMenu( title: "数据网络清晰度", value: controller.qualityLevelCellular.value, valueMap: const { 0: "最低", 1: "中等", 2: "最高", }, onChanged: (e) { controller.setQualityLevelCellular(e); }, ), ), ], ), ), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 24), child: Text( "聊天区", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( mainAxisSize: MainAxisSize.min, children: [ Obx( () => SettingsNumber( title: "文字大小", value: controller.chatTextSize.value.toInt(), min: 8, max: 36, onChanged: (e) { controller.setChatTextSize(e.toDouble()); }, ), ), AppStyle.divider, Obx( () => SettingsNumber( title: "上下间隔", value: controller.chatTextGap.value.toInt(), min: 0, max: 12, onChanged: (e) { controller.setChatTextGap(e.toDouble()); }, ), ), AppStyle.divider, Obx( () => SettingsSwitch( title: "气泡样式", value: controller.chatBubbleStyle.value, onChanged: (e) { controller.setChatBubbleStyle(e); }, ), ), ], ), ), ], ), ); } } ================================================ FILE: simple_live_app/lib/modules/sync/local_sync/device/sync_device_controller.dart ================================================ import 'dart:convert'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/models/sync_client_info_model.dart'; import 'package:simple_live_app/requests/sync_client_request.dart'; import 'package:simple_live_app/services/bilibili_account_service.dart'; import 'package:simple_live_app/services/db_service.dart'; import 'package:simple_live_app/services/sync_service.dart'; class SyncDeviceController extends BaseController { final SyncClinet client; final SyncClientInfoModel info; SyncDeviceController({required this.client, required this.info}); SyncClientRequest request = SyncClientRequest(); Future showOverlayDialog() async { var overlay = await Utils.showAlertDialog( "是否覆盖远端数据?", title: "数据覆盖", confirm: "覆盖", cancel: "不覆盖", ); return overlay; } void syncFollowAndTag() async { try { var overlay = await showOverlayDialog(); SmartDialog.showLoading(msg: "同步中..."); var users = DBService.instance.getFollowList(); var tags = DBService.instance.getFollowTagList(); var data = json.encode(users.map((e) => e.toJson()).toList()); var dataT = json.encode(tags.map((e) => e.toJson()).toList()); await request.syncFollow(client, data, overlay: overlay); // 标签和关注必须同时同步 await request.syncTag(client, dataT, overlay: overlay); SmartDialog.showToast("已同步关注列表和标签"); } catch (e) { SmartDialog.showToast("同步失败:$e"); Log.logPrint(e); } finally { SmartDialog.dismiss(); } } void syncHistory() async { try { var overlay = await showOverlayDialog(); SmartDialog.showLoading(msg: "同步中..."); var histores = DBService.instance.getHistories(); var data = json.encode(histores.map((e) => e.toJson()).toList()); await request.syncHistory(client, data, overlay: overlay); SmartDialog.showToast("已同步历史记录"); } catch (e) { SmartDialog.showToast("同步失败:$e"); Log.logPrint(e); } finally { SmartDialog.dismiss(); } } void syncBlockedWord() async { try { var overlay = await showOverlayDialog(); SmartDialog.showLoading(msg: "同步中..."); var shieldList = AppSettingsController.instance.shieldList; var data = json.encode(shieldList.toList()); await request.syncBlockedWord(client, data, overlay: overlay); SmartDialog.showToast("已同步屏蔽词"); } catch (e) { SmartDialog.showToast("同步失败:$e"); Log.logPrint(e); } finally { SmartDialog.dismiss(); } } void syncBiliAccount() async { try { if (!BiliBiliAccountService.instance.logined.value) { SmartDialog.showToast("未登录哔哩哔哩"); return; } SmartDialog.showLoading(msg: "同步中..."); await request.syncBiliAccount( client, BiliBiliAccountService.instance.cookie); SmartDialog.showToast("已同步哔哩哔哩账号"); } catch (e) { SmartDialog.showToast("同步失败:$e"); Log.logPrint(e); } finally { SmartDialog.dismiss(); } } } ================================================ FILE: simple_live_app/lib/modules/sync/local_sync/device/sync_device_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/modules/sync/local_sync/device/sync_device_controller.dart'; import 'package:simple_live_app/widgets/settings/settings_card.dart'; class SyncDevicePage extends GetView { const SyncDevicePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("同步"), ), body: ListView( padding: AppStyle.edgeInsetsA12.copyWith(top: 0), children: [ SettingsCard( child: ListTile( leading: buildIcon(), title: Text(controller.info.name), subtitle: Text( "${controller.info.type.toUpperCase()} ${controller.info.address}"), ), ), AppStyle.vGap12, SettingsCard( child: Column( children: [ ListTile( leading: const Icon(Remix.heart_line), title: const Text("同步关注列表"), trailing: const Icon(Icons.chevron_right), onTap: () { controller.syncFollowAndTag(); }, ), AppStyle.divider, ListTile( leading: const Icon(Icons.history), title: const Text("同步观看记录"), trailing: const Icon(Icons.chevron_right), onTap: () { controller.syncHistory(); }, ), AppStyle.divider, ListTile( leading: const Icon(Remix.shield_keyhole_line), title: const Text("同步弹幕屏蔽词"), trailing: const Icon(Icons.chevron_right), onTap: () { controller.syncBlockedWord(); }, ), AppStyle.divider, ListTile( leading: const Icon(Remix.account_circle_line), title: const Text("同步哔哩哔哩账号"), trailing: const Icon(Icons.chevron_right), onTap: () { controller.syncBiliAccount(); }, ), ], ), ), ], ), ); } Widget buildIcon() { var icon = controller.info.type.toLowerCase(); if (icon == "android") { return const Icon(Remix.android_line); } else if (icon == "ios") { return const Icon(Remix.apple_line); } else if (icon == "tv") { return const Icon(Remix.tv_2_line); } else if (icon == "windows") { return const Icon(Remix.microsoft_fill); } else if (icon == "macos") { return const Icon(Remix.mac_line); } else if (icon == "linux") { return const Icon(Remix.ubuntu_line); } else { return const Icon(Remix.device_line); } } } ================================================ FILE: simple_live_app/lib/modules/sync/local_sync/local_sync_controller.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/requests/sync_client_request.dart'; import 'package:simple_live_app/routes/app_navigation.dart'; import 'package:simple_live_app/routes/route_path.dart'; import 'package:simple_live_app/services/sync_service.dart'; class LocalSyncController extends BaseController { final String? address; LocalSyncController(this.address); @override void onInit() { SyncService.instance.refreshClients(); Future.delayed(Duration.zero, initConnect); super.onInit(); } void initConnect() { if (address != null && address!.isNotEmpty) { addressController.text = address!; connect(); } } TextEditingController addressController = TextEditingController(); SyncClientRequest request = SyncClientRequest(); void connect() async { var address = addressController.text; if (address.isEmpty) { SmartDialog.showToast("请输入地址"); return; } if (address.startsWith('http')) { var uri = Uri.tryParse(address); if (uri != null) { address = uri.host; } } else if (address.contains(':')) { var parts = address.split(":"); address = parts.first; } var client = SyncClinet( id: 'manual', address: address, port: SyncService.httpPort, name: "手动输入", type: Platform.operatingSystem, ); connectClient(client); } void connectClient(SyncClinet client) async { try { SmartDialog.showLoading(msg: "连接中..."); var info = await request.getClientInfo(client); AppNavigator.toSyncDevice(client, info); } catch (e) { SmartDialog.showToast("连接失败:$e"); } finally { SmartDialog.dismiss(); } } void toScanQr() async { var result = await Get.toNamed(RoutePath.kSyncScan); if (result == null || result.isEmpty) { return; } var addressList = (result as String).split(";"); if (addressList.length >= 2) { //弹窗选择 showPickerAddress(addressList); } else { addressController.text = result; //connect(); } } void showPickerAddress(List addressList) { SmartDialog.showToast("扫描到多个地址,请选择一个连接"); Utils.showBottomSheet( title: '请选择地址', child: ListView.builder( itemBuilder: (_, i) { return ListTile( title: Text(addressList[i]), onTap: () { Get.back(); addressController.text = addressList[i]; // connect(); }, ); }, itemCount: addressList.length, ), ); } void showInfo() { Utils.showBottomSheet( title: "本机信息", child: Column( children: [ Visibility( visible: SyncService.instance.httpRunning.value, child: GestureDetector( onTap: () { Get.back(); }, child: QrImageView( data: SyncService.instance.ipAddress.value, version: QrVersions.auto, backgroundColor: Colors.white, padding: AppStyle.edgeInsetsA12, size: 200, ), ), ), AppStyle.vGap24, Visibility( visible: SyncService.instance.httpRunning.value, child: Text( '服务已启动:${SyncService.instance.ipAddress.value.split(';').map((e) => '$e:${SyncService.httpPort}').join(';')}', textAlign: TextAlign.center, ), ), Visibility( visible: !SyncService.instance.httpRunning.value, child: Text( 'HTTP服务未启动:${SyncService.instance.httpErrorMsg},请尝试重启应用', textAlign: TextAlign.center, ), ), AppStyle.vGap12, Visibility( visible: SyncService.instance.httpRunning.value, child: const Text( "请使用其他Slive客户端扫描上方二维码\n建立连接后可选择需要同步的数据", textAlign: TextAlign.center, ), ), ], ), ); } } ================================================ FILE: simple_live_app/lib/modules/sync/local_sync/local_sync_page.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/modules/sync/local_sync/local_sync_controller.dart'; import 'package:simple_live_app/services/sync_service.dart'; import 'package:simple_live_app/widgets/settings/settings_card.dart'; class LocalSyncPage extends GetView { const LocalSyncPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('局域网数据同步'), actions: [ TextButton.icon( onPressed: controller.showInfo, icon: const Icon(Icons.qr_code), label: const Text("信息"), ), ], ), body: ListView( padding: AppStyle.edgeInsetsA12, children: [ SettingsCard( child: Padding( padding: AppStyle.edgeInsetsA12, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ TextField( controller: controller.addressController, onSubmitted: (e) { controller.connect(); }, decoration: InputDecoration( labelText: '客户端地址', hintText: '请输入地址或扫码自动填写', contentPadding: AppStyle.edgeInsetsH12, border: const OutlineInputBorder(), suffixIcon: Visibility( visible: Platform.isAndroid || Platform.isIOS, child: TextButton.icon( onPressed: controller.toScanQr, icon: const Icon(Remix.qr_scan_line), label: const Text("扫一扫"), ), ), ), ), AppStyle.vGap12, ElevatedButton( onPressed: () { controller.connect(); }, child: const Text("连接"), ), ], ), ), ), AppStyle.vGap12, ListTile( title: Obx( () => Text( "已发现设备(${SyncService.instance.scanClients.length})", style: Get.textTheme.titleSmall, ), ), visualDensity: VisualDensity.compact, contentPadding: AppStyle.edgeInsetsH12, trailing: IconButton( visualDensity: VisualDensity.compact, onPressed: () { SyncService.instance.refreshClients(); }, icon: const Icon(Icons.refresh), ), ), SettingsCard( child: Obx( () => ListView.separated( shrinkWrap: true, padding: EdgeInsets.zero, physics: const NeverScrollableScrollPhysics(), separatorBuilder: (BuildContext context, int index) => AppStyle.divider, itemCount: SyncService.instance.scanClients.length, itemBuilder: (BuildContext context, int index) { var client = SyncService.instance.scanClients[index]; return ListTile( title: Text(client.name), subtitle: Text(client.address), trailing: const Icon(Icons.chevron_right), onTap: () { controller.connectClient(client); }, ); }, ), ), ), AppStyle.vGap12, const Text( "如果无法扫描到设备,请手动输入地址", textAlign: TextAlign.center, style: TextStyle( color: Colors.grey, fontSize: 14, ), ), ], ), ); } } ================================================ FILE: simple_live_app/lib/modules/sync/local_sync/scan_qr/sync_scan_qr_controller.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/routes/route_path.dart'; class SyncScanQRControlelr extends BaseController { final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); QRViewController? qrController; StreamSubscription? barcodeStreamSubscription; bool pause = false; void onQRViewCreated(QRViewController controller) { qrController = controller; barcodeStreamSubscription = qrController!.scannedDataStream.listen((scanData) async { Log.d(scanData.toString()); if (pause) { return; } pause = true; // 扫码成功后暂停摄像头 await controller.pauseCamera(); var code = scanData.code ?? ""; // 处理扫码结果 if (code.isEmpty) { pause = false; await controller.resumeCamera(); return; } // 如果是5位字符串,为房间号 if (code.length == 5) { Get.offAndToNamed(RoutePath.kRemoteSyncRoom, arguments: code); return; } else { var addressList = code.split(";"); if (addressList.length >= 2) { //弹窗选择 showPickerAddress(addressList); } else { Get.back(result: code); } } }); } void showPickerAddress(List addressList) async { SmartDialog.showToast("扫描到多个地址,请选择一个连接"); var address = await Utils.showBottomSheet( title: '请选择地址', child: ListView.builder( itemBuilder: (_, i) { return ListTile( title: Text(addressList[i]), onTap: () { Get.back(result: addressList[i]); }, ); }, itemCount: addressList.length, ), ); if (address != null && address.isNotEmpty) { Get.back(result: address); } } @override void onClose() { barcodeStreamSubscription?.cancel(); super.onClose(); } } ================================================ FILE: simple_live_app/lib/modules/sync/local_sync/scan_qr/sync_scan_qr_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; import 'package:simple_live_app/modules/sync/local_sync/scan_qr/sync_scan_qr_controller.dart'; class SyncScanQRPage extends GetView { const SyncScanQRPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('扫描二维码'), actions: [ IconButton( onPressed: () { controller.qrController?.toggleFlash(); }, icon: const Icon(Icons.flash_on), ), // 反转摄像头 IconButton( onPressed: () { controller.qrController?.flipCamera(); }, icon: const Icon(Icons.flip_camera_android), ), ], ), body: Stack( children: [ QRView( key: controller.qrKey, onQRViewCreated: controller.onQRViewCreated, ), const ScanRectangle(), ], ), ); } } class ScanRectangle extends StatefulWidget { const ScanRectangle({super.key}); @override State createState() => _ScanRectangleState(); } class _ScanRectangleState extends State with SingleTickerProviderStateMixin { late AnimationController animeController; late Animation animation; @override void initState() { animeController = AnimationController(duration: const Duration(seconds: 2), vsync: this); animeController.addStatusListener((status) { if (status == AnimationStatus.completed) { animeController.reverse(); } else if (status == AnimationStatus.dismissed) { animeController.forward(); } }); animation = Tween( begin: const Offset(0, 0), end: const Offset(0, 1), ).animate(animeController); animeController.forward(); super.initState(); } @override Widget build(BuildContext context) { return Center( child: Container( height: 240, width: 240, decoration: BoxDecoration( border: Border.all( color: Colors.grey.withAlpha(50), width: 2, ), ), child: SlideTransition( position: animation, child: Container( height: 240, width: 240, decoration: const BoxDecoration( border: Border( top: BorderSide( width: 2, color: Colors.green, ), ), ), ), ), ), ); } @override void dispose() { animeController.dispose(); super.dispose(); } } ================================================ FILE: simple_live_app/lib/modules/sync/remote_sync/room/remote_sync_room_controller.dart ================================================ import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/event_bus.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/models/db/follow_user.dart'; import 'package:simple_live_app/models/db/history.dart'; import 'package:simple_live_app/services/bilibili_account_service.dart'; import 'package:simple_live_app/services/db_service.dart'; import 'package:simple_live_app/services/signalr_service.dart'; class RemoteSyncRoomController extends BaseController { final String roomId; final SignalRService signalR = SignalRService(); RemoteSyncRoomController(this.roomId) { if (roomId.isNotEmpty) { currentRoomId.value = roomId; } } StreamSubscription? _roomDestroyedSubscription; StreamSubscription? _roomUserUpdatedSubscription; StreamSubscription? _onFavoriteSubscription; StreamSubscription? _onHistorySubscription; StreamSubscription? _onShieldWordSubscription; StreamSubscription? _onBiliAccountSubscription; var currentRoomId = "--".obs; RxList roomUsers = [].obs; Timer? _timer; var countDown = 600.obs; @override void onInit() { connect(); super.onInit(); } void connect() async { listenSignalR(); await signalR.connect(); if (signalR.state == SignalRConnectionState.connected) { if (roomId.isEmpty) { createRoom(); } else { joinRoom(roomId); } } } void createRoom() async { try { var resp = await signalR.createRoom(); if (resp.isSuccess) { currentRoomId.value = resp.data!; _startTimer(); } else { SmartDialog.showToast(resp.message); Get.back(); } } catch (e) { SmartDialog.showToast("创建房间失败"); Get.back(); } } void _startTimer() { // 倒计时5分钟,自动关闭页面 countDown.value = 600; _timer = Timer.periodic(const Duration(seconds: 1), (timer) { countDown--; if (countDown <= 0) { timer.cancel(); Get.back(); } }); } void joinRoom(String roomId) async { try { var resp = await signalR.joinRoom(roomId); if (!resp.isSuccess) { SmartDialog.showToast(resp.message); Get.back(); } } catch (e) { SmartDialog.showToast("加入房间失败"); Get.back(); } } void listenSignalR() { _roomDestroyedSubscription = signalR.onRoomDestroyedStream.listen((roomId) { SmartDialog.showToast("房间已被销毁"); Get.back(); }); _roomUserUpdatedSubscription = signalR.onRoomUserUpdatedStream.listen( (roomUsers) { this.roomUsers.assignAll(roomUsers); }, ); _onFavoriteSubscription = signalR.onFavoriteStream.listen((data) { onReceiveFavorite(data.$1, data.$2); }); _onHistorySubscription = signalR.onHistoryStream.listen((data) { onReceiveHistory(data.$1, data.$2); }); _onShieldWordSubscription = signalR.onShieldWordStream.listen((data) { onReceiveShieldWord(data.$1, data.$2); }); _onBiliAccountSubscription = signalR.onBiliAccountStream.listen((data) { onReceiveBiliAccount(data.$1, data.$2); }); } void onReceiveFavorite(bool overlay, String data) async { try { var jsonBody = json.decode(data); if (overlay) { await DBService.instance.followBox.clear(); } for (var item in jsonBody) { var user = FollowUser.fromJson(item); await DBService.instance.followBox.put(user.id, user); } SmartDialog.showToast('已同步关注用户列表'); EventBus.instance.emit(Constant.kUpdateFollow, 0); SmartDialog.showToast("已同步关注列表"); } catch (e) { SmartDialog.showToast("同步失败:$e"); Log.logPrint(e); } } void onReceiveHistory(bool overlay, String data) async { try { var jsonBody = json.decode(data); if (overlay) { await DBService.instance.historyBox.clear(); } for (var item in jsonBody) { var history = History.fromJson(item); if (DBService.instance.historyBox.containsKey(history.id)) { var old = DBService.instance.historyBox.get(history.id); //如果本地的更新时间比较新,就不更新 if (old!.updateTime.isAfter(history.updateTime)) { continue; } } await DBService.instance.addOrUpdateHistory(history); } SmartDialog.showToast('已同步历史记录'); EventBus.instance.emit(Constant.kUpdateHistory, 0); } catch (e) { SmartDialog.showToast("同步失败:$e"); Log.logPrint(e); } } void onReceiveShieldWord(bool overlay, String data) async { try { var jsonBody = json.decode(data); if (overlay) { AppSettingsController.instance.clearShieldList(); } for (var item in jsonBody) { // add to Hive AppSettingsController.instance.addShieldList(item); } SmartDialog.showToast('已同步屏蔽词'); } catch (e) { SmartDialog.showToast("同步失败:$e"); Log.logPrint(e); } } void onReceiveBiliAccount(bool overlay, String data) async { try { var jsonBody = json.decode(data); var cookie = jsonBody['cookie']; BiliBiliAccountService.instance.setCookie(cookie); BiliBiliAccountService.instance.loadUserInfo(); SmartDialog.showToast('已同步哔哩哔哩账号'); } catch (e) { SmartDialog.showToast("同步失败:$e"); Log.logPrint(e); } } Future showOverlayDialog() async { var overlay = await Utils.showAlertDialog( "是否覆盖远端数据?", title: "数据覆盖", confirm: "覆盖", cancel: "不覆盖", ); return overlay; } void syncFollow() async { try { if (roomUsers.length <= 1) { SmartDialog.showToast("无设备连接"); return; } var overlay = await showOverlayDialog(); SmartDialog.showLoading(msg: "发送中..."); var users = DBService.instance.getFollowList(); var data = json.encode(users.map((e) => e.toJson()).toList()); var resp = await signalR.sendContent( roomName: currentRoomId.value, action: "SendFavorite", overlay: overlay, content: data, ); if (resp.isSuccess) { SmartDialog.showToast("已发送关注列表"); } else { SmartDialog.showToast("发送失败:${resp.message}"); } } catch (e) { SmartDialog.showToast("发送失败:$e"); Log.logPrint(e); } finally { SmartDialog.dismiss(); } } void syncHistory() async { try { if (roomUsers.length <= 1) { SmartDialog.showToast("无设备连接"); return; } var overlay = await showOverlayDialog(); SmartDialog.showLoading(msg: "发送中..."); var histores = DBService.instance.getHistories(); var data = json.encode(histores.map((e) => e.toJson()).toList()); var resp = await signalR.sendContent( roomName: currentRoomId.value, action: "SendHistory", overlay: overlay, content: data, ); if (resp.isSuccess) { SmartDialog.showToast("已发送历史记录"); } else { SmartDialog.showToast("发送失败:${resp.message}"); } } catch (e) { SmartDialog.showToast("发送失败:$e"); Log.logPrint(e); } finally { SmartDialog.dismiss(); } } void syncBlockedWord() async { try { if (roomUsers.length <= 1) { SmartDialog.showToast("无设备连接"); return; } var overlay = await showOverlayDialog(); SmartDialog.showLoading(msg: "发送中..."); var shieldList = AppSettingsController.instance.shieldList; var data = json.encode(shieldList.toList()); var resp = await signalR.sendContent( roomName: currentRoomId.value, action: "SendShieldWord", overlay: overlay, content: data, ); if (resp.isSuccess) { SmartDialog.showToast("已发送屏蔽词"); } else { SmartDialog.showToast("发送失败:${resp.message}"); } } catch (e) { SmartDialog.showToast("发送失败:$e"); Log.logPrint(e); } finally { SmartDialog.dismiss(); } } void syncBiliAccount() async { try { if (roomUsers.length <= 1) { SmartDialog.showToast("无设备连接"); return; } if (!BiliBiliAccountService.instance.logined.value) { SmartDialog.showToast("未登录哔哩哔哩"); return; } SmartDialog.showLoading(msg: "发送中..."); var resp = await signalR.sendContent( roomName: currentRoomId.value, action: "SendBiliAccount", overlay: true, content: json.encode({ "cookie": BiliBiliAccountService.instance.cookie, }), ); if (resp.isSuccess) { SmartDialog.showToast("已发送哔哩哔哩账号"); } else { SmartDialog.showToast("发送失败:${resp.message}"); } } catch (e) { SmartDialog.showToast("同步失败:$e"); Log.logPrint(e); } finally { SmartDialog.dismiss(); } } void showQRInfo() { Utils.showBottomSheet( title: "房间信息", child: Column( children: [ QrImageView( data: currentRoomId.value, version: QrVersions.auto, backgroundColor: Colors.white, padding: AppStyle.edgeInsetsA12, size: 200, ), AppStyle.vGap24, Text( currentRoomId.value, textAlign: TextAlign.center, style: Get.textTheme.titleLarge, ), const Text( "请使用其他Simple Live客户端扫描上方二维码\n建立连接后可选择需要同步的数据", textAlign: TextAlign.center, ), ], ), ); } @override void onClose() { _timer?.cancel(); _roomDestroyedSubscription?.cancel(); _roomUserUpdatedSubscription?.cancel(); _onFavoriteSubscription?.cancel(); _onHistorySubscription?.cancel(); _onShieldWordSubscription?.cancel(); _onBiliAccountSubscription?.cancel(); signalR.dispose(); super.onClose(); } } ================================================ FILE: simple_live_app/lib/modules/sync/remote_sync/room/remote_sync_room_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/modules/sync/remote_sync/room/remote_sync_room_controller.dart'; import 'package:simple_live_app/services/signalr_service.dart'; import 'package:simple_live_app/widgets/settings/settings_card.dart'; class RemoteSyncRoomPage extends GetView { const RemoteSyncRoomPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("数据同步"), actions: [ Padding( padding: AppStyle.edgeInsetsH12, child: StreamBuilder( stream: controller.signalR.stateStream, builder: (context, snapshot) { if (snapshot.hasData) { switch (snapshot.data) { case SignalRConnectionState.connected: return Row( children: [ Container( width: 8, height: 8, decoration: const BoxDecoration( color: Colors.green, shape: BoxShape.circle, ), ), AppStyle.hGap8, const Text( '已连接', style: TextStyle( fontSize: 14, ), ), ], ); case SignalRConnectionState.disconnected: return Row( children: [ Container( width: 8, height: 8, decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), ), AppStyle.hGap8, const Text( '断开连接', style: TextStyle( fontSize: 14, ), ), ], ); default: return const Row( children: [ SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, ), ), Text( '连接中', style: TextStyle( fontSize: 14, ), ), ], ); } } return const SizedBox(); }, ), ), ], ), body: ListView( padding: AppStyle.edgeInsetsA12.copyWith(top: 0), children: [ Visibility( visible: controller.roomId.isEmpty, child: SettingsCard( child: ListTile( visualDensity: VisualDensity.compact, leading: const Icon(Remix.timer_line), title: Obx( () => Text( "${controller.countDown.value}秒后房间将会自动关闭", style: const TextStyle(fontSize: 14), ), ), ), ), ), Padding( padding: AppStyle.edgeInsetsA12, child: Text( "房间号", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Obx( () => ListTile( contentPadding: AppStyle.edgeInsetsL12, title: SelectableText( controller.currentRoomId.value, style: Theme.of(context).textTheme.titleLarge, ), trailing: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon( Icons.copy, size: 20, ), onPressed: () { Utils.copyToClipboard(controller.currentRoomId.value); }, ), IconButton( icon: const Icon( Icons.qr_code, size: 20, ), onPressed: () { controller.showQRInfo(); }, ), AppStyle.hGap4, ], ), ), ), ), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 12), child: Text( "同步数据至其他设备", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Remix.heart_line), title: const Text("发送关注列表"), trailing: const Icon(Icons.chevron_right), onTap: () { controller.syncFollow(); }, ), AppStyle.divider, ListTile( leading: const Icon(Icons.history), title: const Text("发送观看记录"), trailing: const Icon(Icons.chevron_right), onTap: () { controller.syncHistory(); }, ), AppStyle.divider, ListTile( leading: const Icon(Remix.shield_keyhole_line), title: const Text("发送弹幕屏蔽词"), trailing: const Icon(Icons.chevron_right), onTap: () { controller.syncBlockedWord(); }, ), AppStyle.divider, ListTile( leading: const Icon(Remix.account_circle_line), title: const Text("发送哔哩哔哩账号"), trailing: const Icon(Icons.chevron_right), onTap: () { controller.syncBiliAccount(); }, ), ], ), ), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 12), child: Text( "已连接设备", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Obx( () => ListView.separated( padding: EdgeInsets.zero, itemCount: controller.roomUsers.length, shrinkWrap: true, separatorBuilder: (context, index) => AppStyle.divider, physics: const NeverScrollableScrollPhysics(), itemBuilder: (BuildContext context, int index) { var user = controller.roomUsers[index]; return ListTile( visualDensity: VisualDensity.compact, leading: SizedBox( width: 48, height: 48, child: Center( child: buildIcon(user.platform), ), ), title: Text.rich( TextSpan( text: user.shortId, style: const TextStyle(fontSize: 16), children: user.isCreator! ? [ WidgetSpan( child: Container( margin: AppStyle.edgeInsetsL4, padding: AppStyle.edgeInsetsH4, decoration: BoxDecoration( border: Border.all(color: Colors.blue), borderRadius: BorderRadius.circular(4), ), child: const Text( "创建者", style: TextStyle( color: Colors.blue, fontSize: 12, ), ), ), ), ] : null, ), ), subtitle: Text("${user.app} - v${user.version}"), trailing: Visibility( visible: controller.signalR.hubConnection?.connectionId == user.connectionId, child: const Text( "本机", ), ), ); }, ), ), ) ], ), ); } Widget buildIcon(String platform) { if (platform == "android") { return const Icon(Remix.android_line); } else if (platform == "ios") { return const Icon(Remix.apple_line); } else if (platform == "tv") { return const Icon(Remix.tv_2_line); } else if (platform == "windows") { return const Icon(Remix.microsoft_fill); } else if (platform == "xbox") { return const Icon(Remix.xbox_line); } else if (platform == "macos") { return const Icon(Remix.mac_line); } else if (platform == "linux") { return const Icon(Remix.ubuntu_line); } else { return const Icon(Remix.device_line); } } } ================================================ FILE: simple_live_app/lib/modules/sync/remote_sync/webdav/remote_sync_webdav_config_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/modules/sync/remote_sync/webdav/remote_sync_webdav_controller.dart'; import 'package:simple_live_app/widgets/none_border_circular_textfield.dart'; class RemoteSyncWebDAVConfigPage extends StatefulWidget { const RemoteSyncWebDAVConfigPage({super.key}); @override State createState() => _RemoteSyncWebDAVConfigPageState(); } class _RemoteSyncWebDAVConfigPageState extends State { late TextEditingController _urlController; late TextEditingController _userNameController; late TextEditingController _passwordController; @override void initState() { _urlController = TextEditingController(); _userNameController = TextEditingController(); _passwordController = TextEditingController(); super.initState(); } @override void dispose() { _urlController.dispose(); _userNameController.dispose(); _passwordController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("WebDAV账号配置"), centerTitle: true, actions: [ IconButton( icon: const Icon(Icons.help_outline), onPressed: () { Utils.showInformationHelpDialog( content: [ const Text("此功能可以将您的数据备份到 WebDAV 服务器中或者进行数据恢复.\n"), const Text( "WebDAV 服务器地址请以 http:// 或 https:// 开头,如坚果云(点击复制):"), Padding( padding: const EdgeInsets.symmetric(vertical: 16), child: InkWell( onTap: () { Clipboard.setData(const ClipboardData( text: "https://dav.jianguoyun.com/dav/")); SmartDialog.showToast("复制成功"); }, child: const Text("https://dav.jianguoyun.com/dav/"), ), ), ], ); }, ), ], ), body: GetX( builder: (controller) { return SingleChildScrollView( child: Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ NoneBorderCircularTextField( editingController: _urlController, labelText: "WebDAV服务器地址", hintText: "请以http:// 或 http:// 开头", prefixIcon: const Icon(Icons.public), trailing: InkWell( child: const Icon( Icons.cancel, size: 20, ), onTap: () => _urlController.clear(), ), ), NoneBorderCircularTextField( editingController: _userNameController, labelText: "账号", prefixIcon: const Icon(Icons.account_circle), trailing: InkWell( child: const Icon( Icons.cancel, size: 20, ), onTap: () => _userNameController.clear()), ), NoneBorderCircularTextField( editingController: _passwordController, labelText: "密码", prefixIcon: const Icon(Icons.lock), obscureText: controller.passwordVisible.value, trailing: Row( mainAxisSize: MainAxisSize.min, children: [ InkWell( child: const Icon( Icons.cancel, size: 20, ), onTap: () => _passwordController.clear(), ), AppStyle.hGap12, InkWell( child: controller.passwordVisible.value ? const Icon(Icons.visibility_off) : const Icon(Icons.visibility), onTap: () { controller.changePasswordVisible(); }, ), ], ), ), Padding( padding: const EdgeInsets.only(top: 5, bottom: 15), child: MaterialButton( minWidth: double.infinity, color: Theme.of(context).primaryColor, focusElevation: 0, elevation: 0, highlightElevation: 4, height: 40, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(8.0)), ), child: const Text( "登录", style: TextStyle(color: Colors.white), ), onPressed: () { controller.doWebDAVLogin( _urlController.text, _userNameController.text, _passwordController.text, ); }, ), ) ], ), ), ); }, ), ); } } ================================================ FILE: simple_live_app/lib/modules/sync/remote_sync/webdav/remote_sync_webdav_controller.dart ================================================ import 'dart:convert'; import 'dart:io'; import 'dart:isolate'; import 'dart:typed_data'; import 'package:archive/archive.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/app/event_bus.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/app/utils/archive.dart'; import 'package:simple_live_app/app/utils/document.dart'; import 'package:simple_live_app/models/db/follow_user.dart'; import 'package:simple_live_app/models/db/follow_user_tag.dart'; import 'package:simple_live_app/models/db/history.dart'; import 'package:simple_live_app/requests/webdav_client.dart'; import 'package:simple_live_app/services/bilibili_account_service.dart'; import 'package:simple_live_app/services/db_service.dart'; import 'package:simple_live_app/services/douyin_account_service.dart'; import 'package:simple_live_app/services/follow_service.dart'; import 'package:simple_live_app/services/local_storage_service.dart'; import 'package:simple_live_app/services/migration_service.dart'; class RemoteSyncWebDAVController extends BaseController { // ui var passwordVisible = true.obs; // ui-用户选择是否同步 var isSyncFollows = true.obs; var isSyncHistories = true.obs; var isSyncBlockWord = true.obs; var isSyncAccount = true.obs; var isSyncSetting = true.obs; late DAVClient davClient; var user = "--".obs; var lastRecoverTime = "--".obs; var lastUploadTime = "--".obs; var uri = ""; var password = ""; var webDavBackupDirectory = "/simple_live_app".obs; final _userFollowJsonName = 'SimpleLive_follows.json'; final _userHistoriesJsonName = 'SimpleLive_histories.json'; final _userBlockedWordJsonName = 'SimpleLive_blocked_word.json'; final _userAccountJsonName = 'SimpleLive_bilibili_account.json'; final _userTagsJsonName = 'SimpleLive_Tags.json'; final _userSettingsJsonName = 'SimpleLive_Settings.json'; @override void onInit() { doWebDAVInit(); super.onInit(); } void setWebDavBackupDirectory({required String newDirectory}) { if (newDirectory == webDavBackupDirectory.value) { return; } // 防呆 final filePathCheck = RegExp(r'^/[^/]+$'); if (!filePathCheck.hasMatch(newDirectory)) { SmartDialog.showToast("请输入正确的文件路径"); return; } webDavBackupDirectory.value = newDirectory; LocalStorageService.instance.setValue( LocalStorageService.kWebDAVDirectory, webDavBackupDirectory.value, ); // 重定义/应该单例化 davClient = DAVClient( uri, user.value, password, webDAVDirectory: webDavBackupDirectory.value, ); } // webDAV 逻辑 // 初始化webDAV void doWebDAVInit() { uri = LocalStorageService.instance .getValue(LocalStorageService.kWebDAVUri, ""); if (uri.isEmpty) { notLogin.value = true; } else { user.value = LocalStorageService.instance .getValue(LocalStorageService.kWebDAVUser, ""); password = LocalStorageService.instance .getValue(LocalStorageService.kWebDAVPassword, ""); webDavBackupDirectory.value = LocalStorageService.instance.getValue( LocalStorageService.kWebDAVDirectory, "/simple_live_app", ); davClient = DAVClient( uri, user.value, password, webDAVDirectory: webDavBackupDirectory.value, ); // 从未同步过默认为最新数据 lastRecoverTime.value = Utils.parseTime( DateTime.fromMillisecondsSinceEpoch( LocalStorageService.instance.getValue( LocalStorageService.kWebDAVLastRecoverTime, DateTime.now().millisecondsSinceEpoch, ), ), ); lastUploadTime.value = Utils.parseTime( DateTime.fromMillisecondsSinceEpoch( LocalStorageService.instance.getValue( LocalStorageService.kWebDAVLastUploadTime, DateTime.now().millisecondsSinceEpoch, ), ), ); checkIsLogin(); } } // 检查webDAV登录状态 Future checkIsLogin() async { try { // 返回登录结果 bool value = await davClient.pingCompleter.future; notLogin.value = !value; } catch (e) { Log.e("$e", StackTrace.current); notLogin.value = true; } } // WebDAV登录 void doWebDAVLogin( String webDAVUri, String webDAVUser, String webDAVPassword) async { // 确认登录 davClient = DAVClient(webDAVUri, webDAVUser, webDAVPassword); await checkIsLogin(); if (!notLogin.value) { // 保存到本地 LocalStorageService.instance .setValue(LocalStorageService.kWebDAVUri, webDAVUri); LocalStorageService.instance .setValue(LocalStorageService.kWebDAVUser, webDAVUser); user.value = webDAVUser; LocalStorageService.instance .setValue(LocalStorageService.kWebDAVPassword, webDAVPassword); Get.back(); SmartDialog.showToast("登录成功!"); } else { SmartDialog.showToast("WebDAV账号密码验证失败,请重新输入!"); } } // WebDAV登出 @override Future onLogout() async { var result = await Utils.showAlertDialog("确定要登出WebDAV账号?", title: "退出登录"); if (result) { // 清除本地账号数据 LocalStorageService.instance.setValue(LocalStorageService.kWebDAVUri, ""); LocalStorageService.instance .setValue(LocalStorageService.kWebDAVUser, ""); LocalStorageService.instance .setValue(LocalStorageService.kWebDAVPassword, ""); notLogin.value = true; } } // webDAV上传到云端 Future doWebDAVUpload() async { SmartDialog.showLoading(msg: "正在上传到云端"); _backupData().then((value) async { SmartDialog.dismiss(); if (value.isNotEmpty) { var result = await davClient.backup(Uint8List.fromList(value)); if (result) { SmartDialog.showToast("上传成功"); DateTime uploadTime = DateTime.now(); lastUploadTime.value = Utils.parseTime(uploadTime); LocalStorageService.instance.setValue( LocalStorageService.kWebDAVLastUploadTime, uploadTime.millisecondsSinceEpoch); } else { Log.e("备份失败", StackTrace.current); SmartDialog.showToast("上传失败"); } } else { SmartDialog.showToast("上传失败"); } }); } // 备份所有数据 Future> _backupData() async { final archive = Archive(); List zipBytes = []; // 获取本地备份路径 var dir = (await getApplicationSupportDirectory()).path; var profile = Directory(join(dir, 'backup')); if (!profile.existsSync()) { profile.createSync(); } try { // archive.add(filepath, data_map) 会导致文件损坏 // follows var userFollowList = DBService.instance.getFollowList(); var dataFollowsMap = { 'data': userFollowList.map((e) => e.toJson()).toList() }; final userFollowJsonFile = File(join(profile.path, _userFollowJsonName)); await userFollowJsonFile.writeAsString(jsonEncode(dataFollowsMap)); // 用户自定义标签 var userTagsList = DBService.instance.getFollowTagList(); var dataTagsMap = {'data': userTagsList.map((e) => e.toJson()).toList()}; var userTagsJsonFile = File(join(profile.path, _userTagsJsonName)); await userTagsJsonFile.writeAsString(jsonEncode(dataTagsMap)); // histories var userHistoriesList = DBService.instance.getHistories(); var dataHistoriesMap = { 'data': userHistoriesList.map((e) => e.toJson()).toList() }; final userHistoriesJsonFile = File(join(profile.path, _userHistoriesJsonName)); await userHistoriesJsonFile.writeAsString(jsonEncode(dataHistoriesMap)); // blocked_word var userShieldList = AppSettingsController.instance.shieldList; var dataShieldListMap = {'data': userShieldList.toList()}; final userBlockedWordJsonFile = File(join(profile.path, _userBlockedWordJsonName)); await userBlockedWordJsonFile .writeAsString(jsonEncode(dataShieldListMap)); // bilibili_account var userAccountCookieMap = { 'data': { 'cookie': BiliBiliAccountService.instance.cookie, 'douyin_cookie': DouyinAccountService.instance.cookie } }; final accountJsonFile = File(join(profile.path, _userAccountJsonName)); await accountJsonFile.writeAsString(jsonEncode(userAccountCookieMap)); await userTagsJsonFile.writeAsString(jsonEncode(dataTagsMap)); // 全量备份用户设置,为修改包名无痛迁移数据做准备 // v1.8.3 修改为按平台备份/恢复用户设置 var settingList = LocalStorageService.instance.settingsBox.toMap(); settingList.remove(LocalStorageService.kHiveDbVer); var dataSettingListMap = { "data": { LocalStorageService.kHiveDbVer: AppSettingsController.instance.dbVer, Platform.operatingSystem: settingList, }, }; final settingJsonFile = File(join(profile.path, _userSettingsJsonName)); await settingJsonFile.writeAsString(jsonEncode(dataSettingListMap)); // 遍历profile路径下的所有文件压缩 archive.addDirectoryToArchive(profile.path, profile.path); final zipEncoder = ZipEncoder(); zipBytes = zipEncoder.encode(archive); profile.clearSync(); } catch (e) { Log.logPrint(e); SmartDialog.showToast("备份失败:$e"); } return zipBytes; } // webDAV恢复到本地 void doWebDAVRecovery() async { SmartDialog.showLoading(msg: "正在恢复到本地"); List data; try{ data = await davClient.recovery(); }catch(e,s){ Log.e("WebDAV恢复失败:$e", s); SmartDialog.dismiss(); SmartDialog.showToast('同步失败'); return; } final archive = await Isolate.run(() { final zipDecoder = ZipDecoder(); return zipDecoder.decodeBytes(data); }); for (ArchiveFile file in archive) { await _recovery(file); } // 旧版本备份需要迁移 MigrationService.migrateDataByVersion(); SmartDialog.dismiss(); SmartDialog.showToast('同步完成'); DateTime recoverTime = DateTime.now(); lastRecoverTime.value = Utils.parseTime(recoverTime); LocalStorageService.instance.setValue( LocalStorageService.kWebDAVLastRecoverTime, recoverTime.millisecondsSinceEpoch); } // todo: 后续迁出实现无感同步 Future _recovery(ArchiveFile file) async { if (file.isFile && file.name.endsWith('.json')) { var jsonString = utf8.decode(file.content); var jsonData = json.decode(jsonString)['data']; // 同步follows if (file.name == _userFollowJsonName && isSyncFollows.value) { // 修改为多端一致 try { // 清空本地关注列表 await DBService.instance.followBox.clear(); for (var item in jsonData) { var user = FollowUser.fromJson(item); await DBService.instance.followBox.put(user.id, user); } Log.i('已同步关注用户列表'); } catch (e) { Log.e('同步关注用户列表失败: $e', StackTrace.current); } } else if (file.name == _userHistoriesJsonName && isSyncHistories.value) { try { for (var item in jsonData) { var history = History.fromJson(item); // 完全同步机制 await DBService.instance.addOrUpdateHistory(history); } Log.i('已同步用户观看历史记录'); } catch (e) { Log.e('同步用户观看历史记录失败: $e', StackTrace.current); } } else if (file.name == _userBlockedWordJsonName && isSyncBlockWord.value) { try { for (var keyword in jsonData) { AppSettingsController.instance.addShieldList(keyword.trim()); } Log.i('已同步用户屏蔽词'); } catch (e) { Log.e('同步用户屏蔽词失败:$e', StackTrace.current); } } else if (file.name == _userAccountJsonName && isSyncAccount.value) { try { var biliCookie = jsonData['cookie']; BiliBiliAccountService.instance.setCookie(biliCookie); BiliBiliAccountService.instance.loadUserInfo(); var douyinCookie = jsonData['douyin_cookie']; DouyinAccountService.instance.setCookie(douyinCookie); DouyinAccountService.instance.loadUserInfo(); Log.i('已同步用户平台账号'); } catch (e) { Log.e('同步用户平台账号失败:$e', StackTrace.current); } } else if (file.name == _userSettingsJsonName && isSyncSetting.value) { try { var platform = Platform.operatingSystem; if ((jsonData as Map).containsKey(platform)) { jsonDecode(jsonData[platform]).forEach( (key, value) { LocalStorageService.instance.setValue(key, value); }, ); } else { Log.i("缺少$platform对应平台用户设置备份"); } // 低于v1.8.5需要升级数据 LocalStorageService.instance.setValue( LocalStorageService.kHiveDbVer, (jsonData as Map).containsKey(LocalStorageService.kHiveDbVer) ? jsonData[LocalStorageService.kHiveDbVer] : "10805", ); Log.i('已同步用户设置'); } catch (e) { Log.e("同步用户设置失败:$e", StackTrace.current); } } else if (file.name == _userTagsJsonName && isSyncFollows.value) { try { // 标签功能和关注具有依赖关系,必须同时同步 // 清空本地标签列表 await DBService.instance.clearFollowTag(); for (var item in jsonData) { var tag = FollowUserTag.fromJson(item); await DBService.instance.tagBox.put(tag.id, tag); // 插入之后验证 var insertedTag = DBService.instance.tagBox.get(tag.id); Log.i('Inserted tag: ${insertedTag?.tag}'); } // 数据校准 await FollowService.instance.followUserAllDataCheck(); Log.i('已同步用户自定义标签'); // 确保tag同步完成后,更新关注列表 EventBus.instance.emit(Constant.kUpdateFollow, 0); } catch (e) { Log.e('同步用户自定义标签失败:$e', StackTrace.current); } } else { return; } } else { Log.i('不是正确的文件名'); } } // ui控制--密码可见控制 void changePasswordVisible() { passwordVisible.value = !passwordVisible.value; } void changeIsSyncFollows() { isSyncFollows.value = !isSyncFollows.value; } void changeIsSyncHistories() { isSyncHistories.value = !isSyncHistories.value; } void changeIsSyncBlockWord() { isSyncBlockWord.value = !isSyncBlockWord.value; } void changeIsSyncAccount() { isSyncAccount.value = !isSyncAccount.value; } void changeIsSyncSetting() { isSyncSetting.value = !isSyncSetting.value; } } ================================================ FILE: simple_live_app/lib/modules/sync/remote_sync/webdav/remote_sync_webdav_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/modules/sync/remote_sync/webdav/remote_sync_webdav_controller.dart'; import 'package:simple_live_app/routes/route_path.dart'; import 'package:simple_live_app/widgets/settings/settings_card.dart'; class RemoteSyncWebDAVPage extends GetView { const RemoteSyncWebDAVPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("WebDAV同步"), ), body: ListView( padding: AppStyle.edgeInsetsA12, children: [ SettingsCard( child: Obx( () => Column( children: controller.notLogin.value ? [ ListTile( title: const Text("点击登录"), leading: const Icon(Icons.login), subtitle: const Text("登录后可以同步您的所有数据"), trailing: const Icon(Icons.chevron_right), onTap: () { Get.toNamed(RoutePath.kRemoteSyncWebDavConfig); }, ), ] : [ ListTile( title: const Text("已登录"), leading: const Icon(Icons.cloud_circle_outlined), subtitle: Text(controller.user.value), trailing: const Icon(Icons.logout), onTap: () { controller.onLogout(); }, ), AppStyle.divider, Obx( () => ListTile( title: const Text("云端备份目录"), leading: const Icon(Icons.drive_folder_upload), subtitle: Text(controller.webDavBackupDirectory.value), trailing: const Icon(Icons.chevron_right), onTap: _showEditBackupDirectory, ), ), AppStyle.divider, ListTile( title: const Text("上传到云端"), subtitle: Text("上次上传:${controller.lastUploadTime}"), leading: const Icon(Icons.cloud_upload_outlined), trailing: const Icon(Icons.chevron_right), onTap: () { controller.doWebDAVUpload(); }, ), AppStyle.divider, ListTile( title: const Text("恢复到本地"), subtitle: Text("上次恢复:${controller.lastRecoverTime}"), leading: const Icon(Icons.cloud_download_outlined), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.settings), onPressed: showSetting, ), const Icon(Icons.chevron_right), ], ), onTap: () { controller.doWebDAVRecovery(); }, onLongPress: showSetting, ), ], ), ), ), ], ), ); } void showSetting() { Utils.showBottomSheet( title: '同步选项', child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ AppStyle.divider, Obx( () => CheckboxListTile( secondary: const Icon(Remix.heart_line), title: const Text("同步关注列表"), value: controller.isSyncFollows.value, controlAffinity: ListTileControlAffinity.trailing, onChanged: (value) => controller.changeIsSyncFollows(), ), ), AppStyle.divider, Obx( () => CheckboxListTile( secondary: const Icon(Icons.history), title: const Text("同步播放历史记录"), value: controller.isSyncHistories.value, controlAffinity: ListTileControlAffinity.trailing, onChanged: (value) => controller.changeIsSyncHistories(), ), ), AppStyle.divider, Obx( () => CheckboxListTile( secondary: const Icon(Remix.shield_keyhole_line), title: const Text("同步屏蔽字"), value: controller.isSyncBlockWord.value, controlAffinity: ListTileControlAffinity.trailing, onChanged: (value) => controller.changeIsSyncBlockWord(), ), ), AppStyle.divider, Obx( () => CheckboxListTile( secondary: const Icon(Remix.account_circle_line), title: const Text("同步用户平台账号"), value: controller.isSyncAccount.value, controlAffinity: ListTileControlAffinity.trailing, onChanged: (value) => controller.changeIsSyncAccount(), ), ), AppStyle.divider, Obx( () => CheckboxListTile( secondary: const Icon(Remix.user_settings_line), title: const Text("同步用户设置"), value: controller.isSyncSetting.value, controlAffinity: ListTileControlAffinity.trailing, onChanged: (value) => controller.changeIsSyncSetting(), ), ), //todo: 启动应用时自动同步数据 ], ), ); } void _showEditBackupDirectory() async { var directory = await Utils.showEditTextDialog( controller.webDavBackupDirectory.value, title: "修改远程备份文件夹", ); if (directory == null || directory.isEmpty) { return; } controller.setWebDavBackupDirectory(newDirectory: directory); } } ================================================ FILE: simple_live_app/lib/modules/sync/sync_page.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/routes/route_path.dart'; import 'package:simple_live_app/widgets/settings/settings_card.dart'; class SyncPage extends StatelessWidget { const SyncPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("数据同步"), actions: [ Visibility( visible: GetPlatform.isAndroid || GetPlatform.isIOS, child: TextButton.icon( onPressed: () async { var result = await Get.toNamed(RoutePath.kSyncScan); if (result == null || result.isEmpty) { return; } if (result.length == 5) { Get.toNamed(RoutePath.kRemoteSyncRoom, arguments: result); } else { Get.toNamed(RoutePath.kLocalSync, arguments: result); } }, icon: const Icon(Remix.qr_scan_line), label: const Text("扫一扫"), ), ), ], ), body: ListView( padding: AppStyle.edgeInsetsA12, children: [ Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 0), child: Text( "远程同步", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( children: [ ListTile( title: const Text("WebDAV"), leading: const Icon(Icons.cloud_upload_outlined), subtitle: const Text("通过WebDAV同步数据"), trailing: const Icon(Icons.chevron_right), onTap: () { Get.toNamed(RoutePath.kRemoteSyncWebDav); }, ), ], ), ), Padding( padding: AppStyle.edgeInsetsA12.copyWith(top: 24), child: Text( "局域网同步", style: Get.textTheme.titleSmall, ), ), SettingsCard( child: Column( children: [ ListTile( title: const Text("局域网同步"), subtitle: const Text("在局域网内同步数据"), leading: const Icon(Remix.device_line), trailing: const Icon(Icons.chevron_right), onTap: () { Get.toNamed(RoutePath.kLocalSync); }, ), ], ), ), ], ), ); } } ================================================ FILE: simple_live_app/lib/requests/common_request.dart ================================================ import 'dart:convert'; import 'package:simple_live_app/models/version_model.dart'; import 'package:simple_live_app/requests/http_client.dart'; /// 通用的请求 class CommonRequest { Future checkUpdate() async { try { return await checkUpdateGitMirror(); } catch (e) { return await checkUpdateJsDelivr(); } } /// 检查更新 Future checkUpdateGitMirror() async { var result = await HttpClient.instance.getJson( "https://raw.gitmirror.com/slotsun/dart_simple_live/master/assets/app_version.json", queryParameters: { "ts": DateTime.now().millisecondsSinceEpoch, }, ); if (result is Map) { return VersionModel.fromJson(result as Map); } return VersionModel.fromJson(json.decode(result)); } /// 检查更新 Future checkUpdateJsDelivr() async { var result = await HttpClient.instance.getJson( "https://cdn.jsdelivr.net/gh/slotsun/dart_simple_live@master/assets/app_version.json", queryParameters: { "ts": DateTime.now().millisecondsSinceEpoch, }, ); if (result is Map) { return VersionModel.fromJson(result as Map); } return VersionModel.fromJson(json.decode(result)); } } ================================================ FILE: simple_live_app/lib/requests/custom_log_interceptor.dart ================================================ import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_core/simple_live_core.dart'; class CustomLogInterceptor extends Interceptor { @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { options.extra["ts"] = DateTime.now().millisecondsSinceEpoch; super.onRequest(options, handler); } @override void onError(DioException err, ErrorInterceptorHandler handler) { var time = DateTime.now().millisecondsSinceEpoch - err.requestOptions.extra["ts"]; if (!kReleaseMode) { Log.e('''【HTTP请求错误-${err.type}】 耗时:${time}ms ${err.message} Request Method:${err.requestOptions.method} Response Code:${err.response?.statusCode} Request URL:${err.requestOptions.uri} Request Query:${err.requestOptions.queryParameters} Request Data:${err.requestOptions.data} Request Headers:${err.requestOptions.headers} Response Headers:${err.response?.headers.map} Response Data:${err.response?.data}''', err.stackTrace); } else { CoreLog.e('''[HTTP Error] [${err.type}] [Time:${time}ms] ${err.message} Request Method:${err.requestOptions.method} Response Code:${err.response?.statusCode} Request URL:${err.requestOptions.uri} Request Query:${err.requestOptions.queryParameters} Request Data:${err.requestOptions.data} Request Headers:${_maskHeader(err.requestOptions.headers)} Response Headers:${err.response?.headers.map} Response Data:${err.response?.data}''', err.stackTrace); } super.onError(err, handler); } @override void onResponse(Response response, ResponseInterceptorHandler handler) { var time = DateTime.now().millisecondsSinceEpoch - response.requestOptions.extra["ts"]; if (!kReleaseMode) { Log.i( '''【HTTP请求响应】 耗时:${time}ms Request Method:${response.requestOptions.method} Request Code:${response.statusCode} Request URL:${response.requestOptions.uri} Request Query:${response.requestOptions.queryParameters} Request Data:${response.requestOptions.data} Request Headers:${response.requestOptions.headers} Response Headers:${response.headers.map} Response Data:${response.data}''', ); } else { CoreLog.i( "[HTTP Response] [time:${time}ms] [${response.statusCode}] ${response.requestOptions.uri}", ); } super.onResponse(response, handler); } // Header脱敏 String _maskHeader(Map header) { var result = {}; header.forEach((key, value) { var k = key.toLowerCase(); if (k == "cookie" || k == "authorization") { result[key] = "******"; } else { result[key] = value; } }); return result.toString(); } } ================================================ FILE: simple_live_app/lib/requests/http_client.dart ================================================ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:simple_live_app/requests/custom_log_interceptor.dart'; import 'package:simple_live_app/requests/http_error.dart'; class HttpClient { static HttpClient? _httpUtil; static HttpClient get instance { _httpUtil ??= HttpClient(); return _httpUtil!; } late Dio dio; HttpClient() { dio = Dio( BaseOptions( connectTimeout: const Duration(seconds: 20), receiveTimeout: const Duration(seconds: 20), sendTimeout: const Duration(seconds: 20), ), ); dio.interceptors.add(CustomLogInterceptor()); } /// Get请求,返回String /// * [url] 请求链接 /// * [queryParameters] 请求参数 /// * [cancel] 任务取消Token Future getText( String url, { Map? queryParameters, Map? header, CancelToken? cancel, }) async { try { queryParameters ??= {}; header ??= {}; var result = await dio.get( url, queryParameters: queryParameters, options: Options( responseType: ResponseType.plain, headers: header, ), cancelToken: cancel, ); return result.data; } catch (e) { if (e is DioException && e.type == DioExceptionType.badResponse) { throw HttpError(e.message ?? "", statusCode: e.response?.statusCode ?? 0); } else { throw HttpError("发送GET请求失败"); } } } /// Get请求,返回Map /// * [url] 请求链接 /// * [queryParameters] 请求参数 /// * [cancel] 任务取消Token Future getJson( String url, { Map? queryParameters, Map? header, CancelToken? cancel, }) async { try { queryParameters ??= {}; header ??= {}; var result = await dio.get( url, queryParameters: queryParameters, options: Options( responseType: ResponseType.json, headers: header, ), cancelToken: cancel, ); return result.data; } catch (e) { if (e is DioException && e.type == DioExceptionType.badResponse) { throw HttpError(e.message ?? "", statusCode: e.response?.statusCode ?? 0); } else { throw HttpError("发送GET请求失败"); } } } /// Get请求,返回Response /// * [url] 请求链接 /// * [queryParameters] 请求参数 /// * [cancel] 任务取消Token Future> get( String url, { Map? queryParameters, Map? header, CancelToken? cancel, }) async { try { queryParameters ??= {}; header ??= {}; var result = await dio.get( url, queryParameters: queryParameters, options: Options( responseType: ResponseType.json, headers: header, ), cancelToken: cancel, ); return result; } catch (e) { if (e is DioException && e.type == DioExceptionType.badResponse) { throw HttpError(e.message ?? "", statusCode: e.response?.statusCode ?? 0); } else { throw HttpError("发送GET请求失败"); } } } /// Post请求,返回Map /// * [url] 请求链接 /// * [queryParameters] 请求参数 /// * [data] 内容 /// * [cancel] 任务取消Token Future postJson( String url, { Map? queryParameters, dynamic data, Map? header, bool formUrlEncoded = false, CancelToken? cancel, }) async { try { queryParameters ??= {}; header ??= {}; data ??= {}; var result = await dio.post( url, queryParameters: queryParameters, data: data, options: Options( responseType: ResponseType.json, headers: header, contentType: formUrlEncoded ? Headers.formUrlEncodedContentType : null, ), cancelToken: cancel, ); return result.data; } catch (e) { if (e is DioException && e.type == DioExceptionType.badResponse) { throw HttpError(e.message ?? "", statusCode: e.response?.statusCode ?? 0); } else { throw HttpError("发送POST请求失败"); } } } /// Head请求,返回Response /// * [url] 请求链接 /// * [queryParameters] 请求参数 /// * [cancel] 任务取消Token Future head( String url, { Map? queryParameters, Map? header, CancelToken? cancel, }) async { try { queryParameters ??= {}; header ??= {}; var result = await dio.head( url, queryParameters: queryParameters, options: Options( headers: header, receiveDataWhenStatusError: true, ), cancelToken: cancel, ); return result; } catch (e) { if (e is DioException && e.type == DioExceptionType.badResponse) { return e.response!; } else { throw HttpError("发送HEAD请求失败"); } } } /// DOWNLOAD 文件 /// * [url] 下载链接 /// * [savePath] 保存路径 /// * [header] 可选请求头 /// * [cancel] 任务取消Token /// * [onProgress] 下载进度 0~1 Future download( String url, String savePath, { Map? header, CancelToken? cancel, Function(int value, int progress)? onReceiveProgress, }) async { header ??= {}; final tempPath = "$savePath.part"; final tempFile = File(tempPath); try { if (!await tempFile.exists()) { await tempFile.create(recursive: true); } final response = await dio.download( url, tempPath, cancelToken: cancel, onReceiveProgress: onReceiveProgress, options: Options( headers: header, ), ); if (response.statusCode == 200 || response.statusCode == 206) { // 下载完成重命名临时文件 return await tempFile.rename(savePath); } else { throw HttpError("下载失败", statusCode: response.statusCode ?? 0); } } on DioException catch (e) { if (CancelToken.isCancel(e)) { throw HttpError("下载已取消"); } else if (e.type == DioExceptionType.badResponse) { throw HttpError(e.message ?? "", statusCode: e.response?.statusCode ?? 0); } else { throw HttpError("下载请求失败"); } } } } ================================================ FILE: simple_live_app/lib/requests/http_error.dart ================================================ class HttpError extends Error { /// 错误码 final int statusCode; /// 错误信息 final String message; HttpError( this.message, { this.statusCode = 0, }); @override String toString() { if (statusCode != 0) { return statusCodeToString(statusCode); } return message; } String statusCodeToString(int statusCode) { switch (statusCode) { case 400: return "错误的请求(400)"; case 401: return "无权限访问资源(401)"; case 403: return "无权限访问资源(403)"; case 404: return "服务器找不到请求的资源(404)"; case 500: return "服务器出现错误(500)"; case 502: return "服务器出现错误(502)"; case 503: return "服务器出现错误(503)"; default: return "连接服务器失败,请稍后再试($statusCode)"; } } } ================================================ FILE: simple_live_app/lib/requests/sync_client_request.dart ================================================ import 'package:simple_live_app/models/sync_client_info_model.dart'; import 'package:simple_live_app/requests/http_client.dart'; import 'package:simple_live_app/services/sync_service.dart'; class SyncClientRequest { Future getClientInfo(SyncClinet client) async { var url = "http://${client.address}:${client.port}/info"; var data = await HttpClient.instance.getJson(url); return SyncClientInfoModel.fromJson(data); } Future syncFollow( SyncClinet client, dynamic body, { bool overlay = false, }) async { var url = "http://${client.address}:${client.port}/sync/follow"; var data = await HttpClient.instance.postJson( url, data: body, queryParameters: { 'overlay': overlay ? '1' : '0', }, ); if (data["status"]) { return true; } else { throw data["message"]; } } Future syncTag( SyncClinet client, dynamic body, { bool overlay = false, }) async { var url = "http://${client.address}:${client.port}/sync/tag"; var data = await HttpClient.instance.postJson( url, data: body, queryParameters: { 'overlay': overlay ? '1' : '0', }, ); if (data["status"]) { return true; } else { throw data["message"]; } } Future syncHistory( SyncClinet client, dynamic body, { bool overlay = false, }) async { var url = "http://${client.address}:${client.port}/sync/history"; var data = await HttpClient.instance.postJson( url, data: body, queryParameters: { 'overlay': overlay ? '1' : '0', }, ); if (data["status"]) { return true; } else { throw data["message"]; } } Future syncBlockedWord( SyncClinet client, dynamic body, { bool overlay = false, }) async { var url = "http://${client.address}:${client.port}/sync/blocked_word"; var data = await HttpClient.instance.postJson( url, data: body, queryParameters: { 'overlay': overlay ? '1' : '0', }, ); if (data["status"]) { return true; } else { throw data["message"]; } } Future syncBiliAccount(SyncClinet client, String cookie) async { var url = "http://${client.address}:${client.port}/sync/account/bilibili"; var data = await HttpClient.instance.postJson( url, data: { "cookie": cookie, }, ); if (data["status"]) { return true; } else { throw data["message"]; } } } ================================================ FILE: simple_live_app/lib/requests/webdav_client.dart ================================================ import 'dart:async'; import 'dart:typed_data'; import 'package:simple_live_app/app/log.dart'; import 'package:webdav_client/webdav_client.dart'; class DAVClient { late Client client; Completer pingCompleter = Completer(); // 强制统一 String root = "/simple_live_app"; DAVClient(String webDAVUri, String webDAVUser, String webDAVPassword, {String webDAVDirectory = "/simple_live_app"}) { client = newClient( webDAVUri, user: webDAVUser, password: webDAVPassword, ); client.setHeaders( { 'accept-charset': 'utf-8', 'Content-Type': 'text/xml', }, ); client.setConnectTimeout(8000); client.setSendTimeout(8000); client.setReceiveTimeout(8000); pingCompleter.complete(_ping()); root = webDAVDirectory; } Future _ping() async { try { await client.ping(); return true; } catch (_) { return false; } } String get backupFile => "$root/backup.zip"; Future backup(Uint8List data) async { try { await client.mkdir(root); await client.write(backupFile, data); } catch (e, s) { Log.e("WebDAV上传失败: $e", s); return false; } return true; } Future> recovery() async { await client.mkdir(root); final data = await client.read(backupFile); return data; } } ================================================ FILE: simple_live_app/lib/routes/app_analytics_observer.dart ================================================ import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:flutter/cupertino.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; class AppAnalyticsObserver extends NavigatorObserver { static AppAnalyticsObserver get observer => AppAnalyticsObserver(); void _log(Route? route) { if (route == null) return; if (!AppSettingsController.instance.firebaseEnable.value) { return; } final name = route.settings.name; if (name == null) return; FirebaseAnalytics.instance.logScreenView( screenName: name, screenClass: name, ); } @override void didPush(Route route, Route? previousRoute) { _log(route); super.didPush(route, previousRoute); } @override void didPop(Route route, Route? previousRoute) { _log(previousRoute); super.didPop(route, previousRoute); } @override void didReplace({Route? newRoute, Route? oldRoute}) { _log(newRoute); super.didReplace(newRoute: newRoute, oldRoute: oldRoute); } } ================================================ FILE: simple_live_app/lib/routes/app_navigation.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/models/sync_client_info_model.dart'; import 'package:simple_live_app/routes/route_path.dart'; import 'package:simple_live_app/services/bilibili_account_service.dart'; import 'package:simple_live_app/services/sync_service.dart'; import 'package:simple_live_core/simple_live_core.dart'; import 'package:simple_live_app/models/db/follow_user.dart'; /// APP页面跳转封装 /// * 需要参数的页面都应使用此类 /// * 如不需要参数,可以使用Get.toNamed class AppNavigator { /// 跳转至分类详情 static void toCategoryDetail( {required Site site, required LiveSubCategory category}) { Get.toNamed(RoutePath.kCategoryDetail, arguments: [site, category]); } /// 跳转至直播间 static void toLiveRoomDetail( {required Site site, required String roomId}) async { if (site.id == Constant.kBiliBili && !BiliBiliAccountService.instance.logined.value && AppSettingsController.instance.bilibiliLoginTip.value) { var result = await Utils.showAlertDialog( "哔哩哔哩需要登录才能观看高清直播,是否前往登录?", title: "登录哔哩哔哩", actions: [ TextButton( onPressed: () { AppSettingsController.instance.setBiliBiliLoginTip(false); Get.back(result: false); }, child: const Text("不再提示"), ), ], ); if (result == true) { await toBiliBiliLogin(); if (!BiliBiliAccountService.instance.logined.value) { SmartDialog.showToast("未完成登录"); } } } Get.toNamed(RoutePath.kLiveRoomDetail, arguments: site, parameters: { "roomId": roomId, }); } /// 跳转至哔哩哔哩登录 static Future toBiliBiliLogin() async { if (Platform.isAndroid || Platform.isIOS) { await Get.toNamed(RoutePath.kBiliBiliWebLogin); } else { await Get.toNamed(RoutePath.kBiliBiliQRLogin); } } /// 跳转至同步设备 static Future toSyncDevice( SyncClinet client, SyncClientInfoModel info) async { await Get.toNamed( RoutePath.kLocalSyncDevice, arguments: { "client": client, "info": info, }, ); } /// 跳转至关注用户信息详情 static void toFollowInfo(FollowUser follow) { Get.toNamed(RoutePath.kFollowInfo, arguments: follow); } } ================================================ FILE: simple_live_app/lib/routes/app_pages.dart ================================================ // ignore_for_file: prefer_inlined_adds import 'package:get/get.dart'; import 'package:simple_live_app/modules/category/detail/category_detail_controller.dart'; import 'package:simple_live_app/modules/category/detail/category_detail_page.dart'; import 'package:simple_live_app/modules/follow_user/follow_app_setting/follow_app_settings_controller.dart'; import 'package:simple_live_app/modules/follow_user/follow_app_setting/follow_app_settings_page.dart'; import 'package:simple_live_app/modules/follow_user/follow_info_setting/follow_info_controller.dart'; import 'package:simple_live_app/modules/follow_user/follow_info_setting/follow_info_page.dart'; import 'package:simple_live_app/modules/follow_user/follow_user_controller.dart'; import 'package:simple_live_app/modules/follow_user/follow_user_page.dart'; import 'package:simple_live_app/modules/indexed/indexed_controller.dart'; import 'package:simple_live_app/modules/live_room/live_room_controller.dart'; import 'package:simple_live_app/modules/live_room/live_room_page.dart'; import 'package:simple_live_app/modules/mine/account/account_controller.dart'; import 'package:simple_live_app/modules/mine/account/account_page.dart'; import 'package:simple_live_app/modules/mine/account/bilibili/qr_login_controller.dart'; import 'package:simple_live_app/modules/mine/account/bilibili/qr_login_page.dart'; import 'package:simple_live_app/modules/mine/account/bilibili/web_login_controller.dart'; import 'package:simple_live_app/modules/mine/account/bilibili/web_login_page.dart'; import 'package:simple_live_app/modules/mine/history/history_controller.dart'; import 'package:simple_live_app/modules/mine/history/history_page.dart'; import 'package:simple_live_app/modules/mine/parse/parse_controller.dart'; import 'package:simple_live_app/modules/mine/parse/parse_page.dart'; import 'package:simple_live_app/modules/search/search_controller.dart'; import 'package:simple_live_app/modules/search/search_page.dart'; import 'package:simple_live_app/modules/settings/appstyle_settings/appstyle_setting_page.dart'; import 'package:simple_live_app/modules/settings/auto_exit_settings_page.dart'; import 'package:simple_live_app/modules/settings/danmu_settings_page.dart'; import 'package:simple_live_app/modules/settings/danmu_shield/danmu_shield_controller.dart'; import 'package:simple_live_app/modules/settings/danmu_shield/danmu_shield_page.dart'; import 'package:simple_live_app/modules/settings/indexed_settings/indexed_settings_controller.dart'; import 'package:simple_live_app/modules/settings/indexed_settings/indexed_settings_page.dart'; import 'package:simple_live_app/modules/settings/other/other_settings_controller.dart'; import 'package:simple_live_app/modules/settings/other/other_settings_page.dart'; import 'package:simple_live_app/modules/settings/play_settings_page.dart'; import 'package:simple_live_app/modules/sync/local_sync/device/sync_device_controller.dart'; import 'package:simple_live_app/modules/sync/local_sync/device/sync_device_page.dart'; import 'package:simple_live_app/modules/sync/local_sync/local_sync_controller.dart'; import 'package:simple_live_app/modules/sync/local_sync/local_sync_page.dart'; import 'package:simple_live_app/modules/sync/local_sync/scan_qr/sync_scan_qr_controller.dart'; import 'package:simple_live_app/modules/sync/local_sync/scan_qr/sync_scan_qr_page.dart'; import 'package:simple_live_app/modules/sync/remote_sync/room/remote_sync_room_controller.dart'; import 'package:simple_live_app/modules/sync/remote_sync/room/remote_sync_room_page.dart'; import 'package:simple_live_app/modules/sync/remote_sync/webdav/remote_sync_webdav_config_page.dart'; import 'package:simple_live_app/modules/sync/remote_sync/webdav/remote_sync_webdav_controller.dart'; import 'package:simple_live_app/modules/sync/remote_sync/webdav/remote_sync_webdav_page.dart'; import 'package:simple_live_app/modules/sync/sync_page.dart'; import '../modules/indexed/indexed_page.dart'; import 'route_path.dart'; class AppPages { AppPages._(); static final routes = [ // 首页 GetPage( name: RoutePath.kIndex, page: () => const IndexedPage(), bindings: [ BindingsBuilder.put(() => IndexedController()), //BindingsBuilder.put(() => HomeController()), ], ), // 观看记录 GetPage( name: RoutePath.kHistory, page: () => const HistoryPage(), bindings: [ BindingsBuilder.put(() => HistoryController()), ], ), // 关注用户 GetPage( name: RoutePath.kFollowUser, page: () => const FollowUserPage(), bindings: [ BindingsBuilder.put(() => FollowUserController()), ], ), // 搜索 GetPage( name: RoutePath.kSearch, page: () => const SearchPage(), bindings: [ BindingsBuilder.put(() => AppSearchController()), ], ), //分类详情 GetPage( name: RoutePath.kCategoryDetail, page: () => const CategoryDetailPage(), binding: BindingsBuilder.put( () => CategoryDetailController( site: Get.arguments[0], subCategory: Get.arguments[1], ), ), ), //直播间 GetPage( name: RoutePath.kLiveRoomDetail, page: () => const LiveRoomPage(), binding: BindingsBuilder.put( () => LiveRoomController( pSite: Get.arguments, pRoomId: Get.parameters["roomId"] ?? "", ), ), ), //弹幕设置 GetPage( name: RoutePath.kSettingsDanmu, page: () => const DanmuSettingsPage(), ), //外观设置 GetPage( name: RoutePath.kAppstyleSetting, page: () => const AppStyleSettingPage()), //播放设置 GetPage( name: RoutePath.kSettingsPlay, page: () => const PlaySettingsPage(), ), //自动关闭 GetPage( name: RoutePath.kSettingsAutoExit, page: () => const AutoExitSettingsPage(), ), //工具箱 GetPage( name: RoutePath.kTools, page: () => const ParsePage(), bindings: [ BindingsBuilder.put(() => ParseController()), ], ), //关键词屏蔽 GetPage( name: RoutePath.kSettingsDanmuShield, page: () => const DanmuShieldPage(), bindings: [ BindingsBuilder.put(() => DanmuShieldController()), ], ), //主页设置 GetPage( name: RoutePath.kSettingsIndexed, page: () => const IndexedSettingsPage(), bindings: [ BindingsBuilder.put(() => IndexedSettingsController()), ], ), //账号设置 GetPage( name: RoutePath.kSettingsAccount, page: () => const AccountPage(), bindings: [ BindingsBuilder.put(() => AccountController()), ], ), //哔哩哔哩Web登录 GetPage( name: RoutePath.kBiliBiliWebLogin, page: () => const BiliBiliWebLoginPage(), bindings: [ BindingsBuilder.put(() => BiliBiliWebLoginController()), ], ), //哔哩哔哩二维码登录 GetPage( name: RoutePath.kBiliBiliQRLogin, page: () => const BiliBiliQRLoginPage(), bindings: [ BindingsBuilder.put(() => BiliBiliQRLoginController()), ], ), // 数据同步 GetPage( name: RoutePath.kSync, page: () => const SyncPage(), ), // 本地同步 GetPage( name: RoutePath.kLocalSync, page: () => const LocalSyncPage(), bindings: [ BindingsBuilder.put( () => LocalSyncController( Get.arguments ?? "", ), ), ], ), //扫码 GetPage( name: RoutePath.kSyncScan, page: () => const SyncScanQRPage(), bindings: [ BindingsBuilder.put(() => SyncScanQRControlelr()), ], ), //同步设备 GetPage( name: RoutePath.kLocalSyncDevice, page: () => const SyncDevicePage(), bindings: [ BindingsBuilder.put( () => SyncDeviceController( client: Get.arguments['client'], info: Get.arguments['info'], ), ), ], ), //远程同步-房间 GetPage( name: RoutePath.kRemoteSyncRoom, page: () => const RemoteSyncRoomPage(), bindings: [ BindingsBuilder.put( () => RemoteSyncRoomController(Get.arguments ?? ""), ), ], ), //远程同步-WebDAV GetPage( name: RoutePath.kRemoteSyncWebDav, page: () => const RemoteSyncWebDAVPage(), bindings: [ BindingsBuilder.put( () => RemoteSyncWebDAVController(), ), ], ), //远程同步-WebDAVConfig GetPage( name: RoutePath.kRemoteSyncWebDavConfig, page: () => const RemoteSyncWebDAVConfigPage(), ), //其他设置 GetPage( name: RoutePath.kSettingsOther, page: () => const OtherSettingsPage(), bindings: [ BindingsBuilder.put(() => OtherSettingsController()), ], ), //关注设置 GetPage( name: RoutePath.kSettingsFollow, page: () => const FollowSettingsPage(), bindings: [ BindingsBuilder.put(() => FollowAppSettingsController()), ], ), // 关注用户-信息详情 GetPage( name: RoutePath.kFollowInfo, page: () => const FollowInfoPage(), bindings: [ BindingsBuilder.put(() => FollowInfoController()), ], ), ]; } ================================================ FILE: simple_live_app/lib/routes/route_path.dart ================================================ /// 路由路径 class RoutePath { /// 首页 static const kIndex = "/index"; /// 搜索 static const kSearch = "/search"; /// 分类详情 static const kCategoryDetail = "/category/detail"; /// 直播间 static const kLiveRoomDetail = "/room/detail"; /// 弹幕设置 static const kSettingsDanmu = "/settings/danmu"; /// 定时关闭设置 static const kSettingsAutoExit = "/settings/auto_exit"; /// 直播间设置 static const kSettingsPlay = "/settings/play"; /// 弹幕关键词屏蔽 static const kSettingsDanmuShield = "/settings/danmu/shield"; /// 其他设置 static const kSettingsOther = "/settings/other"; /// 赞助 static const kSponsor = "/sponsor"; /// 历史记录 static const kHistory = "/user/history"; /// 我的关注 static const kFollowUser = "/user/follow"; /// 关注用户-信息详情 static const kFollowInfo = "/user/follow/info"; /// 工具箱 static const kTools = "/other/tools"; /// 主页设置 static const kSettingsIndexed = "/settings/indexed"; /// 外观设置 static const kAppstyleSetting = "/settings/appstyle"; /// 账号管理 static const kSettingsAccount = "/settings/account"; /// 关注设置 static const kSettingsFollow = "/settings/follow"; /// BiliBili Web登录 static const kBiliBiliWebLogin = "/settings/account/bilibili/web_login"; /// BiliBili 二维码登录 static const kBiliBiliQRLogin = "/settings/account/bilibili/qr_login"; /// 数据同步 static const kLocalSync = "/local_sync"; /// 数据同步 static const kSync = "/sync"; /// 扫描 static const kSyncScan = "/sync/scan"; /// 同步设备 static const kLocalSyncDevice = "/sync/device"; /// 远程同步-房间 static const kRemoteSyncRoom = "/remote_sync/room"; /// 远程同步-WebDAVConfig static const kRemoteSyncWebDav = "/remote_sync/webDAV"; /// 远程同步-WebDAVConfig static const kRemoteSyncWebDavConfig = "/remote_sync/webDAVConfig"; /// 测试页面 static const kTest = "/test"; } ================================================ FILE: simple_live_app/lib/services/bilibili_account_service.dart ================================================ import 'dart:io'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/models/account/bilibili_user_info_page.dart'; import 'package:simple_live_app/requests/http_client.dart'; import 'package:simple_live_app/services/local_storage_service.dart'; import 'package:simple_live_core/simple_live_core.dart'; class BiliBiliAccountService extends GetxService { static BiliBiliAccountService get instance => Get.find(); var logined = false.obs; var cookie = ""; var uid = 0; var name = "未登录".obs; @override void onInit() { cookie = LocalStorageService.instance .getValue(LocalStorageService.kBilibiliCookie, ""); logined.value = cookie.isNotEmpty; loadUserInfo(); super.onInit(); } Future loadUserInfo() async { if (cookie.isEmpty) { return; } try { var result = await HttpClient.instance.getJson( "https://api.bilibili.com/x/member/web/account", header: { "Cookie": cookie, }, ); if (result["code"] == 0) { var info = BiliBiliUserInfoModel.fromJson(result["data"]); name.value = info.uname ?? "未登录"; uid = info.mid ?? 0; setSite(); } else { SmartDialog.showToast("哔哩哔哩登录已失效,请重新登录"); logout(); } } catch (e) { SmartDialog.showToast("获取哔哩哔哩用户信息失败,可前往账号管理重试"); } } void setSite() { var site = (Sites.allSites[Constant.kBiliBili]!.liveSite as BiliBiliSite); site.userId = uid; site.cookie = cookie; } void setCookie(String cookie) { this.cookie = cookie; LocalStorageService.instance .setValue(LocalStorageService.kBilibiliCookie, cookie); logined.value = cookie.isNotEmpty; } void logout() async { cookie = ""; uid = 0; name.value = "未登录"; setSite(); LocalStorageService.instance .setValue(LocalStorageService.kBilibiliCookie, ""); logined.value = false; if (Platform.isAndroid || Platform.isIOS) { CookieManager cookieManager = CookieManager.instance(); await cookieManager.deleteAllCookies(); } } } ================================================ FILE: simple_live_app/lib/services/core_api_service.dart ================================================ // 用于解耦simple_live_core // 候选方案,目前项目强耦合core,需要逐步剥离 import 'package:get/get.dart'; /// api: /// 获取All-Sites: getAllSites()->List /// 获取平台首页推荐: site.getRecommends()->List /// 获取平台分类:site.getCategories()->List() /// 获取平台分类详情: site.getCategoryDetail()->List /// 更新关注列表状态: site.roomId.getFollowLiveState()->bool /// 获取房间详情: site.roomId.getRoomDetail()->roomDetail /// 获取房间播放链列表:site.roomId.getRoomPlayList()->List /// 获取房间清晰度列表: site.roomId.getRoomPlayQualities()->List /// 获取房间弹幕:site.roomId.getRoomDanmaku()->websocket /// 获取平台cookie有效性检测:site.getUserInfo("cookie")->bool /// /// 实现逻辑: /// 1: 命令模式-example: /// core.api( /// { /// site: "bilibli", /// roomId:"", /// func: getRecommends, /// func-params:{...} /// } /// ) /// /// 2。 builder设计-example: /// builder /// .setSite("douyu") /// .setRoomId("") /// .setFuncName("getRecommendation") /// .getParam("limit":"50") /// .build(); /// 均需要多重设计,部分model需要copy并实现json接口 /// 参数列表,函数列表字典 /// api字典 class CoreApiBuilder { String? _site; String? _roomId; String? _funcName; final Map _params = {}; CoreApiBuilder(); CoreApiBuilder site(String site) { _site = site; return this; } CoreApiBuilder room(String roomId) { _roomId = roomId; return this; } CoreApiBuilder func(String funcName) { _funcName = funcName; return this; } CoreApiBuilder param(String key, dynamic value) { _params[key] = value; return this; } CoreApiBuilder params(Map params) { _params.addAll(params); return this; } Future> build() { if (_site == null) { throw Exception("site must be set."); } if (_funcName == null) { throw Exception("funcName must be set."); } return CoreDispatcher.dispatch( site: _site!, funcName: _funcName!, roomId: _roomId, params: _params, ); } } class CoreDispatcher { static Future> dispatch({ required String site, required String funcName, String? roomId, Map? params, }) async { //缺少参数检查 return { 'site': site, 'roomId': roomId, 'funcName': funcName, 'func-params': params ?? {}, }; } } class CoreApiService extends GetxService { CoreApiBuilder get builder => CoreApiBuilder(); // demo void exampleUsage() { builder.site("bilibili").func("getRecommends").param("limit", 1).build(); // builder // .site("douyu") // .room("12345") // .func("getRoomDetail") // .build(); // var res = core.api(arg=builder) // 参数类型选择器->json to model // simple_live_core 应该实现 参数解析器->统一返回json->调用端:json to model 实现解耦 // 目的: 自建 api 或 ffi 调用 // 该工作非必要,暂且备份为草稿 } } ================================================ FILE: simple_live_app/lib/services/db_service.dart ================================================ import 'dart:async'; import 'package:fractional_indexing_dart/fractional_indexing_dart.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:simple_live_app/models/db/follow_user.dart'; import 'package:simple_live_app/models/db/follow_user_tag.dart'; import 'package:simple_live_app/models/db/history.dart'; import 'package:collection/collection.dart'; class DBService extends GetxService { static DBService get instance => Get.find(); late Box historyBox; late Box followBox; late Box tagBox; Future init() async { historyBox = await Hive.openBox("History"); followBox = await Hive.openBox("FollowUser"); tagBox = await Hive.openBox("FollowUserTag"); } Future clearFollowTag() async { await tagBox.clear(); } bool getFollowTagExist(String id) { return tagBox.containsKey(id); } List getFollowTagList() { return tagBox.values.toList(); } Future updateFollowTag(FollowUserTag followTag) async { await tagBox.put(followTag.id, followTag); } Future addFollowTag(String tag) async { // 查找数据库中是否已存在 存在则直接返回 if (getFollowTagExistByTag(tag)) { return getFollowTag(tag)!; } String? lastKey = tagBox.keys.lastOrNull; final String uniqueId = FractionalIndexing.generateKeyBetween(lastKey, null); final followUserTag = FollowUserTag(id: uniqueId, tag: tag, userId: []); await tagBox.put(uniqueId, followUserTag); return followUserTag; } Future deleteFollowTag(String id) async { await tagBox.delete(id); } FollowUserTag? getFollowTag(String tag) { return tagBox.values.firstWhereOrNull((item) => item.tag == tag); } // 判断tag名称是否重复 bool getFollowTagExistByTag(String tag) { return tagBox.values.any((item) => item.tag == tag); } bool getFollowExist(String id) { return followBox.containsKey(id); } List getFollowList() { return followBox.values.toList(); } Future addFollow(FollowUser follow) async { await followBox.put(follow.id, follow); } Future deleteFollow(String id) async { await followBox.delete(id); } History? getHistory(String id) { if (historyBox.containsKey(id)) { return historyBox.get(id); } return null; } Future addOrUpdateHistory(History history) async { await historyBox.put(history.id, history); } Future delHistory(String id) async { await historyBox.delete(id); } List getHistories() { var his = historyBox.values.toList(); his.sort((a, b) => b.updateTime.compareTo(a.updateTime)); return his; } } ================================================ FILE: simple_live_app/lib/services/douyin_account_service.dart ================================================ import 'dart:io'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/models/account/douyin_user_info.dart'; import 'package:simple_live_app/services/local_storage_service.dart'; import 'package:simple_live_core/simple_live_core.dart'; class DouyinAccountService extends GetxService { static DouyinAccountService get instance => Get.find(); final site = (Sites.allSites[Constant.kDouyin]!.liveSite as DouyinSite); var logined = false.obs; var cookie = ""; var name = "未登录".obs; var hlsFirst = false; @override void onInit() { hlsFirst = LocalStorageService.instance .getValue(LocalStorageService.kDouyinHlsFirst, false); _setSiteHlsFirst(); cookie = LocalStorageService.instance .getValue(LocalStorageService.kDouyinCookie, ""); logined.value = cookie.isNotEmpty; loadUserInfo(); super.onInit(); } // 设置DouyinHlsFirst void _setSiteHlsFirst() { site.hlsFirst = hlsFirst; } Future loadUserInfo() async { if (cookie.isEmpty) { return; } try { final data = await site.getUserInfoByCookie(cookie); if (data.isEmpty) { SmartDialog.showToast("抖音登录已失效,请重新登录"); logout(); return; } var info = DouyinUserInfoModel.fromJson(data); name.value = info.nickname!; logined.value = true; _setSiteCookie(); } catch (e) { SmartDialog.showToast("获取抖音登录用户信息失败,可前往账号管理重试"); } } void _setSiteCookie() { if (cookie.isEmpty) { site.headers.remove("cookie"); } else { site.headers["cookie"] = cookie; } } void setCookie(String cookie) { this.cookie = cookie; LocalStorageService.instance .setValue(LocalStorageService.kDouyinCookie, cookie); } void logout() async { cookie = ""; LocalStorageService.instance .setValue(LocalStorageService.kDouyinCookie, ""); logined.value = false; name.value = "未登录"; _setSiteCookie(); if (Platform.isAndroid || Platform.isIOS) { CookieManager cookieManager = CookieManager.instance(); await cookieManager.deleteAllCookies(); } } } ================================================ FILE: simple_live_app/lib/services/firebase_service.dart ================================================ // only for Android import 'dart:ui'; import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; class FirebaseService extends GetxService { static FirebaseService get instance => Get.find(); @override onInit() { bool e = AppSettingsController.instance.firebaseEnable.value; setCrashlytics(e); super.onInit(); } static Future setCrashlytics(bool enable) async { await FirebaseAnalytics.instance.setAnalyticsCollectionEnabled(enable); if (enable) { FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError; PlatformDispatcher.instance.onError = (error, stack) { FirebaseCrashlytics.instance.recordError(error, stack, fatal: true); return true; }; }else{ FlutterError.onError = FlutterError.dumpErrorToConsole; } } } ================================================ FILE: simple_live_app/lib/services/follow_service.dart ================================================ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:fractional_indexing_dart/fractional_indexing_dart.dart'; import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; import 'package:pinyin/pinyin.dart'; import 'package:pool/pool.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/event_bus.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/app/utils/duration_2_str_utils.dart'; import 'package:simple_live_app/app/utils/dynamic_sort.dart'; import 'package:simple_live_app/app/utils/string_normalizer.dart'; import 'package:simple_live_app/models/db/follow_user.dart'; import 'package:simple_live_app/models/db/follow_user_tag.dart'; import 'package:simple_live_app/models/db/history.dart'; import 'package:simple_live_app/services/db_service.dart'; import 'package:simple_live_core/simple_live_core.dart'; import 'package:synchronized/synchronized.dart'; class FollowService extends GetxService { StreamSubscription? subscription; static FollowService get instance => Get.find(); final StreamController _updatedListController = StreamController.broadcast(); Stream get updatedListStream => _updatedListController.stream; /// 关注用户列表 RxList followList = RxList(); /// 直播中的用户列表 RxList liveList = RxList(); /// 未直播的用户列表 RxList notLiveList = RxList(); /// 用户自定义的tag RxList followTagList = RxList(); /// 当前tag的用户列表 RxList curTagFollowList = RxList(); /// 线程安全 final _lock = Lock(); /// 已经更新状态的数量 var updatedCount = 0; /// 是否正在更新 var updating = false.obs; Timer? updateTimer; int _totalToUpdate = 0; int _refreshCycle = 0; @override void onInit() { subscription = EventBus.instance.listen(Constant.kUpdateFollow, (data) { if (data is History) { updateFollowHistory(data); } else { loadData(updateStatus: false); } }); initTimer(); super.onInit(); } void updateTagName(FollowUserTag followUserTag, String newTagName) { final FollowUserTag newTag = followUserTag.copyWith(tag: newTagName); updateFollowUserTag(newTag); // update item's tag when update tagName for (var i in newTag.userId) { var follow = DBService.instance.followBox.get(i); if (follow != null) { follow.tag = newTagName; addFollow(follow); } } } Future updateFollowUserTag(FollowUserTag tag) async { if (tag.tag == '全部') { return; } await DBService.instance.updateFollowTag(tag); // 查找并修改 var index = followTagList.indexWhere((oTag) => oTag.id == tag.id); followTagList[index] = tag; } Future addFollowUserTag(String tag) async { // 判断待添加tag是否已存在,存在则return if (followTagList.any((item) => item.tag == tag)) { SmartDialog.showToast("标签名重复,修改失败"); return; } FollowUserTag item = await DBService.instance.addFollowTag(tag); followTagList.add(item); } Future removeFollowUserTag(FollowUserTag tag) async { // 将tag下的所有follow设置为全部 for (var i in tag.userId) { var follow = DBService.instance.followBox.get(i); if (follow != null) { follow.tag = "全部"; FollowService.instance.addFollow(follow); } } followTagList.remove(tag); await DBService.instance.deleteFollowTag(tag.id); } // 获取用户自定义标签列表 void getAllTagList() { var list = DBService.instance.getFollowTagList(); followTagList.assignAll(list); } /// 获取包含“全部”的标签选项列表 List getTagOptionsWithAll() { return [ FollowUserTag(id: '0', tag: '全部', userId: []), ...followTagList, ]; } /// 为关注项设置标签(统一逻辑) void setFollowTag(FollowUser item, FollowUserTag targetTag) { // 当前标签对象(可能为“全部”且不在 followTagList 中) FollowUserTag? currentTag; if (item.tag != '全部') { for (final t in followTagList) { if (t.tag == item.tag) { currentTag = t; break; } } } // 从旧标签移除 if (currentTag != null) { currentTag.userId.remove(item.id); DBService.instance.updateFollowTag(currentTag); } // 添加到新标签(跳过“全部”) if (targetTag.tag != '全部') { // targetTag来源于UI选项,需定位真实对象 FollowUserTag? tar; for (final t in followTagList) { if (t.tag == targetTag.tag) { tar = t; break; } } if (tar != null) { tar.userId.addIf(!tar.userId.contains(item.id), item.id); DBService.instance.updateFollowTag(tar); } } // 更新FollowUser本身 item.tag = targetTag.tag; addFollow(item); } void filterDataByTag(FollowUserTag tag) { // 清空curTagFollowList curTagFollowList.clear(); // 用一个新的列表来存储需要删除的 userId List toRemove = []; for (var id in tag.userId) { if (followList.any((x) => x.id == id)) { // 找到对应的 followUser 添加到 curTagFollowList curTagFollowList.add(followList.firstWhere((x) => x.id == id)); } else { // 标记要删除的 id toRemove.add(id); } } // 在遍历结束后统一移除不在 followList 中的 id tag.userId.removeWhere((id) => toRemove.contains(id)); // 更新数据库 if (toRemove.isNotEmpty) { DBService.instance.updateFollowTag(tag); } listSortByMethod(curTagFollowList, AppSettingsController.instance.followSortMethod.value); } Future updateFollowTagOrder(FollowUserTag oldTag,FollowUserTag newTag) async { await DBService.instance.deleteFollowTag(oldTag.id); await DBService.instance.updateFollowTag(newTag); loadData(updateStatus: false); } // 添加关注 void addFollow(FollowUser follow) { // follow变动过程中romanName统一变化 String romanName = ""; if(follow.remark !=null && follow.remark!.isNotEmpty){ romanName = PinyinHelper.getShortPinyin(follow.romanName!); }else{ romanName = PinyinHelper.getShortPinyin(follow.userName); } follow.romanName = romanName.normalize(); DBService.instance.addFollow(follow); } // 取消关注 Future removeFollowUser(String id) async { await DBService.instance.deleteFollow(id); } // 判断关注是否存在 bool getFollowExist(String id) { return DBService.instance.getFollowExist(id); } // 更新关注的历史记录 void updateFollowHistory(History history) { var follow = followList.where((follow) => follow.id == history.id).firstOrNull; if (follow == null) { return; } else { follow.watchDuration = history.watchDuration; addFollow(follow); } Log.i("已更新当前播放的观看时长:${follow.watchDuration}"); } void initTimer() { if (AppSettingsController.instance.autoUpdateFollowEnable.value) { updateTimer?.cancel(); _refreshCycle = 0; updateTimer = Timer.periodic( Duration( minutes: AppSettingsController.instance.autoUpdateFollowDuration.value), (timer) { CoreLog.i("Update Follow Timer - Cycle: $_refreshCycle"); loadData(updateStatus: true, cycle: _refreshCycle); _refreshCycle = (_refreshCycle + 1) % 2; // 2-cycle rotation }, ); } else { updateTimer?.cancel(); } } Future loadData({bool updateStatus = true, int? cycle}) async { var list = DBService.instance.getFollowList(); getAllTagList(); if (list.isEmpty) { updating.value = false; followList.assignAll(list); liveList.clear(); notLiveList.clear(); _updatedListController.add(0); return; } if (updateStatus) { followList.assignAll(list); startUpdateStatus(cycle: cycle); }else{ _updatedListController.add(0); } } void multiRoundPriority() { final historyList = DBService.instance.getHistories(); final Map historyRankMap = { for (var i = 0; i < historyList.length; i++) historyList[i].id: i }; final int maxRank = historyList.isNotEmpty ? historyList.length : 1; Duration maxDuration = const Duration(); for (var user in followList) { final duration = user.watchDuration!.toDuration(); if (duration > maxDuration) { maxDuration = duration; } } final double maxDurationInSeconds = maxDuration.inSeconds > 0 ? maxDuration.inSeconds.toDouble() : 1.0; // 简单线性加权组合算法,目前认定观看时长和最近观看时间权重一致 // 如果用户历史行为序列非常长:可替换为时间衰减 + 观看时长加权 followList.sort((a, b) { // 静态权重 const double wDuration = 0.5; const double wRecency = 0.5; // 在线降权,离线增权 const double wOnline = 0.3; const double wOffline = 1 - wOnline; // 动态权重 double normDurationA = a.watchDuration!.toDuration().inSeconds.toDouble() / maxDurationInSeconds; int rankA = historyRankMap[a.id] ?? maxRank; double normRecencyA = (maxRank - rankA).toDouble() / maxRank; double scoreA = ((wDuration * normDurationA) + (wRecency * normRecencyA)) * (a.liveStatus.value == 2 ? wOnline : wOffline); double normDurationB = b.watchDuration!.toDuration().inSeconds.toDouble() / maxDurationInSeconds; int rankB = historyRankMap[b.id] ?? maxRank; double normRecencyB = (maxRank - rankB).toDouble() / maxRank; double scoreB = ((wDuration * normDurationB) + (wRecency * normRecencyB)) * (b.liveStatus.value == 2 ? wOnline : wOffline); return scoreB.compareTo(scoreA); }); } void startUpdateStatus({int? cycle}) async { List usersToUpdate; final totalUsers = followList.length; final douyinCount = followList.where((x) => x.siteId == 'douyin').length; //tips: 噪音用户画像(高风险平台:90%; 多次手刷; 单高关注数>50; 频繁切直播间; 不登录反复高危操作; 移动宽带用户; 反复关注取消; 多ip切换; 特殊地区风控; 多端在线请求; 黑号) if (cycle != null && (totalUsers > 100 || douyinCount > 50)) { // 简单28 final topNCount = (totalUsers * 0.2).round(); // Top 20% final bottomNCount = (totalUsers * 0.2).round(); // Bottom 20% final middlePartEndIndex = totalUsers - bottomNCount; multiRoundPriority(); final topNUsers = followList.sublist(0, topNCount); final middleUsers = followList.sublist(topNCount, middlePartEndIndex); if (cycle == 0) { usersToUpdate = topNUsers; CoreLog.i( "Update Follow: Cycle 0, updating top ${usersToUpdate.length}/$totalUsers users."); } else { usersToUpdate = [...topNUsers, ...middleUsers]; CoreLog.i( "Update Follow: Cycle 1, updating top+middle ${usersToUpdate.length}/$totalUsers users."); } } else { usersToUpdate = List.from(followList); if (cycle != null) { CoreLog.i( "Update Follow: List <= 100, updating all ${usersToUpdate.length} users."); } } _totalToUpdate = usersToUpdate.length; updatedCount = 0; updating.value = true; if (_totalToUpdate == 0) { updating.value = false; filterData(); return; } var threadCount = AppSettingsController.instance.updateFollowThreadCount.value; var pool = Pool(threadCount); var tasks = []; for (var user in usersToUpdate) { tasks.add(pool.withResource(() => updateLiveInformation(user))); } await Future.wait(tasks); await pool.close(); } Future updateLiveInformation(FollowUser item) async { try { var site = Sites.allSites[item.siteId]!; LiveRoomDetail detail = await site.liveSite.getRoomDetail(roomId: item.roomId); item.liveStatus.value = detail.status ? 2 : 1; item.cover.value = detail.status ? detail.cover : ""; item.title.value = detail.title; item.online.value = detail.online; } catch (e) { Log.logPrint(e); } finally { await _lock.synchronized(() { updatedCount++; }); if (updatedCount >= _totalToUpdate) { filterData(); updating.value = false; } } } void filterData() { liveListSort(); liveList.assignAll(followList.where((x) => x.liveStatus.value == 2)); notLiveList.assignAll(followList.where((x) => x.liveStatus.value == 1)); _updatedListController.add(0); } void liveListSort(){ listSortByMethod(followList, AppSettingsController.instance.followSortMethod.value); liveList.assignAll(followList.where((x) => x.liveStatus.value == 2)); notLiveList.assignAll(followList.where((x) => x.liveStatus.value == 1)); } void listSortByMethod(List list, SortMethod sortMethod) { var liveCondition = SortCondition( valueGetter: (item) => item.liveStatus.value, // Rx ascending: false, ); var watchDurationCondition = SortCondition( valueGetter: (item) => item.watchDuration?.toDuration() ?? Duration.zero, ascending: false, ); var siteIdCondition = SortCondition( valueGetter: (item) { final order = AppSettingsController.instance.siteSort; // 返回索引作为 Comparable return order.indexOf(item.siteId); }, ); var recentlyCondition = SortCondition( valueGetter: (item) => item.addTime, ascending: false, ); var userNameASCCondition = SortCondition( valueGetter: (item) => item.romanName ?? "", ascending: true, ); var userNameDESCCondition = SortCondition( valueGetter: (item) => item.romanName ?? "", ascending: false, ); switch (sortMethod) { case SortMethod.watchDuration: list.dynamicSort([liveCondition, watchDurationCondition]); case SortMethod.siteId: list.dynamicSort([liveCondition, siteIdCondition]); case SortMethod.recently: list.dynamicSort([liveCondition, recentlyCondition]); case SortMethod.userNameASC: list.dynamicSort([liveCondition, userNameASCCondition]); case SortMethod.userNameDESC: list.dynamicSort([liveCondition, userNameDESCCondition]); } } void exportFile() async { if (followList.isEmpty) { SmartDialog.showToast("列表为空"); return; } try { var status = await Utils.checkStorgePermission(); if (!status) { SmartDialog.showToast("无权限"); return; } var dir = ""; if (Platform.isIOS) { dir = (await getApplicationDocumentsDirectory()).path; } else { dir = await FilePicker.platform.getDirectoryPath() ?? ""; } if (dir.isEmpty) { return; } var jsonFile = File( '$dir/SimpleLive_${DateTime.now().millisecondsSinceEpoch ~/ 1000}.json'); var jsonText = generateJson(); await jsonFile.writeAsString(jsonText); SmartDialog.showToast("已导出关注列表"); } catch (e) { Log.logPrint(e); SmartDialog.showToast("导出失败:$e"); } } void inputFile() async { try { var status = await Utils.checkStorgePermission(); if (!status) { SmartDialog.showToast("无权限"); return; } var file = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: ['json'], ); if (file == null) { return; } var jsonFile = File(file.files.single.path!); await inputJson(await jsonFile.readAsString()); SmartDialog.showToast("导入成功"); } catch (e) { Log.logPrint(e); SmartDialog.showToast("导入失败:$e"); } finally { loadData(); } } void exportText() { if (followList.isEmpty) { SmartDialog.showToast("列表为空"); return; } var content = generateJson(); Get.dialog( AlertDialog( title: const Text("导出为文本"), content: TextField( controller: TextEditingController(text: content), decoration: const InputDecoration( border: OutlineInputBorder(), ), minLines: 5, maxLines: 8, ), actions: [ TextButton( onPressed: () { Get.back(); }, child: const Text("关闭"), ), TextButton( onPressed: () { Utils.copyToClipboard(content); Get.back(); }, child: const Text("复制"), ), ], ), ); } void inputText() async { final TextEditingController textController = TextEditingController(); await Get.dialog( AlertDialog( title: const Text("从文本导入"), content: TextField( controller: textController, decoration: const InputDecoration( border: OutlineInputBorder(), hintText: "请输入内容", ), minLines: 5, maxLines: 8, ), actions: [ TextButton( onPressed: () { Get.back(); }, child: const Text("关闭"), ), TextButton( onPressed: () async { var content = await Utils.getClipboard(); if (content != null) { textController.text = content; } }, child: const Text("粘贴"), ), TextButton( onPressed: () async { if (textController.text.isEmpty) { SmartDialog.showToast("内容为空"); return; } try { await inputJson(textController.text); SmartDialog.showToast("导入成功"); Get.back(); loadData(); } catch (e) { SmartDialog.showToast("导入失败,请检查内容是否正确"); } }, child: const Text("导入"), ), ], ), ); } String generateJson() { var data = followList .map( (item) => { "siteId": item.siteId, "id": item.id, "roomId": item.roomId, "userName": item.userName, "face": item.face, "watchDuration": item.watchDuration, "addTime": item.addTime.toString(), "remark": item.remark, "romanName": item.romanName, "tag": item.tag }, ) .toList(); return jsonEncode(data); } Future inputJson(String content) async { var data = jsonDecode(content); for (var item in data) { var follow = FollowUser.fromJson(item); await DBService.instance.addFollow(follow); } await followUserAllDataCheck(); } // 数据校对 // 核心关注数据有几种错乱情况,需要进行校对,需要一定时间复核代码 // 1:未关注,但标签包含关注 // 2: 已关注,且设置标签,但标签不包含 // 3: 已关注,且设置标签,但标签不存在 // 4: 标签重复 // 5: webdav同步导致的数据错乱 // 校对思路,followList是基础数据源,tagList为索引数据,重建数据即可 // 根据此思路,可以重写文件导入导出以及webdav恢复逻辑 Future followUserAllDataCheck() async { var followUserListTemp = DBService.instance.getFollowList(); var oldTagList = DBService.instance.getFollowTagList(); final Map> tagMap = { for (var tag in oldTagList) tag.tag: [], }; // 手动添加罗马音 for (FollowUser follow in followUserListTemp) { if (follow.remark != null && follow.remark!.isNotEmpty) { var roman = PinyinHelper.getShortPinyin(follow.remark!).normalize(); follow.romanName = roman; } else { follow.romanName = PinyinHelper.getShortPinyin(follow.userName).normalize(); } await DBService.instance.addFollow(follow); } Log.i("transfer follow.name to roman is down!"); for (var follow in followUserListTemp) { if(follow.tag!="全部"){ tagMap.putIfAbsent(follow.tag, () => []).add(follow.id); } } // 落库 final Map res = {}; String? lastKey; for (var entry in tagMap.entries) { lastKey = FractionalIndexing.generateKeyBetween(lastKey, null); final followUserTag = FollowUserTag( id: lastKey, tag: entry.key, userId: entry.value, ); res[followUserTag.id] = followUserTag; } await DBService.instance.tagBox.clear(); await DBService.instance.tagBox.putAll(res); Log.i("Follow-Service: data check down,follows:${followUserListTemp.length},tags:${tagMap.length}"); } @override void onClose() { updateTimer?.cancel(); subscription?.cancel(); super.onClose(); } } ================================================ FILE: simple_live_app/lib/services/history_service.dart ================================================ import 'dart:async'; import 'package:get/get.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/event_bus.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/utils/duration_2_str_utils.dart'; import 'package:simple_live_app/models/db/history.dart'; import 'db_service.dart'; class HistoryService extends GetxService { static HistoryService get instance => Get.find(); final Stopwatch _stopwatch = Stopwatch(); var _elapsed = Duration.zero; Duration _oldWatchedDuration = Duration.zero; History? curLiveRoomHistory; //两分钟自动保存一次,防止用户直接关闭app,丢失数据 final _saveInterval = const Duration(minutes: 2); Timer? _timer; // 定时器 // 开始计时 void start(History history) { _loadHistory(history); _stopwatch.start(); _timer = Timer.periodic(_saveInterval, (timer) { _updateHistory(); }); } // reset void reset(String roomId) { _updateHistory(); _stopwatch.reset(); History? history = DBService.instance.getHistory(roomId); if(history != null){ _loadHistory(history); } } // 停止计时 void stop() { _stopwatch.stop(); _updateHistory(); _stopwatch.reset(); _elapsed = Duration.zero; // 取消定时器 _timer?.cancel(); _timer = null; curLiveRoomHistory = null; Log.i("本次观看时长:$_elapsed"); } void _loadHistory(History history) { curLiveRoomHistory = DBService.instance.getHistory(history.id); // 首次观看则创建 if (curLiveRoomHistory == null) { curLiveRoomHistory = history; DBService.instance.addOrUpdateHistory(history); } _oldWatchedDuration = curLiveRoomHistory!.duration; } // updateHistory void _updateHistory() { if (curLiveRoomHistory == null) { return; } // 累加到当前历史记录 _elapsed = _stopwatch.elapsed; Duration curTime = _oldWatchedDuration + _elapsed; Log.i( "已观看时间:${_oldWatchedDuration.toHMSString()}_增加时间:${_elapsed.toHMSString()}"); curLiveRoomHistory?.watchDuration = curTime.toHMSString(); curLiveRoomHistory?.updateTime = DateTime.now(); DBService.instance.addOrUpdateHistory(curLiveRoomHistory!); EventBus.instance.emit(Constant.kUpdateFollow, curLiveRoomHistory); } // 获取历史记录中存储的累计观看时长 String getHistoryDuration({required String followUserId}) { var historyWatchDuration = "00:00:00"; History? history = DBService.instance.getHistory(followUserId); historyWatchDuration = history?.watchDuration ?? "00:00:00"; return historyWatchDuration; } } ================================================ FILE: simple_live_app/lib/services/local_storage_service.dart ================================================ import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:simple_live_app/app/log.dart'; class LocalStorageService extends GetxService { static LocalStorageService get instance => Get.find(); /// 首次运行 static const String kFirstRun = "FirstRun"; /// 缩放模式 static const String kPlayerScaleMode = "ScaleMode"; /// 网站排序 static const String kSiteSort = "SiteSort"; /// 首页排序 static const String kHomeSort = "HomeSort"; /// 显示模式 /// * [0] 跟随系统 /// * [1] 浅色模式 /// * [2] 深色模式 static const String kThemeMode = "ThemeMode"; /// DEBUG模式 static const String kDebugModeKey = "DebugMode"; /// 弹幕大小 static const String kDanmuSize = "DanmuSize"; /// 弹幕速度 static const String kDanmuSpeed = "DanmuSpeed"; /// 弹幕区域 static const String kDanmuArea = "DanmuArea"; /// 弹幕透明度 static const String kDanmuOpacity = "DanmuOpacity"; /// 弹幕描边大小 static const String kDanmuStrokeWidth = "DanmuStrokeWidth"; /// 弹幕-屏蔽滚动 static const String kDanmuHideScroll = "DanmuHideScroll"; /// 弹幕-屏蔽底部 static const String kDanmuHideBottom = "DanmuHideBottom"; /// 弹幕-屏蔽顶部 static const String kDanmuHideTop = "DanmuHideTop"; /// 弹幕-顶部边距 static const String kDanmuTopMargin = "DanmuTopMargin"; /// 弹幕-底部边距 static const String kDanmuBottomMargin = "DanmuBottomMargin"; /// 弹幕开启 static const String kDanmuEnable = "DanmuEnable"; /// 弹幕去重 static const String kDanmakuMaskEnable = "DanmakuMaskEnable"; /// 弹幕字重 static const String kDanmuFontWeight = "DanmuFontWeight"; /// 弹幕去重参数--文本归一化 static const String kDanmuTextNormalization = "DanmuTextNormalization"; /// 弹幕去重参数--窗口大小:10s-60s static const String kDanmuWindowMs = "DanmuWindowMs"; /// 弹幕去重参数--开启去重频率限制 static const String kDanmuFrequencyControl = "DanmuFrequencyControl"; /// 弹幕去重参数--去重频率限制 static const String kDanmuMaxFrequency = "DanmuMaxFrequency"; /// 硬件解码 static const String kHardwareDecode = "HardwareDecode"; /// 聊天区文字大小 static const String kChatTextSize = "ChatTextSize"; /// 聊天区间隔 static const String kChatTextGap = "ChatTextGap"; /// 聊天区-气泡样式 static const String kChatBubbleStyle = "ChatBubbleStyle"; /// 播放清晰度,0=低,1=中,2=高 static const String kQualityLevel = "QualityLevel"; /// 蜂窝网络下播放清晰度,0=低,1=中,2=高 static const String kQualityLevelCellular = "QualityLevelCellular"; /// 开启定时关闭 static const String kAutoExitEnable = "AutoExitEnable"; /// 定时关闭时间(分钟) static const String kAutoExitDuration = "AutoExitDuration"; /// 房间内定时关闭时间(分钟) /// 需要一个不同的 key,因为用户在房间内设置的倒计时和全局的可能不同。 static const String kRoomAutoExitDuration = "RoomAutoExitDuration"; /// 播放器兼容模式 static const String kPlayerCompatMode = "PlayerCompatMode"; /// 播放器后台自动暂停 static const String kPlayerAutoPause = "PlayerAutoPause"; /// 播放器缓冲区大小 static const String kPlayerBufferSize = "PlayerBufferSize"; /// 播放器强制使用HTTPS static const String kPlayerForceHttps = "PlayerForceHttps"; /// Douyin设置hls优先 static const String kDouyinHlsFirst = "DouyinHlsFirst"; /// 自动全屏 static const String kAutoFullScreen = "AutoFullScreen"; /// 播放器音量 static const String kPlayerVolume = "PlayerVolume"; /// 小窗隐藏弹幕 static const String kPIPHideDanmu = "PIPHideDanmu"; /// 哔哩哔哩cookie static const String kBilibiliCookie = "BilibiliCookie"; /// 抖音cookie static const String kDouyinCookie = "DouyinCookie"; ///主题色 static const String kStyleColor = "kStyleColor"; ///动态取色 static const String kIsDynamic = "kIsDynamic"; /// 自定义字体 static const String kCustomFont = "CustomFont"; /// 提示哔哩哔哩登录 static const String kBilibiliLoginTip = "BilibiliLoginTip"; /// 日志记录 static const String kLogEnable = "LogEnable"; /// Firebase数据分析 static const String kFirebaseEnable = "FirebaseEnable"; /// 开启自定义播放器视频输出 static const String kCustomPlayerOutput = "CustomPlayerOutput"; /// 视频输出驱动 static const String kVideoOutputDriver = "VideoOutputDriver"; /// 视频硬件解码器 static const String kVideoHardwareDecoder = "VideoHardwareDecoder"; /// 音频输出驱动 static const String kAudioOutputDriver = "AudioOutputDriver"; /// 视频硬件解码器 static const String kVideoDoubleBuffering = "VideoDoubleBuffering"; /// 开启自动更新关注 static const String kAutoUpdateFollowEnable = "AutoUpdateFollowEnable"; /// 定时自动更新关注间隔(分钟) static const String kUpdateFollowDuration = "AutoUpdateFollowDuration"; /// 开启多线程更新关注 static const String kUpdateFollowThreadCount = "UpdateFollowThreadCount"; /// WebDAV_服务器地址 static const String kWebDAVUri = "WebDAVUri"; /// WebDAV_文件夹 static const String kWebDAVDirectory = "WebDAVDirectory"; /// WebDAV_登录账号 static const String kWebDAVUser = "WebDAVUser"; /// WebDAV_登录密码 static const String kWebDAVPassword = "kWebDAVPassword"; /// WebDAV_最后一次上传时间 static const String kWebDAVLastUploadTime = "kWebDAVLastUploadTime"; /// WebDAV_最后一次备份时间 static const String kWebDAVLastRecoverTime = "kWebDAVLastRecoverTime"; /// windows窗口size static const String kWindowX = "WindowX"; static const String kWindowY = "WindowY"; static const String kWindowWidth = "WindowWidth"; static const String kWindowHeight = "WindowHeight"; /// 关注列表排序方法 static const String kFollowSortMethod = "FollowSortMethod"; /// 关注列表样式 static const String kFollowStyleNotGrid = "FollowStyleNotGrid"; /// 数据库版本 static const String kHiveDbVer = "kHiveDbVer"; late Box settingsBox; late Box shieldBox; Future init() async { settingsBox = await Hive.openBox( "LocalStorage", ); shieldBox = await Hive.openBox( "DanmuShield", ); } T getValue(dynamic key, T defaultValue) { try { var value = settingsBox.get(key, defaultValue: defaultValue) as T; Log.d("Get LocalStorage:$key\r\n$value"); return value; } catch (e) { Log.logPrint(e); return defaultValue; } } T? getNullValue(dynamic key, T? defaultValue) { try { var value = settingsBox.get(key, defaultValue: defaultValue) as T?; Log.d("Get LocalStorage:$key\r\n$value"); return value; } catch (e) { Log.logPrint(e); return defaultValue; } } Future setValue(dynamic key, T value) async { Log.d("Set LocalStorage:$key\r\n$value"); return await settingsBox.put(key, value); } Future removeValue(dynamic key) async { Log.d("Remove LocalStorage:$key"); return await settingsBox.delete(key); } Future flush() async { return await settingsBox.flush(); } } ================================================ FILE: simple_live_app/lib/services/migration_service.dart ================================================ import 'dart:io'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/models/db/follow_user.dart'; import 'package:simple_live_app/models/db/follow_user_tag.dart'; import 'package:simple_live_app/services/db_service.dart'; import 'package:simple_live_app/services/follow_service.dart'; import 'package:simple_live_app/services/local_storage_service.dart'; class MigrationService { /// 将Hive数据迁移到Application Support static Future migrateData() async { if (Platform.isAndroid || Platform.isIOS) { return; } var hiveFileList = [ "followuser", //旧版本写错成hostiry了 "hostiry", "followusertag", "localstorage", "danmushield", ]; try { var newDir = await getApplicationSupportDirectory(); var hiveFile = File(p.join(newDir.path, "followuser.hive")); if (await hiveFile.exists()) { return; } var oldDir = await getApplicationDocumentsDirectory(); for (var element in hiveFileList) { var oldFile = File(p.join(oldDir.path, "$element.hive")); if (await oldFile.exists()) { var fileName = "$element.hive"; if (element == "hostiry") { fileName = "history.hive"; } await oldFile.copy(p.join(newDir.path, fileName)); await oldFile.delete(); } var lockFile = File(p.join(oldDir.path, "$element.lock")); if (await lockFile.exists()) { await lockFile.delete(); } } } catch (e) { Log.logPrint(e); } } /// 数据迁移根据版本:from 1.7.8 static Future migrateDataByVersion() async { int curAppVer = Utils.parseVersion(Utils.packageInfo.version); int curDBVer = LocalStorageService.instance .getValue(LocalStorageService.kHiveDbVer, 10708); Log.i("curDBVer: $curDBVer, curAppVer: $curAppVer"); if (curDBVer <= 10708) { LocalStorageService.instance.settingsBox .delete(LocalStorageService.kWebDAVLastUploadTime); LocalStorageService.instance.settingsBox .delete(LocalStorageService.kWebDAVLastRecoverTime); } // follow_user 添加 tag属性 // 从followUserTag 读取 标签 if (curDBVer <= 10709) { List tagList = DBService.instance.tagBox.values.toList(); List followList = DBService.instance.followBox.values.toList(); for (int i = 0; i < followList.length; i++) { for (FollowUserTag tag in tagList) { if (tag.userId.contains(followList[i].id)) { followList[i].tag = tag.tag; DBService.instance.addFollow(followList[i]); break; } } } } // sortkey-romanName if (curDBVer <= 10805) { await FollowService.instance.followUserAllDataCheck(); } LocalStorageService.instance.settingsBox .put(LocalStorageService.kHiveDbVer, curAppVer); } } ================================================ FILE: simple_live_app/lib/services/signalr_service.dart ================================================ import 'dart:async'; import 'dart:io'; import 'package:signalr_netcore/signalr_client.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/utils.dart'; enum SignalRConnectionState { connecting, connected, disconnected, } class SignalRService { static const String kUrl = "https://sync1.nsapps.cn/sync"; SignalRConnectionState state = SignalRConnectionState.connecting; final _stateStreamController = StreamController.broadcast(); Stream get stateStream => _stateStreamController.stream; final _onFavoriteStreamController = StreamController<(bool, String)>.broadcast(); Stream<(bool, String)> get onFavoriteStream => _onFavoriteStreamController.stream; final _onHistoryStreamController = StreamController<(bool, String)>.broadcast(); Stream<(bool, String)> get onHistoryStream => _onHistoryStreamController.stream; final _onShieldWordStreamController = StreamController<(bool, String)>.broadcast(); Stream<(bool, String)> get onShieldWordStream => _onShieldWordStreamController.stream; final _onBiliAccountStreamController = StreamController<(bool, String)>.broadcast(); Stream<(bool, String)> get onBiliAccountStream => _onBiliAccountStreamController.stream; final _onRoomDestroyedStreamController = StreamController.broadcast(); Stream get onRoomDestroyedStream => _onRoomDestroyedStreamController.stream; final _onRoomUserUpdatedStreamController = StreamController>.broadcast(); Stream> get onRoomUserUpdatedStream => _onRoomUserUpdatedStreamController.stream; HubConnection? hubConnection; Future connect() async { hubConnection = HubConnectionBuilder().withUrl(kUrl).build(); hubConnection!.onclose(({Exception? error}) { state = SignalRConnectionState.disconnected; _stateStreamController.add(state); }); hubConnection!.onreconnected(({String? connectionId}) { Log.d("reconnected: $connectionId"); state = SignalRConnectionState.connected; _stateStreamController.add(state); }); await hubConnection!.start(); state = SignalRConnectionState.connected; _stateStreamController.add(state); _listen(); } void _listen() { hubConnection?.on("onFavoriteReceived", (args) { _onFavoriteStreamController.add((args![0] as bool, args[1] as String)); }); hubConnection?.on("onHistoryReceived", (args) { _onHistoryStreamController.add((args![0] as bool, args[1] as String)); }); hubConnection?.on("onShieldWordReceived", (args) { _onShieldWordStreamController.add((args![0] as bool, args[1] as String)); }); hubConnection?.on("onBiliAccountReceived", (args) { _onBiliAccountStreamController.add((args![0] as bool, args[1] as String)); }); hubConnection?.on("onRoomDestroyed", (args) { _onRoomDestroyedStreamController.add(args![0].toString()); }); hubConnection?.on("onUserUpdated", (args) { var list = (args![0] as List).map((e) => RoomUser.fromObject(e)).toList(); _onRoomUserUpdatedStreamController.add(list); }); } Future disconnect() async { await hubConnection?.stop(); state = SignalRConnectionState.disconnected; _stateStreamController.add(state); } Future> createRoom() async { if (state != SignalRConnectionState.connected) { throw Exception("not connected"); } String app = "Slive"; String platform = Platform.operatingSystem; String version = Utils.packageInfo.version; var resp = await hubConnection ?.invoke("CreateRoom", args: [app, platform, version]); return Resp.fromObject(resp); } Future joinRoom(String roomId) async { if (state != SignalRConnectionState.connected) { throw Exception("not connected"); } String app = "Simple Live"; String platform = Platform.operatingSystem; String version = Utils.packageInfo.version; var resp = await hubConnection ?.invoke("JoinRoom", args: [roomId, app, platform, version]); return Resp.fromObject(resp); } Future sendContent({ required String roomName, required String action, required bool overlay, required String content, }) async { if (state != SignalRConnectionState.connected) { throw Exception("not connected"); } var resp = await hubConnection?.invoke(action, args: [roomName, overlay, content]); return Resp.fromObject(resp); } void dispose() { _stateStreamController.close(); _onFavoriteStreamController.close(); _onHistoryStreamController.close(); _onShieldWordStreamController.close(); _onBiliAccountStreamController.close(); _onRoomDestroyedStreamController.close(); _onRoomUserUpdatedStreamController.close(); hubConnection?.stop(); } } class Resp { final bool isSuccess; final String message; final T? data; Resp(this.isSuccess, this.message, this.data); factory Resp.fromJson(Map json) { return Resp( json['isSuccess'], json['message'] ?? "", json['data'], ); } factory Resp.fromObject(Object? obj) { if (obj is Map) { return Resp.fromJson(obj); } return Resp(false, "unknown", null); } } class RoomUser { final String connectionId; final String shortId; final String platform; final String version; final String app; final bool? isCreator; RoomUser({ required this.connectionId, required this.shortId, required this.platform, required this.version, required this.app, this.isCreator = false, }); factory RoomUser.fromJson(Map json) { return RoomUser( connectionId: json['connectionId'], shortId: json['shortId'], platform: json['platform'], version: json['version'], app: json['app'], isCreator: json['isCreator'], ); } factory RoomUser.fromObject(Object? obj) { if (obj is Map) { return RoomUser.fromJson(obj); } return RoomUser( connectionId: "", shortId: "", platform: "", version: "", app: "", ); } } ================================================ FILE: simple_live_app/lib/services/sync_service.dart ================================================ import 'dart:convert'; import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:network_info_plus/network_info_plus.dart'; import 'package:simple_live_app/app/constant.dart'; import 'package:simple_live_app/app/controller/app_settings_controller.dart'; import 'package:simple_live_app/app/event_bus.dart'; import 'package:simple_live_app/app/log.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/models/db/follow_user.dart'; import 'package:simple_live_app/models/db/follow_user_tag.dart'; import 'package:simple_live_app/models/db/history.dart'; import 'package:simple_live_app/services/bilibili_account_service.dart'; import 'package:simple_live_app/services/db_service.dart'; import 'package:udp/udp.dart'; import 'package:shelf/shelf.dart' as shelf; import 'package:shelf/shelf_io.dart' as shelf_io; import 'package:shelf_router/shelf_router.dart'; import 'package:uuid/uuid.dart'; class SyncService extends GetxService { static SyncService get instance => Get.find(); UDP? udp; RxList scanClients = [].obs; static const int udpPort = 23235; static const int httpPort = 23234; DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); NetworkInfo networkInfo = NetworkInfo(); HttpServer? server; var ipAddress = "".obs; var httpRunning = false.obs; var httpErrorMsg = "".obs; var deviceId = ""; @override void onInit() { Log.d('TVService init'); deviceId = (const Uuid().v4()).split('-').first; listenUDP(); initServer(); super.onInit(); } /// 监听其他端UDP广播的回复 void listenUDP() async { udp = await UDP.bind(Endpoint.any(port: const Port(udpPort))); udp!.asStream().listen(listenUdp); } void listenUdp(Datagram? datagram) { var str = String.fromCharCodes(datagram!.data); Log.i("Received: $str from ${datagram.address}:${datagram.port}"); if (str.startsWith('{') && str.endsWith('}')) { var data = json.decode(str); //如果是自己的广播,就不处理 if (data['id'] == deviceId) { return; } //处理Hello的广播 if (data["type"] == "hello") { //如果http服务已经启动,就回复自己的信息 if (httpRunning.value) { sendInfo(); } return; } // 处理其他端的广播 // 地址直接从datagram中获取,能收到回复说明地址是可以连通的 var address = datagram.address.address; //检查是否已经存在 var index = scanClients.indexWhere((element) => element.address == address); if (index == -1) { scanClients.add( SyncClinet( id: data['id'], name: data['name'], address: address, port: httpPort, type: data['type'], ), ); } } } /// 发送UDP广播至其他端 void sendHello() async { await udp!.send( json.encode({ "id": deviceId, "type": "hello", }).codeUnits, Endpoint.broadcast( port: const Port(udpPort), ), ); Log.i("send udp: hello"); } /// UDP广播自身信息 void sendInfo() async { //var ip = await getLocalIP(); var name = await getDeviceName(); var data = { "id": deviceId, "type": Platform.operatingSystem, //'version': Utils.packageInfo.version, "name": name, //"address": ip, //"port": httpPort, }; await udp!.send( json.encode(data).codeUnits, Endpoint.broadcast( port: const Port(udpPort), ), ); Log.i("send udp info: $data"); } Future getDeviceName() async { var name = "SimpleLive-${Platform.operatingSystem}"; if (Platform.isAndroid) { var info = await deviceInfo.androidInfo; name = info.model; } else if (Platform.isIOS) { var info = await deviceInfo.iosInfo; name = info.name; } else if (Platform.isMacOS) { var info = await deviceInfo.macOsInfo; name = info.computerName; } else if (Platform.isLinux) { var info = await deviceInfo.linuxInfo; name = info.name; } else if (Platform.isWindows) { var info = await deviceInfo.windowsInfo; name = info.userName; } return name; } void refreshClients() { scanClients.clear(); sendHello(); } /// 读取本地IP /// - 如果是wifi,直接获取wifi的IP /// - 如果是有线,获取所有的IP,找到全部的IP Future getLocalIP() async { String? ip = ""; try { ip = await networkInfo.getWifiIP(); } catch (e) { Log.logPrint(e); } try { if (ip == null || ip.isEmpty) { var interfaces = await NetworkInterface.list(); var ipList = []; for (var interface in interfaces) { for (var addr in interface.addresses) { if (addr.type.name == 'IPv4' && !addr.address.startsWith('127') && !addr.isMulticast && !addr.isLoopback) { ipList.add(addr.address); break; } } } ip = ipList.join(';'); } } catch (e) { Log.logPrint(e); } return ip ?? ""; } /// 初始化HTTP服务 void initServer() async { try { var serverRouter = Router(); serverRouter.get('/', _helloRequest); serverRouter.get('/info', _infoRequest); serverRouter.post('/sync/follow', _syncFollowUserReuqest); serverRouter.post('/sync/tag', _syncFollowUserTagRequest); serverRouter.post('/sync/history', _syncHistoryReuqest); serverRouter.post('/sync/blocked_word', _syncBlockedWordReuqest); serverRouter.post('/sync/account/bilibili', _syncBiliAccountReuqest); var server = await shelf_io.serve( serverRouter.call, InternetAddress.anyIPv4, httpPort, ); // Enable content compression server.autoCompress = true; httpRunning.value = true; var ip = await getLocalIP(); ipAddress.value = ip; Log.d('Serving at http://$ip:${server.port}'); } catch (e) { httpErrorMsg.value = e.toString(); Log.logPrint(e); } } /// 测试服务能否正常访问 shelf.Response _helloRequest(shelf.Request request) { return toJsonResponse({ 'status': true, 'message': 'http server is running...', "version": 'SimpeLive ${Platform.operatingSystem} v${Utils.packageInfo.version}', }); } /// 发送自己的信息 Future _infoRequest(shelf.Request request) async { var name = await getDeviceName(); return toJsonResponse({ "id": deviceId, 'type': Platform.operatingSystem, 'name': name, 'version': Utils.packageInfo.version, 'address': ipAddress.value, 'port': httpPort, }); } /// 同步关注用户列表 Future _syncFollowUserReuqest(shelf.Request request) async { try { var overlay = int.parse(request.requestedUri.queryParameters['overlay'] ?? '0'); var body = await request.readAsString(); Log.d('_syncFollowUserReuqest: $body'); var jsonBody = json.decode(body); if (overlay == 1) { await DBService.instance.followBox.clear(); } for (var item in jsonBody) { var user = FollowUser.fromJson(item); await DBService.instance.followBox.put(user.id, user); } SmartDialog.showToast('已同步关注用户列表'); EventBus.instance.emit(Constant.kUpdateFollow, 0); return toJsonResponse({ 'status': true, 'message': 'success', }); } catch (e) { return toJsonResponse({ 'status': false, 'message': e.toString(), }); } } /// 同步标签列表 Future _syncFollowUserTagRequest( shelf.Request request) async { try { var overlay = int.parse(request.requestedUri.queryParameters['overlay'] ?? '0'); var body = await request.readAsString(); Log.d('_syncFollowUserTagRequest: $body'); var jsonBody = json.decode(body); if (overlay == 1) { await DBService.instance.tagBox.clear(); } for (var item in jsonBody) { var tag = FollowUserTag.fromJson(item); await DBService.instance.tagBox.put(tag.id, tag); } SmartDialog.showToast('已同步标签列表'); EventBus.instance.emit(Constant.kUpdateFollow, 0); return toJsonResponse({ 'status': true, 'message': 'success', }); } catch (e) { return toJsonResponse({ 'status': false, 'message': e.toString(), }); } } /// 同步观看记录 Future _syncHistoryReuqest(shelf.Request request) async { try { var overlay = int.parse(request.requestedUri.queryParameters['overlay'] ?? '0'); var body = await request.readAsString(); Log.d('_syncFollowUserReuqest: $body'); var jsonBody = json.decode(body); if (overlay == 1) { await DBService.instance.historyBox.clear(); } for (var item in jsonBody) { var history = History.fromJson(item); if (DBService.instance.historyBox.containsKey(history.id)) { var old = DBService.instance.historyBox.get(history.id); //如果本地的更新时间比较新,就不更新 if (old!.updateTime.isAfter(history.updateTime)) { continue; } } await DBService.instance.addOrUpdateHistory(history); } SmartDialog.showToast('已同步观看记录'); EventBus.instance.emit(Constant.kUpdateHistory, 0); return toJsonResponse({ 'status': true, 'message': 'success', }); } catch (e) { return toJsonResponse({ 'status': false, 'message': e.toString(), }); } } /// 同步弹幕屏蔽词 Future _syncBlockedWordReuqest(shelf.Request request) async { try { var overlay = int.parse(request.requestedUri.queryParameters['overlay'] ?? '0'); var body = await request.readAsString(); Log.d('_syncBlockedWordReuqest: $body'); var jsonBody = json.decode(body); if (overlay == 1) { AppSettingsController.instance.clearShieldList(); } for (var keyword in jsonBody) { AppSettingsController.instance.addShieldList(keyword.trim()); } SmartDialog.showToast('已同步弹幕屏蔽词'); return toJsonResponse({ 'status': true, 'message': 'success', }); } catch (e) { return toJsonResponse({ 'status': false, 'message': e.toString(), }); } } /// 同步哔哩哔哩账号 Future _syncBiliAccountReuqest(shelf.Request request) async { try { var body = await request.readAsString(); Log.d('_syncBiliAccountReuqest: $body'); var jsonBody = json.decode(body); var cookie = jsonBody['cookie']; BiliBiliAccountService.instance.setCookie(cookie); BiliBiliAccountService.instance.loadUserInfo(); SmartDialog.showToast('已同步哔哩哔哩账号'); return toJsonResponse({ 'status': true, 'message': 'success', }); } catch (e) { return toJsonResponse({ 'status': false, 'message': e.toString(), }); } } shelf.Response toJsonResponse(Map data) { return shelf.Response.ok( json.encode(data), headers: { 'Content-Type': 'application/json', }, encoding: Encoding.getByName('utf-8'), ); } @override void onClose() { Log.d('SyncService close'); udp?.close(); server?.close(force: true); super.onClose(); } } class SyncClinet { final String id; final String name; final String address; final int port; final String type; SyncClinet({ required this.id, required this.name, required this.address, required this.port, required this.type, }); } ================================================ FILE: simple_live_app/lib/services/window_service.dart ================================================ import 'dart:io'; import 'dart:ui'; import 'package:get/get.dart'; import 'package:simple_live_app/services/local_storage_service.dart'; import 'package:window_manager/window_manager.dart'; class WindowService extends GetxService implements WindowListener { static WindowService get instance => Get.find(); bool isPIP = false; WindowService() { windowManager.addListener(this); } Future init() async { await resize(); WindowOptions windowOptions = WindowOptions( minimumSize: Size(280, 280), center: false, title: "Slive", ); windowManager.waitUntilReadyToShow(windowOptions, () async { await windowManager.show(); await windowManager.focus(); }); } Future resize() async { // 初始分辨率默认 1920×1080 final width = LocalStorageService.instance .getValue(LocalStorageService.kWindowWidth, 1280.0); final height = LocalStorageService.instance .getValue(LocalStorageService.kWindowHeight, 720.0); final x = LocalStorageService.instance .getValue(LocalStorageService.kWindowX, 320.0); final y = LocalStorageService.instance .getValue(LocalStorageService.kWindowY, 180.0); windowManager.setBounds(Rect.fromLTWH(x, y, width, height)); } @override void onWindowBlur() {} @override void onWindowClose() { if (Platform.isLinux) { exit(0); } } @override void onWindowDocked() {} @override void onWindowEnterFullScreen() {} @override void onWindowEvent(String eventName) {} @override void onWindowFocus() {} @override void onWindowLeaveFullScreen() {} @override void onWindowMaximize() {} @override void onWindowMinimize() {} @override Future onWindowMove() async {} @override Future onWindowMoved() async { if (!isPIP) { final bounds = await windowManager.getBounds(); _saveBounds(bounds); } } @override Future onWindowResize() async {} @override Future onWindowResized() async { if (!isPIP) { final bounds = await windowManager.getBounds(); _saveBounds(bounds); } } @override void onWindowRestore() {} @override void onWindowUndocked() {} @override void onWindowUnmaximize() {} void _saveBounds(Rect bounds) { LocalStorageService.instance .setValue(LocalStorageService.kWindowX, bounds.left); LocalStorageService.instance .setValue(LocalStorageService.kWindowY, bounds.top); LocalStorageService.instance .setValue(LocalStorageService.kWindowWidth, bounds.width); LocalStorageService.instance .setValue(LocalStorageService.kWindowHeight, bounds.height); } } ================================================ FILE: simple_live_app/lib/src/rust/api/danmaku_mask.dart ================================================ // This file is automatically generated, so please do not edit it. // @generated by `flutter_rust_bridge`@ 2.11.1. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import import '../frb_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; // These functions are ignored because they are not marked as `pub`: `adapt_window`, `normalize`, `shift_if_needed` // Rust type: RustOpaqueMoi> abstract class DanmakuMask implements RustOpaqueInterface { /// 批量判断是否允许 /// 返回 Vec:1 = 允许,0 = 屏蔽 Future allowListBatch( {required List texts, required BigInt nowMs}); @override void dispose(); /// 构造函数(会生成 Dart 构造器) factory DanmakuMask( {required int baseWindowMs, required int bucketCount, required bool useNormalization, required bool useFrequencyControl, required int maxFrequency, required bool adaptiveWindow}) => RustLib.instance.api.crateApiDanmakuMaskDanmakuMaskNew( baseWindowMs: baseWindowMs, bucketCount: bucketCount, useNormalization: useNormalization, useFrequencyControl: useFrequencyControl, maxFrequency: maxFrequency, adaptiveWindow: adaptiveWindow); /// 重置状态 void reset(); } ================================================ FILE: simple_live_app/lib/src/rust/api/simple.dart ================================================ // This file is automatically generated, so please do not edit it. // @generated by `flutter_rust_bridge`@ 2.11.1. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import import '../frb_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; // These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `SINGLETON_MASK` // These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `deref`, `initialize` ================================================ FILE: simple_live_app/lib/src/rust/frb_generated.dart ================================================ // This file is automatically generated, so please do not edit it. // @generated by `flutter_rust_bridge`@ 2.11.1. // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field import 'api/danmaku_mask.dart'; import 'dart:async'; import 'dart:convert'; import 'frb_generated.dart'; import 'frb_generated.io.dart' if (dart.library.js_interop) 'frb_generated.web.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; /// Main entrypoint of the Rust API class RustLib extends BaseEntrypoint { @internal static final instance = RustLib._(); RustLib._(); /// Initialize flutter_rust_bridge static Future init({ RustLibApi? api, BaseHandler? handler, ExternalLibrary? externalLibrary, bool forceSameCodegenVersion = true, }) async { await instance.initImpl( api: api, handler: handler, externalLibrary: externalLibrary, forceSameCodegenVersion: forceSameCodegenVersion, ); } /// Initialize flutter_rust_bridge in mock mode. /// No libraries for FFI are loaded. static void initMock({ required RustLibApi api, }) { instance.initMockImpl( api: api, ); } /// Dispose flutter_rust_bridge /// /// The call to this function is optional, since flutter_rust_bridge (and everything else) /// is automatically disposed when the app stops. static void dispose() => instance.disposeImpl(); @override ApiImplConstructor get apiImplConstructor => RustLibApiImpl.new; @override WireConstructor get wireConstructor => RustLibWire.fromExternalLibrary; @override Future executeRustInitializers() async { await api.crateApiSimpleInitApp(); } @override ExternalLibraryLoaderConfig get defaultExternalLibraryLoaderConfig => kDefaultExternalLibraryLoaderConfig; @override String get codegenVersion => '2.11.1'; @override int get rustContentHash => -1540820557; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( stem: 'rust_lib_simple_live_app', ioDirectory: 'rust/target/release/', webPrefix: 'pkg/', ); } abstract class RustLibApi extends BaseApi { Future crateApiDanmakuMaskDanmakuMaskAllowListBatch( {required DanmakuMask that, required List texts, required BigInt nowMs}); void crateApiDanmakuMaskDanmakuMaskDispose({required DanmakuMask that}); DanmakuMask crateApiDanmakuMaskDanmakuMaskNew( {required int baseWindowMs, required int bucketCount, required bool useNormalization, required bool useFrequencyControl, required int maxFrequency, required bool adaptiveWindow}); void crateApiDanmakuMaskDanmakuMaskReset({required DanmakuMask that}); Future crateApiSimpleInitApp(); RustArcIncrementStrongCountFnType get rust_arc_increment_strong_count_DanmakuMask; RustArcDecrementStrongCountFnType get rust_arc_decrement_strong_count_DanmakuMask; CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_DanmakuMaskPtr; } class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { RustLibApiImpl({ required super.handler, required super.wire, required super.generalizedFrbRustBinding, required super.portManager, }); @override Future crateApiDanmakuMaskDanmakuMaskAllowListBatch( {required DanmakuMask that, required List texts, required BigInt nowMs}) { return handler.executeNormal(NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( that, serializer); sse_encode_list_String(texts, serializer); sse_encode_u_64(nowMs, serializer); pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 1, port: port_); }, codec: SseCodec( decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: null, ), constMeta: kCrateApiDanmakuMaskDanmakuMaskAllowListBatchConstMeta, argValues: [that, texts, nowMs], apiImpl: this, )); } TaskConstMeta get kCrateApiDanmakuMaskDanmakuMaskAllowListBatchConstMeta => const TaskConstMeta( debugName: "DanmakuMask_allow_list_batch", argNames: ["that", "texts", "nowMs"], ); @override void crateApiDanmakuMaskDanmakuMaskDispose({required DanmakuMask that}) { return handler.executeSync(SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( that, serializer); return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 2)!; }, codec: SseCodec( decodeSuccessData: sse_decode_unit, decodeErrorData: null, ), constMeta: kCrateApiDanmakuMaskDanmakuMaskDisposeConstMeta, argValues: [that], apiImpl: this, )); } TaskConstMeta get kCrateApiDanmakuMaskDanmakuMaskDisposeConstMeta => const TaskConstMeta( debugName: "DanmakuMask_dispose", argNames: ["that"], ); @override DanmakuMask crateApiDanmakuMaskDanmakuMaskNew( {required int baseWindowMs, required int bucketCount, required bool useNormalization, required bool useFrequencyControl, required int maxFrequency, required bool adaptiveWindow}) { return handler.executeSync(SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_u_32(baseWindowMs, serializer); sse_encode_u_16(bucketCount, serializer); sse_encode_bool(useNormalization, serializer); sse_encode_bool(useFrequencyControl, serializer); sse_encode_u_16(maxFrequency, serializer); sse_encode_bool(adaptiveWindow, serializer); return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 3)!; }, codec: SseCodec( decodeSuccessData: sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask, decodeErrorData: null, ), constMeta: kCrateApiDanmakuMaskDanmakuMaskNewConstMeta, argValues: [ baseWindowMs, bucketCount, useNormalization, useFrequencyControl, maxFrequency, adaptiveWindow ], apiImpl: this, )); } TaskConstMeta get kCrateApiDanmakuMaskDanmakuMaskNewConstMeta => const TaskConstMeta( debugName: "DanmakuMask_new", argNames: [ "baseWindowMs", "bucketCount", "useNormalization", "useFrequencyControl", "maxFrequency", "adaptiveWindow" ], ); @override void crateApiDanmakuMaskDanmakuMaskReset({required DanmakuMask that}) { return handler.executeSync(SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( that, serializer); return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 4)!; }, codec: SseCodec( decodeSuccessData: sse_decode_unit, decodeErrorData: null, ), constMeta: kCrateApiDanmakuMaskDanmakuMaskResetConstMeta, argValues: [that], apiImpl: this, )); } TaskConstMeta get kCrateApiDanmakuMaskDanmakuMaskResetConstMeta => const TaskConstMeta( debugName: "DanmakuMask_reset", argNames: ["that"], ); @override Future crateApiSimpleInitApp() { return handler.executeNormal(NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 5, port: port_); }, codec: SseCodec( decodeSuccessData: sse_decode_unit, decodeErrorData: null, ), constMeta: kCrateApiSimpleInitAppConstMeta, argValues: [], apiImpl: this, )); } TaskConstMeta get kCrateApiSimpleInitAppConstMeta => const TaskConstMeta( debugName: "init_app", argNames: [], ); RustArcIncrementStrongCountFnType get rust_arc_increment_strong_count_DanmakuMask => wire .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask; RustArcDecrementStrongCountFnType get rust_arc_decrement_strong_count_DanmakuMask => wire .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask; @protected DanmakuMask dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return DanmakuMaskImpl.frbInternalDcoDecode(raw as List); } @protected DanmakuMask dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return DanmakuMaskImpl.frbInternalDcoDecode(raw as List); } @protected DanmakuMask dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return DanmakuMaskImpl.frbInternalDcoDecode(raw as List); } @protected String dco_decode_String(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return raw as String; } @protected bool dco_decode_bool(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return raw as bool; } @protected List dco_decode_list_String(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return (raw as List).map(dco_decode_String).toList(); } @protected Uint8List dco_decode_list_prim_u_8_strict(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return raw as Uint8List; } @protected int dco_decode_u_16(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return raw as int; } @protected int dco_decode_u_32(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return raw as int; } @protected BigInt dco_decode_u_64(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return dcoDecodeU64(raw); } @protected int dco_decode_u_8(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return raw as int; } @protected void dco_decode_unit(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return; } @protected BigInt dco_decode_usize(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return dcoDecodeU64(raw); } @protected DanmakuMask sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs return DanmakuMaskImpl.frbInternalSseDecode( sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); } @protected DanmakuMask sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs return DanmakuMaskImpl.frbInternalSseDecode( sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); } @protected DanmakuMask sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs return DanmakuMaskImpl.frbInternalSseDecode( sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); } @protected String sse_decode_String(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs var inner = sse_decode_list_prim_u_8_strict(deserializer); return utf8.decoder.convert(inner); } @protected bool sse_decode_bool(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs return deserializer.buffer.getUint8() != 0; } @protected List sse_decode_list_String(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs var len_ = sse_decode_i_32(deserializer); var ans_ = []; for (var idx_ = 0; idx_ < len_; ++idx_) { ans_.add(sse_decode_String(deserializer)); } return ans_; } @protected Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs var len_ = sse_decode_i_32(deserializer); return deserializer.buffer.getUint8List(len_); } @protected int sse_decode_u_16(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs return deserializer.buffer.getUint16(); } @protected int sse_decode_u_32(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs return deserializer.buffer.getUint32(); } @protected BigInt sse_decode_u_64(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs return deserializer.buffer.getBigUint64(); } @protected int sse_decode_u_8(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs return deserializer.buffer.getUint8(); } @protected void sse_decode_unit(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs } @protected BigInt sse_decode_usize(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs return deserializer.buffer.getBigUint64(); } @protected int sse_decode_i_32(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs return deserializer.buffer.getInt32(); } @protected void sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( DanmakuMask self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs sse_encode_usize( (self as DanmakuMaskImpl).frbInternalSseEncode(move: true), serializer); } @protected void sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( DanmakuMask self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs sse_encode_usize( (self as DanmakuMaskImpl).frbInternalSseEncode(move: false), serializer); } @protected void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( DanmakuMask self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs sse_encode_usize( (self as DanmakuMaskImpl).frbInternalSseEncode(move: null), serializer); } @protected void sse_encode_String(String self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs sse_encode_list_prim_u_8_strict(utf8.encoder.convert(self), serializer); } @protected void sse_encode_bool(bool self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs serializer.buffer.putUint8(self ? 1 : 0); } @protected void sse_encode_list_String(List self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs sse_encode_i_32(self.length, serializer); for (final item in self) { sse_encode_String(item, serializer); } } @protected void sse_encode_list_prim_u_8_strict( Uint8List self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs sse_encode_i_32(self.length, serializer); serializer.buffer.putUint8List(self); } @protected void sse_encode_u_16(int self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs serializer.buffer.putUint16(self); } @protected void sse_encode_u_32(int self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs serializer.buffer.putUint32(self); } @protected void sse_encode_u_64(BigInt self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs serializer.buffer.putBigUint64(self); } @protected void sse_encode_u_8(int self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs serializer.buffer.putUint8(self); } @protected void sse_encode_unit(void self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs } @protected void sse_encode_usize(BigInt self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs serializer.buffer.putBigUint64(self); } @protected void sse_encode_i_32(int self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs serializer.buffer.putInt32(self); } } @sealed class DanmakuMaskImpl extends RustOpaque implements DanmakuMask { // Not to be used by end users DanmakuMaskImpl.frbInternalDcoDecode(List wire) : super.frbInternalDcoDecode(wire, _kStaticData); // Not to be used by end users DanmakuMaskImpl.frbInternalSseDecode(BigInt ptr, int externalSizeOnNative) : super.frbInternalSseDecode(ptr, externalSizeOnNative, _kStaticData); static final _kStaticData = RustArcStaticData( rustArcIncrementStrongCount: RustLib.instance.api.rust_arc_increment_strong_count_DanmakuMask, rustArcDecrementStrongCount: RustLib.instance.api.rust_arc_decrement_strong_count_DanmakuMask, rustArcDecrementStrongCountPtr: RustLib.instance.api.rust_arc_decrement_strong_count_DanmakuMaskPtr, ); /// 批量判断是否允许 /// 返回 Vec:1 = 允许,0 = 屏蔽 Future allowListBatch( {required List texts, required BigInt nowMs}) => RustLib.instance.api.crateApiDanmakuMaskDanmakuMaskAllowListBatch( that: this, texts: texts, nowMs: nowMs); void dispose() => RustLib.instance.api.crateApiDanmakuMaskDanmakuMaskDispose( that: this, ); /// 重置状态 void reset() => RustLib.instance.api.crateApiDanmakuMaskDanmakuMaskReset( that: this, ); } ================================================ FILE: simple_live_app/lib/src/rust/frb_generated.io.dart ================================================ // This file is automatically generated, so please do not edit it. // @generated by `flutter_rust_bridge`@ 2.11.1. // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field import 'api/danmaku_mask.dart'; import 'dart:async'; import 'dart:convert'; import 'dart:ffi' as ffi; import 'frb_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart'; abstract class RustLibApiImplPlatform extends BaseApiImpl { RustLibApiImplPlatform({ required super.handler, required super.wire, required super.generalizedFrbRustBinding, required super.portManager, }); CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_DanmakuMaskPtr => wire ._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMaskPtr; @protected DanmakuMask dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( dynamic raw); @protected DanmakuMask dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( dynamic raw); @protected DanmakuMask dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( dynamic raw); @protected String dco_decode_String(dynamic raw); @protected bool dco_decode_bool(dynamic raw); @protected List dco_decode_list_String(dynamic raw); @protected Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); @protected int dco_decode_u_16(dynamic raw); @protected int dco_decode_u_32(dynamic raw); @protected BigInt dco_decode_u_64(dynamic raw); @protected int dco_decode_u_8(dynamic raw); @protected void dco_decode_unit(dynamic raw); @protected BigInt dco_decode_usize(dynamic raw); @protected DanmakuMask sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( SseDeserializer deserializer); @protected DanmakuMask sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( SseDeserializer deserializer); @protected DanmakuMask sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( SseDeserializer deserializer); @protected String sse_decode_String(SseDeserializer deserializer); @protected bool sse_decode_bool(SseDeserializer deserializer); @protected List sse_decode_list_String(SseDeserializer deserializer); @protected Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); @protected int sse_decode_u_16(SseDeserializer deserializer); @protected int sse_decode_u_32(SseDeserializer deserializer); @protected BigInt sse_decode_u_64(SseDeserializer deserializer); @protected int sse_decode_u_8(SseDeserializer deserializer); @protected void sse_decode_unit(SseDeserializer deserializer); @protected BigInt sse_decode_usize(SseDeserializer deserializer); @protected int sse_decode_i_32(SseDeserializer deserializer); @protected void sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( DanmakuMask self, SseSerializer serializer); @protected void sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( DanmakuMask self, SseSerializer serializer); @protected void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( DanmakuMask self, SseSerializer serializer); @protected void sse_encode_String(String self, SseSerializer serializer); @protected void sse_encode_bool(bool self, SseSerializer serializer); @protected void sse_encode_list_String(List self, SseSerializer serializer); @protected void sse_encode_list_prim_u_8_strict( Uint8List self, SseSerializer serializer); @protected void sse_encode_u_16(int self, SseSerializer serializer); @protected void sse_encode_u_32(int self, SseSerializer serializer); @protected void sse_encode_u_64(BigInt self, SseSerializer serializer); @protected void sse_encode_u_8(int self, SseSerializer serializer); @protected void sse_encode_unit(void self, SseSerializer serializer); @protected void sse_encode_usize(BigInt self, SseSerializer serializer); @protected void sse_encode_i_32(int self, SseSerializer serializer); } // Section: wire_class class RustLibWire implements BaseWire { factory RustLibWire.fromExternalLibrary(ExternalLibrary lib) => RustLibWire(lib.ffiDynamicLibrary); /// Holds the symbol lookup function. final ffi.Pointer Function(String symbolName) _lookup; /// The symbols are looked up in [dynamicLibrary]. RustLibWire(ffi.DynamicLibrary dynamicLibrary) : _lookup = dynamicLibrary.lookup; void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( ffi.Pointer ptr, ) { return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( ptr, ); } late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMaskPtr = _lookup)>>( 'frbgen_simple_live_app_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask'); late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask = _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMaskPtr .asFunction)>(); void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( ffi.Pointer ptr, ) { return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( ptr, ); } late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMaskPtr = _lookup)>>( 'frbgen_simple_live_app_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask'); late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask = _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMaskPtr .asFunction)>(); } ================================================ FILE: simple_live_app/lib/src/rust/frb_generated.web.dart ================================================ // This file is automatically generated, so please do not edit it. // @generated by `flutter_rust_bridge`@ 2.11.1. // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field // Static analysis wrongly picks the IO variant, thus ignore this // ignore_for_file: argument_type_not_assignable import 'api/danmaku_mask.dart'; import 'dart:async'; import 'dart:convert'; import 'frb_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart'; abstract class RustLibApiImplPlatform extends BaseApiImpl { RustLibApiImplPlatform({ required super.handler, required super.wire, required super.generalizedFrbRustBinding, required super.portManager, }); CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_DanmakuMaskPtr => wire.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask; @protected DanmakuMask dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask(dynamic raw); @protected DanmakuMask dco_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask(dynamic raw); @protected DanmakuMask dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask(dynamic raw); @protected String dco_decode_String(dynamic raw); @protected bool dco_decode_bool(dynamic raw); @protected List dco_decode_list_String(dynamic raw); @protected Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); @protected int dco_decode_u_16(dynamic raw); @protected int dco_decode_u_32(dynamic raw); @protected BigInt dco_decode_u_64(dynamic raw); @protected int dco_decode_u_8(dynamic raw); @protected void dco_decode_unit(dynamic raw); @protected BigInt dco_decode_usize(dynamic raw); @protected DanmakuMask sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask(SseDeserializer deserializer); @protected DanmakuMask sse_decode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask(SseDeserializer deserializer); @protected DanmakuMask sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask(SseDeserializer deserializer); @protected String sse_decode_String(SseDeserializer deserializer); @protected bool sse_decode_bool(SseDeserializer deserializer); @protected List sse_decode_list_String(SseDeserializer deserializer); @protected Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); @protected int sse_decode_u_16(SseDeserializer deserializer); @protected int sse_decode_u_32(SseDeserializer deserializer); @protected BigInt sse_decode_u_64(SseDeserializer deserializer); @protected int sse_decode_u_8(SseDeserializer deserializer); @protected void sse_decode_unit(SseDeserializer deserializer); @protected BigInt sse_decode_usize(SseDeserializer deserializer); @protected int sse_decode_i_32(SseDeserializer deserializer); @protected void sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask(DanmakuMask self, SseSerializer serializer); @protected void sse_encode_Auto_RefMut_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask(DanmakuMask self, SseSerializer serializer); @protected void sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask(DanmakuMask self, SseSerializer serializer); @protected void sse_encode_String(String self, SseSerializer serializer); @protected void sse_encode_bool(bool self, SseSerializer serializer); @protected void sse_encode_list_String(List self, SseSerializer serializer); @protected void sse_encode_list_prim_u_8_strict(Uint8List self, SseSerializer serializer); @protected void sse_encode_u_16(int self, SseSerializer serializer); @protected void sse_encode_u_32(int self, SseSerializer serializer); @protected void sse_encode_u_64(BigInt self, SseSerializer serializer); @protected void sse_encode_u_8(int self, SseSerializer serializer); @protected void sse_encode_unit(void self, SseSerializer serializer); @protected void sse_encode_usize(BigInt self, SseSerializer serializer); @protected void sse_encode_i_32(int self, SseSerializer serializer); } // Section: wire_class class RustLibWire implements BaseWire { RustLibWire.fromExternalLibrary(ExternalLibrary lib); void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask(int ptr) => wasmModule.rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask(ptr); void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask(int ptr) => wasmModule.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask(ptr); } @JS('wasm_bindgen') external RustLibWasmModule get wasmModule; @JS() @anonymous extension type RustLibWasmModule._(JSObject _) implements JSObject { external void rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask(int ptr); external void rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask(int ptr); } ================================================ FILE: simple_live_app/lib/widgets/desktop_refresh_button.dart ================================================ import 'package:flutter/material.dart'; import 'package:simple_live_app/app/app_style.dart'; class DesktopRefreshButton extends StatelessWidget { final bool refreshing; final Function()? onPressed; const DesktopRefreshButton( {required this.refreshing, this.onPressed, super.key}); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: Theme.of(context).cardColor, borderRadius: AppStyle.radius48, boxShadow: [ BoxShadow( color: Colors.grey.withAlpha(50), blurRadius: 4, ), ], ), width: 40, height: 40, child: refreshing ? const Center( child: SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, ), ), ) : IconButton( onPressed: onPressed, icon: const Icon(Icons.refresh), ), ); } } ================================================ FILE: simple_live_app/lib/widgets/filter_button.dart ================================================ import 'package:flutter/material.dart'; import 'package:simple_live_app/app/app_style.dart'; class FilterButton extends StatelessWidget { final bool selected; final String text; final Function()? onTap; const FilterButton({ this.selected = false, required this.text, this.onTap, super.key, }); @override Widget build(BuildContext context) { return InkWell( borderRadius: AppStyle.radius24, onTap: onTap, child: Container( padding: AppStyle.edgeInsetsH12.copyWith(top: 4, bottom: 4), decoration: BoxDecoration( border: Border.all( color: selected ? Theme.of(context).textTheme.bodyMedium!.color! : Colors.grey), borderRadius: AppStyle.radius24, ), child: Text( text, style: selected ? Theme.of(context).textTheme.bodyMedium : Theme.of(context).textTheme.bodyMedium!.copyWith( color: Colors.grey, ), ), ), ); } } ================================================ FILE: simple_live_app/lib/widgets/follow_user_item.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/models/db/follow_user.dart'; import 'package:simple_live_app/widgets/net_image.dart'; import 'dart:ui' as ui; class FollowUserItem extends StatelessWidget { final FollowUser item; final Function()? onRemove; final Function()? onTap; final Function()? onLongPress; final bool playing; const FollowUserItem({ required this.item, this.onRemove, this.onTap, this.onLongPress, this.playing = false, super.key, }); @override Widget build(BuildContext context) { var site = Sites.allSites[item.siteId]!; return ListTile( contentPadding: AppStyle.edgeInsetsL16.copyWith(right: 4), leading: NetImage( item.face, width: 48, height: 48, borderRadius: 24, ), title: Text.rich( TextSpan( text: item.remark?.isNotEmpty == true ? item.remark : item.userName, children: [ WidgetSpan( alignment: ui.PlaceholderAlignment.middle, child: Obx( () => Offstage( offstage: item.liveStatus.value == 0, child: Row( mainAxisSize: MainAxisSize.min, children: [ AppStyle.hGap12, Container( width: 8, height: 8, decoration: BoxDecoration( color: item.liveStatus.value == 2 ? Colors.green : Colors.grey, borderRadius: AppStyle.radius12, ), ), AppStyle.hGap4, Text( getStatus(item.liveStatus.value), style: TextStyle( fontSize: 12, fontWeight: FontWeight.normal, color: item.liveStatus.value == 2 ? null : Colors.grey, ), ), ], ), ), ), ), ], ), ), subtitle: Wrap( runSpacing: 1.0, children: [ Image.asset( site.logo, width: 20, ), AppStyle.hGap4, Text( site.name, style: const TextStyle( fontSize: 12, color: Colors.grey, ), ), AppStyle.hGap4, Text( item.watchDuration ?? "00:00:00", style: const TextStyle( fontSize: 12, color: Colors.grey, ), ), AppStyle.hGap4, Text( item.tag.length > 8 ? '${item.tag.substring(0, 8)}...' : item.tag, style: const TextStyle( fontSize: 12, color: Colors.grey, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), trailing: playing ? const SizedBox( width: 64, child: Center( child: Icon( Icons.play_arrow, ), ), ) : (onRemove == null ? null : IconButton( onPressed: () { onRemove?.call(); }, icon: const Icon(Remix.dislike_line), )), onTap: onTap, onLongPress: onLongPress, ); } String getStatus(int status) { if (status == 0) { return "读取中"; } else if (status == 1) { return "未开播"; } else { return "直播中"; } } } ================================================ FILE: simple_live_app/lib/widgets/keep_alive_wrapper.dart ================================================ import 'package:flutter/material.dart'; class KeepAliveWrapper extends StatefulWidget { final Widget child; const KeepAliveWrapper({super.key, required this.child}); @override State createState() => _KeepAliveWrapperState(); } class _KeepAliveWrapperState extends State with AutomaticKeepAliveClientMixin { @override Widget build(BuildContext context) { super.build(context); return widget.child; } @override bool get wantKeepAlive => true; } ================================================ FILE: simple_live_app/lib/widgets/live_room_card.dart ================================================ import 'package:flutter/material.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/sites.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/routes/app_navigation.dart'; import 'package:simple_live_app/widgets/net_image.dart'; import 'package:simple_live_app/widgets/shadow_card.dart'; import 'package:simple_live_core/simple_live_core.dart'; class LiveRoomCard extends StatelessWidget { final Site site; final LiveRoomItem item; final Function()? onLongPress; const LiveRoomCard(this.site, this.item, {super.key, this.onLongPress}); @override Widget build(BuildContext context) { return ShadowCard( onTap: () { AppNavigator.toLiveRoomDetail(site: site, roomId: item.roomId); }, onLongPress: onLongPress, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Stack( children: [ ClipRRect( borderRadius: const BorderRadius.only( topLeft: Radius.circular(8), topRight: Radius.circular(8), ), child: NetImage( item.cover, fit: BoxFit.cover, height: 110, width: double.infinity, ), ), Positioned( right: 0, left: 0, bottom: 0, child: Container( padding: const EdgeInsets.symmetric( horizontal: 4, vertical: 8, ), decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [ Colors.black87, Colors.transparent, ], ), ), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Icon( site.iconData, color: Colors.white, size: 14, ), AppStyle.hGap4, Text( Utils.onlineToString(item.online), style: const TextStyle( fontSize: 12, color: Colors.white, ), ) ], ), ), ), ], ), Padding( padding: AppStyle.edgeInsetsA8.copyWith(bottom: 4), child: Text( item.title, maxLines: 1, overflow: TextOverflow.ellipsis, ), ), Padding( padding: AppStyle.edgeInsetsH8.copyWith(bottom: 8), child: Text( item.userName, maxLines: 1, style: const TextStyle( height: 1.4, fontSize: 12, color: Colors.grey), ), ) ], ), ); } } ================================================ FILE: simple_live_app/lib/widgets/net_image.dart ================================================ import 'package:extended_image/extended_image.dart'; import 'package:flutter/material.dart'; class NetImage extends StatelessWidget { final String picUrl; final double? width; final double? height; final BoxFit? fit; final double borderRadius; const NetImage(this.picUrl, {this.width, this.height, this.fit = BoxFit.cover, this.borderRadius = 0, super.key}); @override Widget build(BuildContext context) { if (picUrl.isEmpty) { return Image.asset( 'assets/images/logo.png', width: width, height: height, ); } var pic = picUrl; if (pic.startsWith("//")) { pic = 'https:$pic'; } return ClipRRect( borderRadius: BorderRadius.circular(borderRadius), child: ExtendedImage.network( pic, fit: fit, height: height, width: width, shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(borderRadius), loadStateChanged: (e) { if (e.extendedImageLoadState == LoadState.loading) { return const Icon( Icons.image, color: Colors.grey, size: 24, ); } if (e.extendedImageLoadState == LoadState.failed) { return const Icon( Icons.broken_image, color: Colors.grey, size: 24, ); } return null; }, ), ); } } ================================================ FILE: simple_live_app/lib/widgets/none_border_circular_textfield.dart ================================================ import 'package:flutter/material.dart'; class NoneBorderCircularTextField extends StatelessWidget { final TextEditingController editingController; final String? hintText; final String? helperText; final String? labelText; final String? errorText; final Widget? prefixIcon; final bool obscureText; final VoidCallback? onEditingComplete; final ValueChanged? onChanged; final VoidCallback? onTap; final TextAlign textAlign; final Widget? trailing; final TextInputType? inputType; final int? maxLines; final bool autoFocus; final FocusNode? focusNode; final bool? enable; final bool readOnly; final bool needPadding; const NoneBorderCircularTextField({ super.key, required this.editingController, this.hintText, this.helperText, this.labelText, this.errorText, this.prefixIcon, this.textAlign = TextAlign.start, this.obscureText = false, this.maxLines = 1, this.onEditingComplete, this.trailing, this.autoFocus = false, this.focusNode, this.inputType, this.onChanged, this.onTap, this.enable, this.readOnly = false, this.needPadding = true, }); @override Widget build(BuildContext context) { TextField common = TextField( enabled: enable, readOnly: readOnly, decoration: InputDecoration( prefixIcon: prefixIcon, hintText: hintText, filled: true, contentPadding: const EdgeInsets.symmetric(vertical: 4, horizontal: 12), suffix: trailing, helperText: helperText, helperMaxLines: 3, border: OutlineInputBorder( borderSide: BorderSide.none, borderRadius: const BorderRadius.all(Radius.circular(8.0)), ), labelText: labelText, errorText: errorText, errorMaxLines: 3, ), textAlign: textAlign, autofocus: autoFocus, keyboardType: inputType, maxLines: maxLines, controller: editingController, obscureText: obscureText, onEditingComplete: onEditingComplete, onChanged: onChanged, onTap: onTap, focusNode: focusNode, ); if (needPadding) { return Padding( padding: const EdgeInsets.only( top: 10, bottom: 10, ), child: common, ); } else { return common; } } } ================================================ FILE: simple_live_app/lib/widgets/page_grid_view.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/widgets/status/app_empty_widget.dart'; import 'package:simple_live_app/widgets/status/app_error_widget.dart'; import 'package:simple_live_app/widgets/status/app_loadding_widget.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter_easyrefresh/easy_refresh.dart'; import 'package:get/get.dart'; class PageGridView extends StatelessWidget { final BasePageController pageController; final IndexedWidgetBuilder itemBuilder; final EdgeInsets? padding; final bool firstRefresh; final Function()? onLoginSuccess; final bool showPageLoadding; final double crossAxisSpacing, mainAxisSpacing; final int crossAxisCount; final bool showPCRefreshButton; const PageGridView({ required this.itemBuilder, required this.pageController, this.padding, this.firstRefresh = false, this.showPageLoadding = false, this.onLoginSuccess, this.crossAxisSpacing = 0.0, this.mainAxisSpacing = 0.0, this.showPCRefreshButton = true, required this.crossAxisCount, super.key, }); @override Widget build(BuildContext context) { return Obx( () => Stack( children: [ EasyRefresh( header: MaterialHeader( completeDuration: const Duration(milliseconds: 400), ), footer: MaterialFooter( completeDuration: const Duration(milliseconds: 400), ), scrollController: pageController.scrollController, controller: pageController.easyRefreshController, firstRefresh: firstRefresh, onLoad: pageController.loadData, onRefresh: pageController.refreshData, child: MasonryGridView.count( padding: padding, itemCount: pageController.list.length, itemBuilder: itemBuilder, crossAxisCount: crossAxisCount, crossAxisSpacing: crossAxisSpacing, mainAxisSpacing: mainAxisSpacing, ), ), Positioned( bottom: 0, left: 0, right: 0, child: // 加载更多按钮 Visibility( visible: (Platform.isWindows || Platform.isLinux || Platform.isMacOS) && pageController.canLoadMore.value && !pageController.pageLoadding.value && !pageController.pageEmpty.value, child: Center( child: TextButton( onPressed: pageController.loadData, child: const Text("加载更多"), ), ), ), ), Positioned( bottom: 12, right: 12, child: // 加载更多按钮 Visibility( visible: (Platform.isWindows || Platform.isLinux || Platform.isMacOS) && pageController.canLoadMore.value && !pageController.pageLoadding.value && !pageController.pageEmpty.value && showPCRefreshButton, child: Center( child: IconButton( style: IconButton.styleFrom( backgroundColor: Get.theme.cardColor.withAlpha(200), elevation: 4, ), onPressed: () { pageController.refreshData(); }, icon: const Icon(Icons.refresh), ), ), ), ), Offstage( offstage: !pageController.pageEmpty.value, child: AppEmptyWidget( onRefresh: () => pageController.refreshData(), ), ), Offstage( offstage: !(showPageLoadding && pageController.pageLoadding.value), child: const AppLoaddingWidget(), ), Offstage( offstage: !pageController.pageError.value, child: AppErrorWidget( errorMsg: pageController.errorMsg.value, onRefresh: () => pageController.refreshData(), ), ), ], ), ); } } ================================================ FILE: simple_live_app/lib/widgets/page_list_view.dart ================================================ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:simple_live_app/app/controller/base_controller.dart'; import 'package:simple_live_app/widgets/status/app_empty_widget.dart'; import 'package:simple_live_app/widgets/status/app_error_widget.dart'; import 'package:simple_live_app/widgets/status/app_loadding_widget.dart'; import 'package:flutter_easyrefresh/easy_refresh.dart'; import 'package:get/get.dart'; typedef IndexedWidgetBuilder = Widget Function(BuildContext context, int index); class PageListView extends StatelessWidget { final BasePageController pageController; final IndexedWidgetBuilder itemBuilder; final IndexedWidgetBuilder? separatorBuilder; final EdgeInsets? padding; final bool firstRefresh; final Function()? onLoginSuccess; final bool showPageLoadding; final bool showPCRefreshButton; const PageListView({ required this.itemBuilder, required this.pageController, this.padding, this.firstRefresh = false, this.showPageLoadding = false, this.showPCRefreshButton = true, this.separatorBuilder, this.onLoginSuccess, super.key, }); @override Widget build(BuildContext context) { return Obx( () => Stack( children: [ EasyRefresh( header: MaterialHeader( completeDuration: const Duration(milliseconds: 400), ), footer: MaterialFooter( completeDuration: const Duration(milliseconds: 400), ), scrollController: pageController.scrollController, controller: pageController.easyRefreshController, firstRefresh: firstRefresh, onLoad: pageController.loadData, onRefresh: pageController.refreshData, child: ListView.separated( padding: padding, itemCount: pageController.list.length, itemBuilder: itemBuilder, separatorBuilder: separatorBuilder ?? (context, i) => const SizedBox(), ), ), Positioned( bottom: 0, left: 0, right: 0, child: // 加载更多按钮 Visibility( visible: (Platform.isWindows || Platform.isLinux || Platform.isMacOS) && pageController.canLoadMore.value && !pageController.pageLoadding.value && !pageController.pageEmpty.value, child: Center( child: TextButton( onPressed: pageController.loadData, child: const Text("加载更多"), ), ), ), ), Positioned( bottom: 12, right: 12, child: // 加载更多按钮 Visibility( visible: (Platform.isWindows || Platform.isLinux || Platform.isMacOS) && pageController.canLoadMore.value && !pageController.pageLoadding.value && !pageController.pageEmpty.value && showPCRefreshButton, child: Center( child: IconButton( style: IconButton.styleFrom( backgroundColor: Get.theme.cardColor.withAlpha(200), elevation: 4, ), onPressed: () { pageController.refreshData(); }, icon: const Icon(Icons.refresh), ), ), ), ), Offstage( offstage: !pageController.pageEmpty.value, child: AppEmptyWidget( onRefresh: () => pageController.refreshData(), ), ), Offstage( offstage: !(showPageLoadding && pageController.pageLoadding.value), child: const AppLoaddingWidget(), ), Offstage( offstage: !pageController.pageError.value, child: AppErrorWidget( errorMsg: pageController.errorMsg.value, onRefresh: () => pageController.refreshData(), ), ), ], ), ); } } ================================================ FILE: simple_live_app/lib/widgets/rectangular_indicator.dart ================================================ import 'package:flutter/material.dart'; /// 来自 https://github.com/adar2378/tab_indicator_styler class RectangularIndicator extends Decoration { /// topRight radius of the indicator, default to 5. final double topRightRadius; /// topLeft radius of the indicator, default to 5. final double topLeftRadius; /// bottomRight radius of the indicator, default to 0. final double bottomRightRadius; /// bottomLeft radius of the indicator, default to 0 final double bottomLeftRadius; /// Color of the indicator, default set to [Colors.black] final Color color; /// Horizontal padding of the indicator, default set to 0 final double horizontalPadding; /// Vertical padding of the indicator, default set to 0 final double verticalPadding; /// [PagingStyle] determines if the indicator should be fill or stroke, default to fill final PaintingStyle paintingStyle; /// StrokeWidth, used for [PaintingStyle.stroke], default set to 0 final double strokeWidth; const RectangularIndicator({ this.topRightRadius = 5, this.topLeftRadius = 5, this.bottomRightRadius = 0, this.bottomLeftRadius = 0, this.color = Colors.black, this.horizontalPadding = 0, this.verticalPadding = 0, this.paintingStyle = PaintingStyle.fill, this.strokeWidth = 2, }); @override MyCustomPainter createBoxPainter([VoidCallback? onChanged]) { return MyCustomPainter( this, onChanged, bottomLeftRadius: bottomLeftRadius, bottomRightRadius: bottomRightRadius, color: color, horizontalPadding: horizontalPadding, topLeftRadius: topLeftRadius, topRightRadius: topRightRadius, verticalPadding: verticalPadding, paintingStyle: paintingStyle, strokeWidth: strokeWidth, ); } } class MyCustomPainter extends BoxPainter { final RectangularIndicator decoration; final double topRightRadius; final double topLeftRadius; final double bottomRightRadius; final double bottomLeftRadius; final Color color; final double horizontalPadding; final double verticalPadding; final PaintingStyle paintingStyle; final double strokeWidth; MyCustomPainter( this.decoration, VoidCallback? onChanged, { required this.topRightRadius, required this.topLeftRadius, required this.bottomRightRadius, required this.bottomLeftRadius, required this.color, required this.horizontalPadding, required this.verticalPadding, required this.paintingStyle, required this.strokeWidth, }) : super(onChanged); @override void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { assert(horizontalPadding >= 0); assert(horizontalPadding < configuration.size!.width / 2, "Padding must be less than half of the size of the tab"); assert(verticalPadding < configuration.size!.height / 2 && verticalPadding >= 0); assert(strokeWidth >= 0 && strokeWidth < configuration.size!.width / 2 && strokeWidth < configuration.size!.height / 2); //offset is the position from where the decoration should be drawn. //configuration.size tells us about the height and width of the tab. Size mysize = Size(configuration.size!.width - (horizontalPadding * 2), configuration.size!.height - (2 * verticalPadding)); Offset myoffset = Offset(offset.dx + (horizontalPadding), offset.dy + verticalPadding); final Rect rect = myoffset & mysize; final Paint paint = Paint(); paint.color = color; paint.style = paintingStyle; paint.strokeWidth = 3; canvas.drawRRect( RRect.fromRectAndCorners( rect, bottomRight: Radius.circular(bottomRightRadius), bottomLeft: Radius.circular(bottomLeftRadius), topLeft: Radius.circular(topLeftRadius), topRight: Radius.circular(topRightRadius), ), paint); } } ================================================ FILE: simple_live_app/lib/widgets/settings/settings_action.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; class SettingsAction extends StatelessWidget { final String title; final String? subtitle; final Function()? onTap; final String? value; final Widget? leading; const SettingsAction({ required this.title, this.value, this.onTap, this.subtitle, this.leading, super.key, }); @override Widget build(BuildContext context) { return ListTile( // visualDensity: VisualDensity.compact, leading: leading, title: Text( title, style: Theme.of(context).textTheme.bodyLarge, ), shape: RoundedRectangleBorder( borderRadius: AppStyle.radius8, ), contentPadding: AppStyle.edgeInsetsL16.copyWith(right: 8), subtitle: subtitle == null ? null : Text( subtitle!, style: Get.textTheme.bodySmall!.copyWith(color: Colors.grey), ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ if (value != null) Text( value!, style: Theme.of(context) .textTheme .bodyMedium! .copyWith(color: Colors.grey), ), AppStyle.hGap4, const Icon( Icons.chevron_right, color: Colors.grey, ), ], ), onTap: onTap, ); } } ================================================ FILE: simple_live_app/lib/widgets/settings/settings_card.dart ================================================ import 'package:flutter/material.dart'; import 'package:simple_live_app/app/app_style.dart'; class SettingsCard extends StatelessWidget { final Widget child; const SettingsCard({required this.child, super.key}); @override Widget build(BuildContext context) { return Material( color: Theme.of(context).brightness == Brightness.dark ? Colors.grey.withAlpha(50) : Colors.white70, shape: RoundedRectangleBorder( borderRadius: AppStyle.radius8, side: BorderSide( color: Colors.grey.withAlpha(25), ), ), child: Container( decoration: BoxDecoration( borderRadius: AppStyle.radius8, ), child: child, ), ); } } ================================================ FILE: simple_live_app/lib/widgets/settings/settings_menu.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; class SettingsMenu extends StatelessWidget { final String title; final String? subtitle; final Map valueMap; final T value; final Widget? trailing; final Function(T)? onChanged; const SettingsMenu({ required this.title, required this.value, required this.valueMap, this.subtitle, this.onChanged, this.trailing, super.key, }); @override Widget build(BuildContext context) { return ListTile( visualDensity: VisualDensity.compact, title: Text( title, style: Theme.of(context).textTheme.bodyLarge, ), shape: RoundedRectangleBorder( borderRadius: AppStyle.radius8, ), contentPadding: AppStyle.edgeInsetsL16.copyWith(right: 8), subtitle: subtitle == null ? null : Text( subtitle!, style: Get.textTheme.bodySmall!.copyWith(color: Colors.grey), ), trailing: trailing ?? Row( mainAxisSize: MainAxisSize.min, children: [ Text( valueMap[value]!.tr, style: Theme.of(context) .textTheme .bodyMedium! .copyWith(color: Colors.grey), ), AppStyle.hGap4, const Icon( Icons.chevron_right, color: Colors.grey, ), ], ), onTap: () => openMenu(context), ); } void openMenu(BuildContext context) { showModalBottomSheet( context: context, showDragHandle: true, useSafeArea: true, //useSafeArea似乎无效 builder: (_) => SafeArea( top: false, child: SingleChildScrollView( child: RadioGroup( groupValue: value, onChanged: (e) { Get.back(); onChanged?.call(e as T); }, child: Column( mainAxisSize: MainAxisSize.min, children: valueMap.keys .map( (e) => RadioListTile( value: e, title: Text( (valueMap[e]?.tr) ?? "???", style: Get.textTheme.bodyMedium, ), ), ) .toList(), ), ), ), ), ); } } ================================================ FILE: simple_live_app/lib/widgets/settings/settings_menu_check.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:remixicon/remixicon.dart'; import 'package:simple_live_app/app/app_style.dart'; class _MenuCheckController extends GetxController { final RxList selectedItems; _MenuCheckController(List initial) : selectedItems = RxList.from(initial); void toggle(T item) { if (selectedItems.contains(item)) { selectedItems.remove(item); } else { selectedItems.add(item); } } } class SettingsMenuCheck extends StatelessWidget { final String title; final String? subtitle; final List items; final List initialSelection; final Future> Function()? itemsProvider; final List Function(List providedItems)? initialSelectionProvider; final String Function(T item) itemToString; final Function(List selectedItems)? onConfirm; final String? confirmText; final String? modalTitle; const SettingsMenuCheck({ required this.title, required this.itemToString, this.items = const [], this.initialSelection = const [], this.itemsProvider, this.initialSelectionProvider, this.subtitle, this.onConfirm, this.confirmText, this.modalTitle, super.key, }); @override Widget build(BuildContext context) { // 这里需要状态管理,暂时不实现 final displayItemsCount = items.length; final displaySelectedCount = initialSelection.length; return ListTile( visualDensity: VisualDensity.compact, title: Text( title, style: Theme.of(context).textTheme.bodyLarge, ), shape: RoundedRectangleBorder( borderRadius: AppStyle.radius8, ), contentPadding: AppStyle.edgeInsetsL16.copyWith(right: 8), subtitle: subtitle == null ? null : Text( subtitle!, style: Get.textTheme.bodySmall!.copyWith(color: Colors.grey), ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ Text( '$displaySelectedCount/$displayItemsCount', style: Theme.of(context) .textTheme .bodyMedium! .copyWith(color: Colors.grey), ), AppStyle.hGap4, const Icon( Icons.chevron_right, color: Colors.grey, ), ], ), onTap: _handleTap, ); } Future _handleTap() async { List menuItems; List menuInitialSelection; if (itemsProvider != null) { SmartDialog.showLoading(msg: ""); try { menuItems = await itemsProvider!(); if (initialSelectionProvider != null) { menuInitialSelection = initialSelectionProvider!(menuItems); } else { menuInitialSelection = menuItems.toList(); } } finally { SmartDialog.dismiss(); } } else { menuItems = items; menuInitialSelection = initialSelection; } if (menuItems.isEmpty) { return; } _openMenu(Get.context!, menuItems, menuInitialSelection); } void _openMenu( BuildContext context, List items, List initialSelection) { final controller = _MenuCheckController(initialSelection); showModalBottomSheet( context: context, isScrollControlled: true, showDragHandle: false, useSafeArea: true, constraints: BoxConstraints( maxWidth: 600, ), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(12), topRight: Radius.circular(12), ), ), builder: (_) { return SafeArea( top: false, child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( contentPadding: const EdgeInsets.only( left: 12, ), title: Text( modalTitle?.tr ?? title.tr, ), trailing: IconButton( onPressed: () { Get.back(); onConfirm?.call(controller.selectedItems.toList()); }, icon: const Icon(Remix.delete_bin_line), ), ), Flexible( child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: items.map((item) { return Obx(() => CheckboxListTile( value: controller.selectedItems.contains(item), controlAffinity: ListTileControlAffinity.leading, title: Text( itemToString(item), style: Get.textTheme.bodyMedium, ), onChanged: (bool? selected) { controller.toggle(item); }, )); }).toList(), ), ), ), ], ), ); }, ); } } ================================================ FILE: simple_live_app/lib/widgets/settings/settings_number.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; class SettingsNumber extends StatelessWidget { final String title; final String? subtitle; final String unit; final int value; final int step; final int min; final int max; final String? displayValue; final Function(int)? onChanged; const SettingsNumber( {required this.title, required this.value, required this.max, this.subtitle, this.onChanged, this.step = 1, this.min = 0, this.unit = '', this.displayValue, super.key}); @override Widget build(BuildContext context) { return ListTile( visualDensity: VisualDensity.compact, title: Text( title, style: Get.textTheme.bodyLarge, ), shape: RoundedRectangleBorder( borderRadius: AppStyle.radius8, ), subtitle: subtitle == null ? null : Text( subtitle!, style: Get.textTheme.bodySmall!.copyWith(color: Colors.grey), ), contentPadding: AppStyle.edgeInsetsL16.copyWith(right: 12), trailing: Container( decoration: BoxDecoration( color: Colors.grey.withAlpha(25), borderRadius: AppStyle.radius24, ), height: 36, child: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( padding: AppStyle.edgeInsetsA4, constraints: const BoxConstraints( minHeight: 32, ), onPressed: () { int newValue = value - step; if (newValue < min) { newValue = min; } onChanged?.call(newValue); }, icon: Icon( Icons.remove, color: Get.textTheme.bodyMedium!.color!.withAlpha(150), ), ), Text( displayValue ?? "$value$unit", textAlign: TextAlign.center, style: Theme.of(context) .textTheme .bodyMedium! .copyWith(color: Colors.grey), ), IconButton( padding: AppStyle.edgeInsetsA4, constraints: const BoxConstraints( minHeight: 32, ), onPressed: () { int newValue = value + step; if (newValue > max) { newValue = max; } onChanged?.call(newValue); }, icon: Icon( Icons.add, color: Get.textTheme.bodyMedium!.color!.withAlpha(150), ), ), ], ), ), onTap: () => openSilder(context), ); } void openSilder(BuildContext context) { var newValue = value.obs; showModalBottomSheet( context: context, showDragHandle: true, useSafeArea: true, //useSafeArea似乎无效 builder: (_) => SafeArea( top: false, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: AppStyle.edgeInsetsH16, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( title, style: Get.textTheme.titleMedium, ), Obx( () => Text( "${newValue.value}$unit", style: Get.textTheme.titleMedium, ), ), ], ), ), Obx( () => Slider( value: newValue.value.toDouble(), min: min.toDouble(), max: max.toDouble(), onChanged: (e) { newValue.value = e.toInt(); }, ), ), Padding( padding: AppStyle.edgeInsetsH16, child: TextButton( onPressed: () { onChanged?.call(newValue.value); Get.back(); }, child: const Text("确定"), ), ), ], ), ), ); } } ================================================ FILE: simple_live_app/lib/widgets/settings/settings_switch.dart ================================================ import 'package:flutter/material.dart'; import 'package:simple_live_app/app/app_style.dart'; class SettingsSwitch extends StatelessWidget { final bool value; final String title; final String? subtitle; final Function(bool) onChanged; const SettingsSwitch({ required this.value, required this.title, this.subtitle, required this.onChanged, super.key, }); @override Widget build(BuildContext context) { return SwitchListTile( title: Text( title, style: Theme.of(context).textTheme.bodyLarge, ), shape: RoundedRectangleBorder( borderRadius: AppStyle.radius8, ), trackOutlineColor: const WidgetStatePropertyAll(Colors.transparent), //visualDensity: VisualDensity.compact, contentPadding: AppStyle.edgeInsetsL16.copyWith(right: 8), subtitle: subtitle != null ? Text( subtitle!, style: Theme.of(context) .textTheme .bodySmall! .copyWith(color: Colors.grey), ) : null, value: value, onChanged: onChanged, ); } } ================================================ FILE: simple_live_app/lib/widgets/shadow_card.dart ================================================ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; class ShadowCard extends StatelessWidget { final Widget child; final double radius; final Function()? onTap; final Function()? onLongPress; const ShadowCard({ required this.child, this.radius = 8.0, this.onTap, this.onLongPress, super.key, }); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(radius), boxShadow: Get.isDarkMode ? [] : [ BoxShadow( blurRadius: 4, color: Colors.grey.withAlpha(50), ) ], ), child: Material( color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(radius), child: InkWell( borderRadius: BorderRadius.circular(radius), onLongPress: onLongPress, onTap: onTap, child: Container( decoration: BoxDecoration( borderRadius: AppStyle.radius8, ), child: child, ), ), ), ); } } ================================================ FILE: simple_live_app/lib/widgets/status/app_empty_widget.dart ================================================ import 'package:flutter/material.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:lottie/lottie.dart'; class AppEmptyWidget extends StatelessWidget { final Function()? onRefresh; const AppEmptyWidget({this.onRefresh, super.key}); @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { onRefresh?.call(); }, child: Padding( padding: AppStyle.edgeInsetsA12, child: Column( mainAxisSize: MainAxisSize.min, children: [ LottieBuilder.asset( 'assets/lotties/empty.json', width: 200, height: 200, repeat: false, ), const Text( "这里什么都没有", textAlign: TextAlign.center, style: TextStyle(fontSize: 12, color: Colors.grey), ), ], ), ), ), ); } } ================================================ FILE: simple_live_app/lib/widgets/status/app_error_widget.dart ================================================ import 'package:flutter/material.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:lottie/lottie.dart'; class AppErrorWidget extends StatelessWidget { final Function()? onRefresh; final String errorMsg; const AppErrorWidget({this.errorMsg = "", this.onRefresh, super.key}); @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { onRefresh?.call(); }, child: Padding( padding: AppStyle.edgeInsetsA12, child: Column( mainAxisSize: MainAxisSize.min, children: [ LottieBuilder.asset( 'assets/lotties/error.json', width: 260, repeat: false, ), Text( "$errorMsg\r\n点击刷新", textAlign: TextAlign.center, style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], ), ), ), ); } } ================================================ FILE: simple_live_app/lib/widgets/status/app_loadding_widget.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:simple_live_app/app/app_style.dart'; class AppLoaddingWidget extends StatelessWidget { const AppLoaddingWidget({super.key}); @override Widget build(BuildContext context) { return Center( child: Container( padding: AppStyle.edgeInsetsA12, decoration: BoxDecoration( shape: BoxShape.circle, color: Theme.of(context).cardColor, boxShadow: Get.isDarkMode ? [] : [ BoxShadow( blurRadius: 4, color: Colors.grey.withAlpha(50), ) ], ), child: const CupertinoActivityIndicator( radius: 10, ), ), ); } } ================================================ FILE: simple_live_app/lib/widgets/superchat_card.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:simple_live_app/app/app_style.dart'; import 'package:simple_live_app/app/utils.dart'; import 'package:simple_live_app/widgets/net_image.dart'; import 'package:simple_live_core/simple_live_core.dart'; class SuperChatCard extends StatefulWidget { final LiveSuperChatMessage message; final Function()? onExpire; const SuperChatCard( this.message, { required this.onExpire, super.key, }); @override State createState() => _SuperChatCardState(); } class _SuperChatCardState extends State { late Timer timer; int countdown = 0; @override void initState() { var currentTime = DateTime.now().millisecondsSinceEpoch ~/ 1000; var endTime = widget.message.endTime.millisecondsSinceEpoch ~/ 1000; countdown = endTime - currentTime; timer = Timer.periodic(const Duration(seconds: 1), timerCallback); super.initState(); } void timerCallback(Timer e) { if (countdown <= 0) { widget.onExpire?.call(); timer.cancel(); return; } setState(() { countdown -= 1; }); } @override Widget build(BuildContext context) { return ClipRRect( borderRadius: AppStyle.radius8, child: Container( decoration: BoxDecoration( color: Utils.convertHexColor(widget.message.backgroundColor), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: AppStyle.edgeInsetsA8, child: Row( children: [ NetImage( widget.message.face, width: 48, height: 48, borderRadius: 36, ), AppStyle.hGap12, Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( widget.message.userName, style: const TextStyle( color: AppColors.black333, ), ), Text( "¥${widget.message.price}", style: const TextStyle( fontSize: 12, color: Colors.grey, ), ), ], ), ), Text( "$countdown", style: const TextStyle( fontSize: 14, color: Colors.grey, ), ), ], ), ), Container( decoration: BoxDecoration( color: Utils.convertHexColor(widget.message.backgroundBottomColor), ), padding: AppStyle.edgeInsetsA8, child: SelectableText( widget.message.message, style: const TextStyle(color: Colors.white), ), ), ], ), ), ); } @override void dispose() { timer.cancel(); super.dispose(); } } ================================================ FILE: simple_live_app/lib/widgets/ui/after_post_frame.dart ================================================ import 'package:flutter/widgets.dart'; mixin AfterFirstFrameMixin on State { bool _afterFirstFrame = false; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _afterFirstFrame = true; }); } void afterFirstFrame(void Function() callback) { if (_afterFirstFrame) { callback(); } } } ================================================ FILE: simple_live_app/linux/.gitignore ================================================ flutter/ephemeral ================================================ FILE: simple_live_app/linux/CMakeLists.txt ================================================ # Project-level configuration. cmake_minimum_required(VERSION 3.13) project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. set(BINARY_NAME "io.github.SlotSun.Slive") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID set(APPLICATION_ID "io.github.SlotSun.Slive") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(SET CMP0063 NEW) # Load bundled libraries from the lib/ directory relative to the binary. set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Root filesystem for cross-building. if(FLUTTER_TARGET_PLATFORM_SYSROOT) set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) endif() # Define build configuration options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() # Compilation settings that should be applied to most targets. # # Be cautious about adding new options here, as plugins use this function by # default. In most cases, you should add new options to specific targets instead # of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") endfunction() # Flutter library and tool build rules. set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) # Application build; see runner/CMakeLists.txt. add_subdirectory("runner") # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) # Only the install-generated bundle's copy of the executable will launch # correctly, since the resources must in the right relative locations. To avoid # people trying to run the unbundled copy, put it in a subdirectory instead of # the default top-level location. set_target_properties(${BINARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" ) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() # Start with a clean build bundle directory every time. install(CODE " file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) install(FILES "${bundled_library}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endforeach(bundled_library) # Copy the native assets provided by the build.dart from all packages. set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") install(DIRECTORY "${NATIVE_ASSETS_DIR}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() ================================================ FILE: simple_live_app/linux/flutter/CMakeLists.txt ================================================ # This file controls Flutter-level build steps. It should not be edited. cmake_minimum_required(VERSION 3.10) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. # Serves the same purpose as list(TRANSFORM ... PREPEND ...), # which isn't available in 3.10. function(list_prepend LIST_NAME PREFIX) set(NEW_LIST "") foreach(element ${${LIST_NAME}}) list(APPEND NEW_LIST "${PREFIX}${element}") endforeach(element) set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) endfunction() # === Flutter Library === # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "fl_basic_message_channel.h" "fl_binary_codec.h" "fl_binary_messenger.h" "fl_dart_project.h" "fl_engine.h" "fl_json_message_codec.h" "fl_json_method_codec.h" "fl_message_codec.h" "fl_method_call.h" "fl_method_channel.h" "fl_method_codec.h" "fl_method_response.h" "fl_plugin_registrar.h" "fl_plugin_registry.h" "fl_standard_message_codec.h" "fl_standard_method_codec.h" "fl_string_codec.h" "fl_value.h" "fl_view.h" "flutter_linux.h" ) list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") target_link_libraries(flutter INTERFACE PkgConfig::GTK PkgConfig::GLIB PkgConfig::GIO ) add_dependencies(flutter flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/_phony_ COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ) ================================================ FILE: simple_live_app/linux/flutter/generated_plugin_registrant.cc ================================================ // // Generated file. Do not edit. // // clang-format off #include "generated_plugin_registrant.h" #include #include #include #include #include #include #include #include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) dynamic_color_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin"); dynamic_color_plugin_register_with_registrar(dynamic_color_registrar); g_autoptr(FlPluginRegistrar) flutter_qjs_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterQjsPlugin"); flutter_qjs_plugin_register_with_registrar(flutter_qjs_registrar); g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin"); media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar); g_autoptr(FlPluginRegistrar) media_kit_video_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitVideoPlugin"); media_kit_video_plugin_register_with_registrar(media_kit_video_registrar); g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); g_autoptr(FlPluginRegistrar) volume_controller_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "VolumeControllerPlugin"); volume_controller_plugin_register_with_registrar(volume_controller_registrar); g_autoptr(FlPluginRegistrar) window_manager_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); window_manager_plugin_register_with_registrar(window_manager_registrar); } ================================================ FILE: simple_live_app/linux/flutter/generated_plugin_registrant.h ================================================ // // Generated file. Do not edit. // // clang-format off #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ #include // Registers Flutter plugins. void fl_register_plugins(FlPluginRegistry* registry); #endif // GENERATED_PLUGIN_REGISTRANT_ ================================================ FILE: simple_live_app/linux/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST dynamic_color flutter_qjs media_kit_libs_linux media_kit_video screen_retriever_linux url_launcher_linux volume_controller window_manager ) list(APPEND FLUTTER_FFI_PLUGIN_LIST rust_lib_simple_live_app ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: simple_live_app/linux/packaging/aur/slive.desktop ================================================ [Desktop Entry] Name=Slive Exec=io.github.SlotSun.Slive Icon=io.github.SlotSun.Slive Comment=Slive is a lightweight and user-friendly application for watching live streams across multiple platforms, offering a seamless watching experience with support for popular streaming services. Comment[zh_CN]=Slive 是一个轻量且用户友好的应用程序,可跨多个平台观看直播流,提供无缝的观看体验,并支持流行的流媒体服务。 Type=Application Terminal=false Categories=Video;AudioVideo;Player; ================================================ FILE: simple_live_app/linux/packaging/deb/make_config.yaml ================================================ display_name: Slive package_name: Slive maintainer: name: slotsun email: slot_sun@outlook.com priority: optional section: x11 installed_size: 24400 essential: false icon: assets/images/logo.png generic_name: Livestream Player keywords: - Livestream - Video categories: - AudioVideo - Audio - Video - Network startup_notify: true metainfo: assets/io.github.SlotSun.Slive.metainfo.xml ================================================ FILE: simple_live_app/linux/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.13) project(runner LANGUAGES CXX) # Define the application target. To change its name, change BINARY_NAME in the # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer # work. # # Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) # Apply the standard set of build settings. This can be removed for applications # that need different build settings. apply_standard_settings(${BINARY_NAME}) # Add preprocessor definitions for the application ID. add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # Add dependency libraries. Add any application-specific dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) target_link_libraries(${BINARY_NAME} PRIVATE ${MIMALLOC_LIB}) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") ================================================ FILE: simple_live_app/linux/runner/main.cc ================================================ #include "my_application.h" int main(int argc, char** argv) { g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); } ================================================ FILE: simple_live_app/linux/runner/my_application.cc ================================================ #include using namespace std; using namespace std::filesystem; #include "my_application.h" #include #ifdef GDK_WINDOWING_X11 #include #endif #include "flutter/generated_plugin_registrant.h" struct _MyApplication { GtkApplication parent_instance; char** dart_entrypoint_arguments; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Called when first Flutter frame received. static void first_frame_cb(MyApplication* self, FlView *view) { gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); } // Implements GApplication::activate. static void my_application_activate(GApplication* application) { MyApplication* self = MY_APPLICATION(application); GList* list = gtk_application_get_windows(GTK_APPLICATION(application)); GtkWindow* existing_window = list ? GTK_WINDOW(list->data) : NULL; if (existing_window) { gtk_window_present(existing_window); return; } GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); const string iconFilename = "assets/images/logo.png"; path execDir = canonical(read_symlink("/proc/self/exe")).parent_path(); path iconPath = execDir / "data/flutter_assets" / iconFilename; gtk_window_set_icon_from_file(GTK_WINDOW(window), iconPath.c_str(), NULL); // Use a header bar when running in GNOME as this is the common style used // by applications and is the setup most users will be using (e.g. Ubuntu // desktop). // If running on X and not using GNOME then just use a traditional title bar // in case the window manager does more exotic layout, e.g. tiling. // If running on Wayland assume the header bar will work (may need changing // if future cases occur). gboolean use_header_bar = FALSE; #ifdef GDK_WINDOWING_X11 GdkScreen* screen = gtk_window_get_screen(window); if (GDK_IS_X11_SCREEN(screen)) { const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); if (g_strcmp0(wm_name, "GNOME Shell") != 0) { use_header_bar = FALSE; } } #endif if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, "Slive"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { gtk_window_set_title(window, "Slive"); } gtk_window_set_default_size(window, 1280, 720); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); FlView* view = fl_view_new(project); GdkRGBA background_color; // Background defaults to black, override it here if necessary, e.g. #00000000 for transparent. gdk_rgba_parse(&background_color, "#000000"); fl_view_set_background_color(view, &background_color); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); // Show the window when Flutter renders. // Requires the view to be realized so we can start rendering. g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), self); gtk_widget_realize(GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } // Implements GApplication::local_command_line. static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { MyApplication* self = MY_APPLICATION(application); // Strip out the first argument as it is the binary name. self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); g_autoptr(GError) error = nullptr; if (!g_application_register(application, nullptr, &error)) { g_warning("Failed to register: %s", error->message); *exit_status = 1; return TRUE; } g_application_activate(application); *exit_status = 0; return TRUE; } // Implements GApplication::startup. static void my_application_startup(GApplication* application) { //MyApplication* self = MY_APPLICATION(object); // Perform any actions required at application startup. G_APPLICATION_CLASS(my_application_parent_class)->startup(application); } // Implements GApplication::shutdown. static void my_application_shutdown(GApplication* application) { //MyApplication* self = MY_APPLICATION(object); // Perform any actions required at application shutdown. G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); } // Implements GObject::dispose. static void my_application_dispose(GObject* object) { MyApplication* self = MY_APPLICATION(object); g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); G_OBJECT_CLASS(my_application_parent_class)->dispose(object); } static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; G_APPLICATION_CLASS(klass)->startup = my_application_startup; G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { // Set the program name to the application ID, which helps various systems // like GTK and desktop environments map this running application to its // corresponding .desktop file. This ensures better integration by allowing // the application to be recognized beyond its binary name. g_set_prgname(APPLICATION_ID); return MY_APPLICATION(g_object_new(my_application_get_type(), "application-id", APPLICATION_ID, // "flags", G_APPLICATION_FLAGS_NONE, nullptr)); } ================================================ FILE: simple_live_app/linux/runner/my_application.h ================================================ #ifndef FLUTTER_MY_APPLICATION_H_ #define FLUTTER_MY_APPLICATION_H_ #include G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, GtkApplication) /** * my_application_new: * * Creates a new Flutter-based application. * * Returns: a new #MyApplication. */ MyApplication* my_application_new(); #endif // FLUTTER_MY_APPLICATION_H_ ================================================ FILE: simple_live_app/macos/.gitignore ================================================ # Flutter-related **/Flutter/ephemeral/ **/Pods/ # Xcode-related **/dgph **/xcuserdata/ ================================================ FILE: simple_live_app/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: simple_live_app/macos/Flutter/Flutter-Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: simple_live_app/macos/Flutter/GeneratedPluginRegistrant.swift ================================================ // // Generated file. Do not edit. // import FlutterMacOS import Foundation import connectivity_plus import device_info_plus import dynamic_color import file_picker import firebase_analytics import firebase_core import firebase_crashlytics import flutter_inappwebview_macos import media_kit_libs_macos_video import media_kit_video import network_info_plus import package_info_plus import screen_brightness_macos import screen_retriever_macos import share_plus import url_launcher_macos import volume_controller import wakelock_plus import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FirebaseAnalyticsPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) VolumeControllerPlugin.register(with: registry.registrar(forPlugin: "VolumeControllerPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } ================================================ FILE: simple_live_app/macos/Podfile ================================================ platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: simple_live_app/macos/Runner/AppDelegate.swift ================================================ import Cocoa import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: simple_live_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images": [ { "size": "16x16", "idiom": "mac", "filename": "icon-16.png", "scale": "1x" }, { "size": "16x16", "idiom": "mac", "filename": "icon-16@2x.png", "scale": "2x" }, { "size": "32x32", "idiom": "mac", "filename": "icon-32.png", "scale": "1x" }, { "size": "32x32", "idiom": "mac", "filename": "icon-32@2x.png", "scale": "2x" }, { "size": "128x128", "idiom": "mac", "filename": "icon-128.png", "scale": "1x" }, { "size": "128x128", "idiom": "mac", "filename": "icon-128@2x.png", "scale": "2x" }, { "size": "256x256", "idiom": "mac", "filename": "icon-256.png", "scale": "1x" }, { "size": "256x256", "idiom": "mac", "filename": "icon-256@2x.png", "scale": "2x" }, { "size": "512x512", "idiom": "mac", "filename": "icon-512.png", "scale": "1x" }, { "size": "512x512", "idiom": "mac", "filename": "icon-512@2x.png", "scale": "2x" } ], "info": { "version": 1, "author": "icon.wuruihong.com" } } ================================================ FILE: simple_live_app/macos/Runner/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: simple_live_app/macos/Runner/Base.lproj/MainMenu.xib ================================================ ================================================ FILE: simple_live_app/macos/Runner/Configs/AppInfo.xcconfig ================================================ // Application-level settings for the Runner target. // // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the // future. If not, the values below would default to using the project name when this becomes a // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. PRODUCT_NAME = Simple Live // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = com.xycz.simpleLiveApp // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2023 com.xycz. All rights reserved. ================================================ FILE: simple_live_app/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: simple_live_app/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: simple_live_app/macos/Runner/Configs/Warnings.xcconfig ================================================ WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings GCC_WARN_UNDECLARED_SELECTOR = YES CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE CLANG_WARN__DUPLICATE_METHOD_MATCH = YES CLANG_WARN_PRAGMA_PACK = YES CLANG_WARN_STRICT_PROTOTYPES = YES CLANG_WARN_COMMA = YES GCC_WARN_STRICT_SELECTOR_MATCH = YES CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES GCC_WARN_SHADOW = YES CLANG_WARN_UNREACHABLE_CODE = YES ================================================ FILE: simple_live_app/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server com.apple.security.network.client com.apple.security.files.user-selected.read-write ================================================ FILE: simple_live_app/macos/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: simple_live_app/macos/Runner/MainFlutterWindow.swift ================================================ import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } ================================================ FILE: simple_live_app/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.network.client com.apple.security.files.user-selected.read-write com.apple.security.network.server ================================================ FILE: simple_live_app/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { isa = PBXAggregateTarget; buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; buildPhases = ( 33CC111E2044C6BF0003C045 /* ShellScript */, ); dependencies = ( ); name = "Flutter Assemble"; productName = FLX; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 33E8CA35C15B26E48DA4EBD7 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4C1377411379D99120C8F83 /* Pods_Runner.framework */; }; 9E6C57AAF7AD9FA590476061 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AEF14C9BF8504F57660D2A38 /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC10EC2044A3C60003C045; remoteInfo = Runner; }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC111A2044C6BA0003C045; remoteInfo = FLX; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 33CC110E2044A8840003C045 /* Bundle Framework */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 22F821424EC724046ABB4672 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* simple_live_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = simple_live_app.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 63D7158B8577651B73F3032F /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 7E3E460DBF561AAF73690196 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; AEF14C9BF8504F57660D2A38 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B51A5741C0A7E288C9368C1A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; C7907028FBA71F2E559043FF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; D4C1377411379D99120C8F83 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D9BBDE6BF6193671AE9960B0 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 331C80D2294CF70F00263BE5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 9E6C57AAF7AD9FA590476061 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 33E8CA35C15B26E48DA4EBD7 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 24357D9109B54496E939BAD5 /* Pods */ = { isa = PBXGroup; children = ( 22F821424EC724046ABB4672 /* Pods-Runner.debug.xcconfig */, C7907028FBA71F2E559043FF /* Pods-Runner.release.xcconfig */, B51A5741C0A7E288C9368C1A /* Pods-Runner.profile.xcconfig */, 63D7158B8577651B73F3032F /* Pods-RunnerTests.debug.xcconfig */, D9BBDE6BF6193671AE9960B0 /* Pods-RunnerTests.release.xcconfig */, 7E3E460DBF561AAF73690196 /* Pods-RunnerTests.profile.xcconfig */, ); name = Pods; path = Pods; sourceTree = ""; }; 331C80D6294CF71000263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( 331C80D7294CF71000263BE5 /* RunnerTests.swift */, ); path = RunnerTests; sourceTree = ""; }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, ); path = Configs; sourceTree = ""; }; 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, 24357D9109B54496E939BAD5 /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* simple_live_app.app */, 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, ); path = Flutter; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( D4C1377411379D99120C8F83 /* Pods_Runner.framework */, AEF14C9BF8504F57660D2A38 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 331C80D4294CF70F00263BE5 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( 1C0ABA43D8228815BB859BFE /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, ); buildRules = ( ); dependencies = ( 331C80DA294CF71000263BE5 /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 671FAACF9F58D03A0C709E83 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, A261E453836422CA4D990339 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* simple_live_app.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { CreatedOnToolsVersion = 14.0; TestTargetID = 33CC10EC2044A3C60003C045; }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 33CC111A2044C6BA0003C045 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 33CC10E42044A3C60003C045; productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 331C80D3294CF70F00263BE5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 1C0ABA43D8228815BB859BFE /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( Flutter/ephemeral/FlutterInputs.xcfilelist, ); inputPaths = ( Flutter/ephemeral/tripwire, ); outputFileListPaths = ( Flutter/ephemeral/FlutterOutputs.xcfilelist, ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; 671FAACF9F58D03A0C709E83 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; A261E453836422CA4D990339 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 331C80D1294CF70F00263BE5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC10EC2044A3C60003C045 /* Runner */; targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 33CC10F52044A3C60003C045 /* Base */, ); name = MainMenu.xib; path = Runner; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 63D7158B8577651B73F3032F /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.xycz.simpleLiveApp.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/simple_live_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/simple_live_app"; }; name = Debug; }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = D9BBDE6BF6193671AE9960B0 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.xycz.simpleLiveApp.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/simple_live_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/simple_live_app"; }; name = Release; }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7E3E460DBF561AAF73690196 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.xycz.simpleLiveApp.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/simple_live_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/simple_live_app"; }; name = Profile; }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Profile; }; 338D0CEA231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; }; 33CC10F92044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 33CC10FA2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 33CC10FC2044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 33CC10FD2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 331C80DB294CF71000263BE5 /* Debug */, 331C80DC294CF71000263BE5 /* Release */, 331C80DD294CF71000263BE5 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10F92044A3C60003C045 /* Debug */, 33CC10FA2044A3C60003C045 /* Release */, 338D0CE9231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10FC2044A3C60003C045 /* Debug */, 33CC10FD2044A3C60003C045 /* Release */, 338D0CEA231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC111C2044C6BA0003C045 /* Debug */, 33CC111D2044C6BA0003C045 /* Release */, 338D0CEB231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } ================================================ FILE: simple_live_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: simple_live_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: simple_live_app/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: simple_live_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: simple_live_app/macos/RunnerTests/RunnerTests.swift ================================================ import FlutterMacOS import Cocoa import XCTest class RunnerTests: XCTestCase { func testExample() { // If you add code to the Runner application, consider adding tests here. // See https://developer.apple.com/documentation/xctest for more information about using XCTest. } } ================================================ FILE: simple_live_app/macos/packaging/dmg/make_config.yaml ================================================ title: Simple Live contents: - x: 448 y: 344 type: link path: "/Applications" - x: 192 y: 344 type: file path: Simple Live.app ================================================ FILE: simple_live_app/pubspec.yaml ================================================ name: simple_live_app version: 1.8.7+10807 publish_to: none description: "Slive APP" environment: sdk: '>=3.0.5 <4.0.0' flutter: 3.38.6 dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter simple_live_core: path: ../simple_live_core # 图标 cupertino_icons: ^1.0.8 remixicon: ^1.4.1 # Remix 图标 # 框架、工具 archive: ^4.0.7 # 文件压缩 get: ^4.7.2 # 状态管理、路由管理、国际化 dio: ^5.4.3+1 # 网络请求 hive: 2.2.3 # 持久化存储 hive_flutter: 1.1.0 # 持久化存储 logger: ^2.0.2 # 日志 firebase_analytics: ^12.1.0 firebase_crashlytics: ^5.0.6 # firebase firebase_core: ^4.3.0 intl: ^0.20.2 # 国际化 pinyin: ^3.3.0 qr_flutter: ^4.1.0 # 二维码生成 dynamic_color: ^1.8.1 # 动态颜色 path: any udp: ^5.0.3 # UDP fractional_indexing_dart: ^1.0.7 synchronized: ^3.3.1 # 线程安全 collection: ^1.18.0 pool: ^1.5.2 #并发控制器 # ffi rust_lib_simple_live_app: path: rust_builder flutter_rust_bridge: 2.11.1 # Widget flutter_staggered_grid_view: ^0.7.0 # 瀑布流 / GridView flutter_easyrefresh: 2.2.2 # 下拉刷新、上拉加载 extended_image: ^10.0.1 # 拓展 Image ,支持缓存 flutter_smart_dialog: ^4.9.8+9 # 各种弹窗 Toast / Dialog / Popup sticky_headers: ^0.3.0+2 # 吸顶 lottie: ^3.3.2 # Lottie 动画 canvas_danmaku: ^0.3.1 # 弹幕库 # 系统交互 package_info_plus: ^8.3.1 # 包信息 device_info_plus: ^12.2.0 # 设备信息 url_launcher: ^6.3.1 # 打开链接 share_plus: ^12.0.1 # 分享 path_provider: ^2.1.5 # 常用路径 cross_file: ^0.3.5+1 # 跨平台文件 # permission_handler: ^12.0.1 # 权限处理 permission_handler_apple: ^9.4.7 permission_handler_android: ^13.0.1 permission_handler_platform_interface: ^4.3.0 flutter_image_gallery_saver: ^1.1.3 # 图片保存到相册 screen_brightness: ^2.1.7 # 亮度控制 auto_orientation_v2: ^2.3.7 # 屏幕方向 wakelock_plus: ^1.3.3 # 屏幕常亮 file_picker: ^10.3.8 # 文件选择 window_manager: ^0.5.0 # 窗口管理 floating: ^6.0.0 # PIP 画中画 flutter_inappwebview: git: url: https://github.com/guide-inc-org/guide-flutter_inappwebview.git path: flutter_inappwebview ref: sbi_fx_pc/v6.2.0-beta.3 connectivity_plus: ^7.0.0 # 网络状态 qr_code_scanner_plus: ^2.0.14 # 二维码扫描 # 网络相关 webdav_client: ^1.2.2 # WebDAV shelf: ^1.4.2 shelf_router: ^1.1.4 network_info_plus: ^7.0.0 signalr_netcore: ^1.4.4 # SignalR # 视频播放 volume_controller: any media_kit: git: url: https://github.com/Predidit/media-kit.git ref: 7b248251fc274f986635876946130f2afc78c959 path: ./media_kit media_kit_video: git: url: https://github.com/Predidit/media-kit.git ref: 7b248251fc274f986635876946130f2afc78c959 path: ./media_kit_video media_kit_libs_video: git: url: https://github.com/Predidit/media-kit.git ref: 7b248251fc274f986635876946130f2afc78c959 path: ./libs/universal/media_kit_libs_video dependency_overrides: media_kit_libs_linux: git: url: https://github.com/Predidit/media-kit.git ref: 7b248251fc274f986635876946130f2afc78c959 path: ./libs/linux/media_kit_libs_linux media_kit_libs_ios_video: git: url: https://github.com/Predidit/media-kit.git ref: 7b248251fc274f986635876946130f2afc78c959 path: ./libs/ios/media_kit_libs_ios_video media_kit_libs_android_video: git: url: https://github.com/Predidit/media-kit.git ref: 7b248251fc274f986635876946130f2afc78c959 path: ./libs/android/media_kit_libs_android_video media_kit_libs_windows_video: git: url: https://github.com/Predidit/media-kit.git ref: 7b248251fc274f986635876946130f2afc78c959 path: ./libs/windows/media_kit_libs_windows_video media_kit_libs_macos_video: git: url: https://github.com/Predidit/media-kit.git ref: 7b248251fc274f986635876946130f2afc78c959 path: ./libs/macos/media_kit_libs_macos_video dev_dependencies: flutter_lints: ^6.0.0 build_runner: ^2.3.3 hive_generator: ^2.0.0 flutter_launcher_icons: ^0.14.4 flutter_test: sdk: flutter integration_test: sdk: flutter flutter_launcher_icons: android: "ic_launcher" ios: true image_path: "assets/logo.png" min_sdk_android: 21 windows: generate: true image_path: "assets/logo.png" icon_size: 256 macos: generate: true image_path: "assets/logo.png" flutter: fonts: - family: iconfont fonts: - asset: assets/iconfont/live_icons.ttf uses-material-design: true assets: - assets/statement.txt - assets/images/ - assets/icons/ - assets/lotties/ - assets/fonts/fonts-manifest.json - shorebird.yaml ================================================ FILE: simple_live_app/rust/.gitignore ================================================ /target ================================================ FILE: simple_live_app/rust/Cargo.toml ================================================ [package] name = "rust_lib_simple_live_app" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib", "staticlib"] [dependencies] flutter_rust_bridge = "=2.11.1" regex = "1.12.2" xxhash-rust = { version = "0.8.5", features = ["xxh3"] } lazy_static = "1.4.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(frb_expand)'] } ================================================ FILE: simple_live_app/rust/src/api/danmaku_mask.rs ================================================ use regex::Regex; use std::collections::{HashMap, HashSet}; use xxhash_rust::xxh3::xxh3_64; /// DanmakuMask: 滑动窗口 + 分桶 + 频控 #[flutter_rust_bridge::frb(opaque)] pub struct DanmakuMask { base_window_ms: u32, // 基础窗口(ms) bucket_count: u16, // 桶数量 max_frequency: u16, // 最大允许频次 use_normalization: bool, use_frequency_control: bool, adaptive_window: bool, // 运行时状态 window_ms: u32, bucket_size_ms: u32, current_bucket: usize, // Vec 索引 last_shift_ms: u64, // 上次滑动的时间戳(ms) buckets: Vec>, // 每个桶存 hash freq_map: HashMap, // hash -> 频次 norm_re_space: Option, norm_re_punct: Option, } #[flutter_rust_bridge::frb(sync)] impl DanmakuMask { /// 构造函数(会生成 Dart 构造器) pub fn new( base_window_ms: u32, bucket_count: u16, use_normalization: bool, use_frequency_control: bool, max_frequency: u16, adaptive_window: bool, ) -> Self { let bucket_count_usize = bucket_count.max(1) as usize; let bucket_size_ms = base_window_ms / bucket_count.max(1) as u32; let norm_re_space = use_normalization .then(|| Regex::new(r"\s+").unwrap()); let norm_re_punct = use_normalization .then(|| Regex::new(r"[~!!??,.,。]").unwrap()); Self { base_window_ms, bucket_count, max_frequency, use_normalization, use_frequency_control, adaptive_window, window_ms: base_window_ms, bucket_size_ms, current_bucket: 0, last_shift_ms: 0, buckets: (0..bucket_count_usize) .map(|_| HashSet::with_capacity(128)) .collect(), freq_map: HashMap::with_capacity(1024), norm_re_space, norm_re_punct, } } /// 文本归一化 fn normalize(&self, text: &str) -> String { if !self.use_normalization { return text.to_owned(); } let mut s = text.trim().to_lowercase(); if let Some(re) = &self.norm_re_space { s = re.replace_all(&s, "").to_string(); } if let Some(re) = &self.norm_re_punct { s = re.replace_all(&s, "").to_string(); } s } fn shift_if_needed(&mut self, now_ms: u64) { if self.last_shift_ms == 0 { self.last_shift_ms = now_ms; return; } while now_ms.saturating_sub(self.last_shift_ms) >= self.bucket_size_ms as u64 { self.last_shift_ms += self.bucket_size_ms as u64; self.current_bucket = (self.current_bucket + 1) % self.bucket_count as usize; let expired = &mut self.buckets[self.current_bucket]; for &hash in expired.iter() { if let Some(v) = self.freq_map.get_mut(&hash) { if *v <= 1 { self.freq_map.remove(&hash); } else { *v -= 1; } } } expired.clear(); } } /// 根据压力自适应窗口 fn adapt_window(&mut self) { if !self.adaptive_window { return; } let total_items: usize = self.buckets.iter().map(|b| b.len()).sum(); if total_items > 300 { self.window_ms = (self.base_window_ms / 2).max(1500); } else if total_items < 50 { self.window_ms = self.base_window_ms; } self.bucket_size_ms = self.window_ms / self.bucket_count.max(1) as u32; } /// 重置状态 pub fn reset(&mut self) { for bucket in &mut self.buckets { bucket.clear(); } self.freq_map.clear(); self.current_bucket = 0; self.last_shift_ms = 0; self.window_ms = self.base_window_ms; self.bucket_size_ms = self.window_ms / self.bucket_count.max(1) as u32; } pub fn dispose(self){ // 消费所有权 } } #[flutter_rust_bridge::frb] impl DanmakuMask { /// 批量判断是否允许 /// 返回 Vec:1 = 允许,0 = 屏蔽 pub fn allow_list_batch( &mut self, texts: Vec, now_ms: u64, ) -> Vec { self.shift_if_needed(now_ms); self.adapt_window(); let mut results = Vec::with_capacity(texts.len()); for text in texts { let normalized = self.normalize(&text); let hash = xxh3_64(normalized.as_bytes()); let mut allowed = true; if self.freq_map.contains_key(&hash) { allowed = false; } if allowed && self.use_frequency_control { let freq = *self.freq_map.get(&hash).unwrap_or(&0u16); if freq >= self.max_frequency { allowed = false; } } if allowed { self.buckets[self.current_bucket].insert(hash); *self.freq_map.entry(hash).or_insert(0) += 1; } results.push(if allowed { 1 } else { 0 }); } results } } ================================================ FILE: simple_live_app/rust/src/api/mod.rs ================================================ pub mod simple; pub mod danmaku_mask; ================================================ FILE: simple_live_app/rust/src/api/simple.rs ================================================ #[flutter_rust_bridge::frb(init)] pub fn init_app() { // Default utilities - feel free to customize flutter_rust_bridge::setup_default_user_utils(); } ================================================ FILE: simple_live_app/rust/src/frb_generated.rs ================================================ // This file is automatically generated, so please do not edit it. // @generated by `flutter_rust_bridge`@ 2.11.1. #![allow( non_camel_case_types, unused, non_snake_case, clippy::needless_return, clippy::redundant_closure_call, clippy::redundant_closure, clippy::useless_conversion, clippy::unit_arg, clippy::unused_unit, clippy::double_parens, clippy::let_and_return, clippy::too_many_arguments, clippy::match_single_binding, clippy::clone_on_copy, clippy::let_unit_value, clippy::deref_addrof, clippy::explicit_auto_deref, clippy::borrow_deref_ref, clippy::needless_borrow )] // Section: imports use crate::api::danmaku_mask::*; use flutter_rust_bridge::for_generated::byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; use flutter_rust_bridge::{Handler, IntoIntoDart}; // Section: boilerplate flutter_rust_bridge::frb_generated_boilerplate!( default_stream_sink_codec = SseCodec, default_rust_opaque = RustOpaqueMoi, default_rust_auto_opaque = RustAutoOpaqueMoi, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1"; pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1540820557; // Section: executor flutter_rust_bridge::frb_generated_default_handler!(); // Section: wire_funcs fn wire__crate__api__danmaku_mask__DanmakuMask_allow_list_batch_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, data_len_: i32, ) { FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( flutter_rust_bridge::for_generated::TaskInfo { debug_name: "DanmakuMask_allow_list_batch", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, }, move || { let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( ptr_, rust_vec_len_, data_len_, ) }; let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); let api_that = , >>::sse_decode(&mut deserializer); let api_texts = >::sse_decode(&mut deserializer); let api_now_ms = ::sse_decode(&mut deserializer); deserializer.end(); move |context| { transform_result_sse::<_, ()>((move || { let mut api_that_guard = None; let decode_indices_ = flutter_rust_bridge::for_generated::lockable_compute_decode_order(vec![ flutter_rust_bridge::for_generated::LockableOrderInfo::new( &api_that, 0, true, ), ]); for i in decode_indices_ { match i { 0 => api_that_guard = Some(api_that.lockable_decode_sync_ref_mut()), _ => unreachable!(), } } let mut api_that_guard = api_that_guard.unwrap(); let output_ok = Result::<_, ()>::Ok( crate::api::danmaku_mask::DanmakuMask::allow_list_batch( &mut *api_that_guard, api_texts, api_now_ms, ), )?; Ok(output_ok) })()) } }, ) } fn wire__crate__api__danmaku_mask__DanmakuMask_dispose_impl( ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, data_len_: i32, ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( flutter_rust_bridge::for_generated::TaskInfo { debug_name: "DanmakuMask_dispose", port: None, mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, }, move || { let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( ptr_, rust_vec_len_, data_len_, ) }; let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); let api_that = ::sse_decode(&mut deserializer); deserializer.end(); transform_result_sse::<_, ()>((move || { let output_ok = Result::<_, ()>::Ok({ crate::api::danmaku_mask::DanmakuMask::dispose(api_that); })?; Ok(output_ok) })()) }, ) } fn wire__crate__api__danmaku_mask__DanmakuMask_new_impl( ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, data_len_: i32, ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( flutter_rust_bridge::for_generated::TaskInfo { debug_name: "DanmakuMask_new", port: None, mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, }, move || { let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( ptr_, rust_vec_len_, data_len_, ) }; let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); let api_base_window_ms = ::sse_decode(&mut deserializer); let api_bucket_count = ::sse_decode(&mut deserializer); let api_use_normalization = ::sse_decode(&mut deserializer); let api_use_frequency_control = ::sse_decode(&mut deserializer); let api_max_frequency = ::sse_decode(&mut deserializer); let api_adaptive_window = ::sse_decode(&mut deserializer); deserializer.end(); transform_result_sse::<_, ()>((move || { let output_ok = Result::<_, ()>::Ok(crate::api::danmaku_mask::DanmakuMask::new( api_base_window_ms, api_bucket_count, api_use_normalization, api_use_frequency_control, api_max_frequency, api_adaptive_window, ))?; Ok(output_ok) })()) }, ) } fn wire__crate__api__danmaku_mask__DanmakuMask_reset_impl( ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, data_len_: i32, ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( flutter_rust_bridge::for_generated::TaskInfo { debug_name: "DanmakuMask_reset", port: None, mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, }, move || { let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( ptr_, rust_vec_len_, data_len_, ) }; let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); let api_that = , >>::sse_decode(&mut deserializer); deserializer.end(); transform_result_sse::<_, ()>((move || { let mut api_that_guard = None; let decode_indices_ = flutter_rust_bridge::for_generated::lockable_compute_decode_order(vec![ flutter_rust_bridge::for_generated::LockableOrderInfo::new( &api_that, 0, true, ), ]); for i in decode_indices_ { match i { 0 => api_that_guard = Some(api_that.lockable_decode_sync_ref_mut()), _ => unreachable!(), } } let mut api_that_guard = api_that_guard.unwrap(); let output_ok = Result::<_, ()>::Ok({ crate::api::danmaku_mask::DanmakuMask::reset(&mut *api_that_guard); })?; Ok(output_ok) })()) }, ) } fn wire__crate__api__simple__init_app_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, data_len_: i32, ) { FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( flutter_rust_bridge::for_generated::TaskInfo { debug_name: "init_app", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, }, move || { let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( ptr_, rust_vec_len_, data_len_, ) }; let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); deserializer.end(); move |context| { transform_result_sse::<_, ()>((move || { let output_ok = Result::<_, ()>::Ok({ crate::api::simple::init_app(); })?; Ok(output_ok) })()) } }, ) } // Section: related_funcs flutter_rust_bridge::frb_generated_moi_arc_impl_value!( flutter_rust_bridge::for_generated::RustAutoOpaqueInner ); // Section: dart2rust impl SseDecode for DanmakuMask { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut inner = , >>::sse_decode(deserializer); return flutter_rust_bridge::for_generated::rust_auto_opaque_decode_owned(inner); } } impl SseDecode for RustOpaqueMoi> { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut inner = ::sse_decode(deserializer); return decode_rust_opaque_moi(inner); } } impl SseDecode for String { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut inner = >::sse_decode(deserializer); return String::from_utf8(inner).unwrap(); } } impl SseDecode for bool { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { deserializer.cursor.read_u8().unwrap() != 0 } } impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); let mut ans_ = vec![]; for idx_ in 0..len_ { ans_.push(::sse_decode(deserializer)); } return ans_; } } impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); let mut ans_ = vec![]; for idx_ in 0..len_ { ans_.push(::sse_decode(deserializer)); } return ans_; } } impl SseDecode for u16 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { deserializer.cursor.read_u16::().unwrap() } } impl SseDecode for u32 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { deserializer.cursor.read_u32::().unwrap() } } impl SseDecode for u64 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { deserializer.cursor.read_u64::().unwrap() } } impl SseDecode for u8 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { deserializer.cursor.read_u8().unwrap() } } impl SseDecode for () { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {} } impl SseDecode for usize { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { deserializer.cursor.read_u64::().unwrap() as _ } } impl SseDecode for i32 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { deserializer.cursor.read_i32::().unwrap() } } fn pde_ffi_dispatcher_primary_impl( func_id: i32, port: flutter_rust_bridge::for_generated::MessagePort, ptr: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len: i32, data_len: i32, ) { // Codec=Pde (Serialization + dispatch), see doc to use other codecs match func_id { 1 => wire__crate__api__danmaku_mask__DanmakuMask_allow_list_batch_impl( port, ptr, rust_vec_len, data_len, ), 5 => wire__crate__api__simple__init_app_impl(port, ptr, rust_vec_len, data_len), _ => unreachable!(), } } fn pde_ffi_dispatcher_sync_impl( func_id: i32, ptr: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len: i32, data_len: i32, ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { // Codec=Pde (Serialization + dispatch), see doc to use other codecs match func_id { 2 => wire__crate__api__danmaku_mask__DanmakuMask_dispose_impl(ptr, rust_vec_len, data_len), 3 => wire__crate__api__danmaku_mask__DanmakuMask_new_impl(ptr, rust_vec_len, data_len), 4 => wire__crate__api__danmaku_mask__DanmakuMask_reset_impl(ptr, rust_vec_len, data_len), _ => unreachable!(), } } // Section: rust2dart // Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for FrbWrapper { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self.0) .into_dart() } } impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for FrbWrapper {} impl flutter_rust_bridge::IntoIntoDart> for DanmakuMask { fn into_into_dart(self) -> FrbWrapper { self.into() } } impl SseEncode for DanmakuMask { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { >>::sse_encode(flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self), serializer); } } impl SseEncode for RustOpaqueMoi> { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { let (ptr, size) = self.sse_encode_raw(); ::sse_encode(ptr, serializer); ::sse_encode(size, serializer); } } impl SseEncode for String { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { >::sse_encode(self.into_bytes(), serializer); } } impl SseEncode for bool { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { serializer.cursor.write_u8(self as _).unwrap(); } } impl SseEncode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { ::sse_encode(self.len() as _, serializer); for item in self { ::sse_encode(item, serializer); } } } impl SseEncode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { ::sse_encode(self.len() as _, serializer); for item in self { ::sse_encode(item, serializer); } } } impl SseEncode for u16 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { serializer.cursor.write_u16::(self).unwrap(); } } impl SseEncode for u32 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { serializer.cursor.write_u32::(self).unwrap(); } } impl SseEncode for u64 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { serializer.cursor.write_u64::(self).unwrap(); } } impl SseEncode for u8 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { serializer.cursor.write_u8(self).unwrap(); } } impl SseEncode for () { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {} } impl SseEncode for usize { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { serializer .cursor .write_u64::(self as _) .unwrap(); } } impl SseEncode for i32 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { serializer.cursor.write_i32::(self).unwrap(); } } #[cfg(not(target_family = "wasm"))] mod io { // This file is automatically generated, so please do not edit it. // @generated by `flutter_rust_bridge`@ 2.11.1. // Section: imports use super::*; use crate::api::danmaku_mask::*; use flutter_rust_bridge::for_generated::byteorder::{ NativeEndian, ReadBytesExt, WriteBytesExt, }; use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; use flutter_rust_bridge::{Handler, IntoIntoDart}; // Section: boilerplate flutter_rust_bridge::frb_generated_boilerplate_io!(); #[unsafe(no_mangle)] pub extern "C" fn frbgen_simple_live_app_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( ptr: *const std::ffi::c_void, ) { MoiArc::>::increment_strong_count(ptr as _); } #[unsafe(no_mangle)] pub extern "C" fn frbgen_simple_live_app_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( ptr: *const std::ffi::c_void, ) { MoiArc::>::decrement_strong_count(ptr as _); } } #[cfg(not(target_family = "wasm"))] pub use io::*; /// cbindgen:ignore #[cfg(target_family = "wasm")] mod web { // This file is automatically generated, so please do not edit it. // @generated by `flutter_rust_bridge`@ 2.11.1. // Section: imports use super::*; use crate::api::danmaku_mask::*; use flutter_rust_bridge::for_generated::byteorder::{ NativeEndian, ReadBytesExt, WriteBytesExt, }; use flutter_rust_bridge::for_generated::wasm_bindgen; use flutter_rust_bridge::for_generated::wasm_bindgen::prelude::*; use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; use flutter_rust_bridge::{Handler, IntoIntoDart}; // Section: boilerplate flutter_rust_bridge::frb_generated_boilerplate_web!(); #[wasm_bindgen] pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( ptr: *const std::ffi::c_void, ) { MoiArc::>::increment_strong_count(ptr as _); } #[wasm_bindgen] pub fn rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerDanmakuMask( ptr: *const std::ffi::c_void, ) { MoiArc::>::decrement_strong_count(ptr as _); } } #[cfg(target_family = "wasm")] pub use web::*; ================================================ FILE: simple_live_app/rust/src/lib.rs ================================================ pub mod api; mod frb_generated; ================================================ FILE: simple_live_app/rust_builder/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ build/ ================================================ FILE: simple_live_app/rust_builder/README.md ================================================ Please ignore this folder, which is just glue to build Rust with Flutter. ================================================ FILE: simple_live_app/rust_builder/android/.gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures .cxx ================================================ FILE: simple_live_app/rust_builder/android/build.gradle ================================================ // The Android Gradle Plugin builds the native code with the Android NDK. group 'com.flutter_rust_bridge.rust_lib_simple_live_app' version '1.0' buildscript { repositories { google() mavenCentral() } dependencies { // The Android Gradle Plugin knows how to build native code with the NDK. classpath 'com.android.tools.build:gradle:7.3.0' } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' android { if (project.android.hasProperty("namespace")) { namespace 'com.flutter_rust_bridge.rust_lib_simple_live_app' } // Bumping the plugin compileSdkVersion requires all clients of this plugin // to bump the version in their app. compileSdkVersion 33 // Use the NDK version // declared in /android/app/build.gradle file of the Flutter project. // Replace it with a version number if this plugin requires a specfic NDK version. // (e.g. ndkVersion "23.1.7779620") ndkVersion android.ndkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { minSdkVersion 19 } } apply from: "../cargokit/gradle/plugin.gradle" cargokit { manifestDir = "../../rust" libname = "rust_lib_simple_live_app" } ================================================ FILE: simple_live_app/rust_builder/android/settings.gradle ================================================ rootProject.name = 'rust_lib_simple_live_app' ================================================ FILE: simple_live_app/rust_builder/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: simple_live_app/rust_builder/cargokit/.gitignore ================================================ target .dart_tool *.iml !pubspec.lock ================================================ FILE: simple_live_app/rust_builder/cargokit/LICENSE ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin Copyright 2022 Matej Knopp ================================================================================ MIT LICENSE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ APACHE LICENSE, VERSION 2.0 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: simple_live_app/rust_builder/cargokit/README ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin Experimental repository to provide glue for seamlessly integrating cargo build with flutter plugins and packages. See https://matejknopp.com/post/flutter_plugin_in_rust_with_no_prebuilt_binaries/ for a tutorial on how to use Cargokit. Example plugin available at https://github.com/irondash/hello_rust_ffi_plugin. ================================================ FILE: simple_live_app/rust_builder/cargokit/build_pod.sh ================================================ #!/bin/sh set -e BASEDIR=$(dirname "$0") # Workaround for https://github.com/dart-lang/pub/issues/4010 BASEDIR=$(cd "$BASEDIR" ; pwd -P) # Remove XCode SDK from path. Otherwise this breaks tool compilation when building iOS project NEW_PATH=`echo $PATH | tr ":" "\n" | grep -v "Contents/Developer/" | tr "\n" ":"` export PATH=${NEW_PATH%?} # remove trailing : env # Platform name (macosx, iphoneos, iphonesimulator) export CARGOKIT_DARWIN_PLATFORM_NAME=$PLATFORM_NAME # Arctive architectures (arm64, armv7, x86_64), space separated. export CARGOKIT_DARWIN_ARCHS=$ARCHS # Current build configuration (Debug, Release) export CARGOKIT_CONFIGURATION=$CONFIGURATION # Path to directory containing Cargo.toml. export CARGOKIT_MANIFEST_DIR=$PODS_TARGET_SRCROOT/$1 # Temporary directory for build artifacts. export CARGOKIT_TARGET_TEMP_DIR=$TARGET_TEMP_DIR # Output directory for final artifacts. export CARGOKIT_OUTPUT_DIR=$PODS_CONFIGURATION_BUILD_DIR/$PRODUCT_NAME # Directory to store built tool artifacts. export CARGOKIT_TOOL_TEMP_DIR=$TARGET_TEMP_DIR/build_tool # Directory inside root project. Not necessarily the top level directory of root project. export CARGOKIT_ROOT_PROJECT_DIR=$SRCROOT FLUTTER_EXPORT_BUILD_ENVIRONMENT=( "$PODS_ROOT/../Flutter/ephemeral/flutter_export_environment.sh" # macOS "$PODS_ROOT/../Flutter/flutter_export_environment.sh" # iOS ) for path in "${FLUTTER_EXPORT_BUILD_ENVIRONMENT[@]}" do if [[ -f "$path" ]]; then source "$path" fi done sh "$BASEDIR/run_build_tool.sh" build-pod "$@" # Make a symlink from built framework to phony file, which will be used as input to # build script. This should force rebuild (podspec currently doesn't support alwaysOutOfDate # attribute on custom build phase) ln -fs "$OBJROOT/XCBuildData/build.db" "${BUILT_PRODUCTS_DIR}/cargokit_phony" ln -fs "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" "${BUILT_PRODUCTS_DIR}/cargokit_phony_out" ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/README.md ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin A sample command-line application with an entrypoint in `bin/`, library code in `lib/`, and example unit test in `test/`. ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/analysis_options.yaml ================================================ # This is copied from Cargokit (which is the official way to use it currently) # Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin # This file configures the static analysis results for your project (errors, # warnings, and lints). # # This enables the 'recommended' set of lints from `package:lints`. # This set helps identify many issues that may lead to problems when running # or consuming Dart code, and enforces writing Dart using a single, idiomatic # style and format. # # If you want a smaller set of lints you can change this to specify # 'package:lints/core.yaml'. These are just the most critical lints # (the recommended set includes the core lints). # The core lints are also what is used by pub.dev for scoring packages. include: package:lints/recommended.yaml # Uncomment the following section to specify additional rules. linter: rules: - prefer_relative_imports - directives_ordering # analyzer: # exclude: # - path/to/excluded/files/** # For more information about the core and recommended set of lints, see # https://dart.dev/go/core-lints # For additional information about configuring this file, see # https://dart.dev/guides/language/analysis-options ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/bin/build_tool.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'package:build_tool/build_tool.dart' as build_tool; void main(List arguments) { build_tool.runMain(arguments); } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/build_tool.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'src/build_tool.dart' as build_tool; Future runMain(List args) async { return build_tool.runMain(args); } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/android_environment.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:io'; import 'dart:isolate'; import 'dart:math' as math; import 'package:collection/collection.dart'; import 'package:path/path.dart' as path; import 'package:version/version.dart'; import 'target.dart'; import 'util.dart'; class AndroidEnvironment { AndroidEnvironment({ required this.sdkPath, required this.ndkVersion, required this.minSdkVersion, required this.targetTempDir, required this.target, }); static void clangLinkerWrapper(List args) { final clang = Platform.environment['_CARGOKIT_NDK_LINK_CLANG']; if (clang == null) { throw Exception( "cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_CLANG env var"); } final target = Platform.environment['_CARGOKIT_NDK_LINK_TARGET']; if (target == null) { throw Exception( "cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_TARGET env var"); } runCommand(clang, [ target, ...args, ]); } /// Full path to Android SDK. final String sdkPath; /// Full version of Android NDK. final String ndkVersion; /// Minimum supported SDK version. final int minSdkVersion; /// Target directory for build artifacts. final String targetTempDir; /// Target being built. final Target target; bool ndkIsInstalled() { final ndkPath = path.join(sdkPath, 'ndk', ndkVersion); final ndkPackageXml = File(path.join(ndkPath, 'package.xml')); return ndkPackageXml.existsSync(); } void installNdk({ required String javaHome, }) { final sdkManagerExtension = Platform.isWindows ? '.bat' : ''; final sdkManager = path.join( sdkPath, 'cmdline-tools', 'latest', 'bin', 'sdkmanager$sdkManagerExtension', ); log.info('Installing NDK $ndkVersion'); runCommand(sdkManager, [ '--install', 'ndk;$ndkVersion', ], environment: { 'JAVA_HOME': javaHome, }); } Future> buildEnvironment() async { final hostArch = Platform.isMacOS ? "darwin-x86_64" : (Platform.isLinux ? "linux-x86_64" : "windows-x86_64"); final ndkPath = path.join(sdkPath, 'ndk', ndkVersion); final toolchainPath = path.join( ndkPath, 'toolchains', 'llvm', 'prebuilt', hostArch, 'bin', ); final minSdkVersion = math.max(target.androidMinSdkVersion!, this.minSdkVersion); final exe = Platform.isWindows ? '.exe' : ''; final arKey = 'AR_${target.rust}'; final arValue = ['${target.rust}-ar', 'llvm-ar', 'llvm-ar.exe'] .map((e) => path.join(toolchainPath, e)) .firstWhereOrNull((element) => File(element).existsSync()); if (arValue == null) { throw Exception('Failed to find ar for $target in $toolchainPath'); } final targetArg = '--target=${target.rust}$minSdkVersion'; final ccKey = 'CC_${target.rust}'; final ccValue = path.join(toolchainPath, 'clang$exe'); final cfFlagsKey = 'CFLAGS_${target.rust}'; final cFlagsValue = targetArg; final cxxKey = 'CXX_${target.rust}'; final cxxValue = path.join(toolchainPath, 'clang++$exe'); final cxxFlagsKey = 'CXXFLAGS_${target.rust}'; final cxxFlagsValue = targetArg; final linkerKey = 'cargo_target_${target.rust.replaceAll('-', '_')}_linker'.toUpperCase(); final ranlibKey = 'RANLIB_${target.rust}'; final ranlibValue = path.join(toolchainPath, 'llvm-ranlib$exe'); final ndkVersionParsed = Version.parse(ndkVersion); final rustFlagsKey = 'CARGO_ENCODED_RUSTFLAGS'; final rustFlagsValue = _libGccWorkaround(targetTempDir, ndkVersionParsed); final runRustTool = Platform.isWindows ? 'run_build_tool.cmd' : 'run_build_tool.sh'; final packagePath = (await Isolate.resolvePackageUri( Uri.parse('package:build_tool/buildtool.dart')))! .toFilePath(); final selfPath = path.canonicalize(path.join( packagePath, '..', '..', '..', runRustTool, )); // Make sure that run_build_tool is working properly even initially launched directly // through dart run. final toolTempDir = Platform.environment['CARGOKIT_TOOL_TEMP_DIR'] ?? targetTempDir; return { arKey: arValue, ccKey: ccValue, cfFlagsKey: cFlagsValue, cxxKey: cxxValue, cxxFlagsKey: cxxFlagsValue, ranlibKey: ranlibValue, rustFlagsKey: rustFlagsValue, linkerKey: selfPath, // Recognized by main() so we know when we're acting as a wrapper '_CARGOKIT_NDK_LINK_TARGET': targetArg, '_CARGOKIT_NDK_LINK_CLANG': ccValue, 'CARGOKIT_TOOL_TEMP_DIR': toolTempDir, }; } // Workaround for libgcc missing in NDK23, inspired by cargo-ndk String _libGccWorkaround(String buildDir, Version ndkVersion) { final workaroundDir = path.join( buildDir, 'cargokit', 'libgcc_workaround', '${ndkVersion.major}', ); Directory(workaroundDir).createSync(recursive: true); if (ndkVersion.major >= 23) { File(path.join(workaroundDir, 'libgcc.a')) .writeAsStringSync('INPUT(-lunwind)'); } else { // Other way around, untested, forward libgcc.a from libunwind once Rust // gets updated for NDK23+. File(path.join(workaroundDir, 'libunwind.a')) .writeAsStringSync('INPUT(-lgcc)'); } var rustFlags = Platform.environment['CARGO_ENCODED_RUSTFLAGS'] ?? ''; if (rustFlags.isNotEmpty) { rustFlags = '$rustFlags\x1f'; } rustFlags = '$rustFlags-L\x1f$workaroundDir'; return rustFlags; } } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/artifacts_provider.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:io'; import 'package:ed25519_edwards/ed25519_edwards.dart'; import 'package:http/http.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; import 'builder.dart'; import 'crate_hash.dart'; import 'options.dart'; import 'precompile_binaries.dart'; import 'rustup.dart'; import 'target.dart'; class Artifact { /// File system location of the artifact. final String path; /// Actual file name that the artifact should have in destination folder. final String finalFileName; AritifactType get type { if (finalFileName.endsWith('.dll') || finalFileName.endsWith('.dll.lib') || finalFileName.endsWith('.pdb') || finalFileName.endsWith('.so') || finalFileName.endsWith('.dylib')) { return AritifactType.dylib; } else if (finalFileName.endsWith('.lib') || finalFileName.endsWith('.a')) { return AritifactType.staticlib; } else { throw Exception('Unknown artifact type for $finalFileName'); } } Artifact({ required this.path, required this.finalFileName, }); } final _log = Logger('artifacts_provider'); class ArtifactProvider { ArtifactProvider({ required this.environment, required this.userOptions, }); final BuildEnvironment environment; final CargokitUserOptions userOptions; Future>> getArtifacts(List targets) async { final result = await _getPrecompiledArtifacts(targets); final pendingTargets = List.of(targets); pendingTargets.removeWhere((element) => result.containsKey(element)); if (pendingTargets.isEmpty) { return result; } final rustup = Rustup(); for (final target in targets) { final builder = RustBuilder(target: target, environment: environment); builder.prepare(rustup); _log.info('Building ${environment.crateInfo.packageName} for $target'); final targetDir = await builder.build(); // For local build accept both static and dynamic libraries. final artifactNames = { ...getArtifactNames( target: target, libraryName: environment.crateInfo.packageName, aritifactType: AritifactType.dylib, remote: false, ), ...getArtifactNames( target: target, libraryName: environment.crateInfo.packageName, aritifactType: AritifactType.staticlib, remote: false, ) }; final artifacts = artifactNames .map((artifactName) => Artifact( path: path.join(targetDir, artifactName), finalFileName: artifactName, )) .where((element) => File(element.path).existsSync()) .toList(); result[target] = artifacts; } return result; } Future>> _getPrecompiledArtifacts( List targets) async { if (userOptions.usePrecompiledBinaries == false) { _log.info('Precompiled binaries are disabled'); return {}; } if (environment.crateOptions.precompiledBinaries == null) { _log.fine('Precompiled binaries not enabled for this crate'); return {}; } final start = Stopwatch()..start(); final crateHash = CrateHash.compute(environment.manifestDir, tempStorage: environment.targetTempDir); _log.fine( 'Computed crate hash $crateHash in ${start.elapsedMilliseconds}ms'); final downloadedArtifactsDir = path.join(environment.targetTempDir, 'precompiled', crateHash); Directory(downloadedArtifactsDir).createSync(recursive: true); final res = >{}; for (final target in targets) { final requiredArtifacts = getArtifactNames( target: target, libraryName: environment.crateInfo.packageName, remote: true, ); final artifactsForTarget = []; for (final artifact in requiredArtifacts) { final fileName = PrecompileBinaries.fileName(target, artifact); final downloadedPath = path.join(downloadedArtifactsDir, fileName); if (!File(downloadedPath).existsSync()) { final signatureFileName = PrecompileBinaries.signatureFileName(target, artifact); await _tryDownloadArtifacts( crateHash: crateHash, fileName: fileName, signatureFileName: signatureFileName, finalPath: downloadedPath, ); } if (File(downloadedPath).existsSync()) { artifactsForTarget.add(Artifact( path: downloadedPath, finalFileName: artifact, )); } else { break; } } // Only provide complete set of artifacts. if (artifactsForTarget.length == requiredArtifacts.length) { _log.fine('Found precompiled artifacts for $target'); res[target] = artifactsForTarget; } } return res; } static Future _get(Uri url, {Map? headers}) async { int attempt = 0; const maxAttempts = 10; while (true) { try { return await get(url, headers: headers); } on SocketException catch (e) { // Try to detect reset by peer error and retry. if (attempt++ < maxAttempts && (e.osError?.errorCode == 54 || e.osError?.errorCode == 10054)) { _log.severe( 'Failed to download $url: $e, attempt $attempt of $maxAttempts, will retry...'); await Future.delayed(Duration(seconds: 1)); continue; } else { rethrow; } } } } Future _tryDownloadArtifacts({ required String crateHash, required String fileName, required String signatureFileName, required String finalPath, }) async { final precompiledBinaries = environment.crateOptions.precompiledBinaries!; final prefix = precompiledBinaries.uriPrefix; final url = Uri.parse('$prefix$crateHash/$fileName'); final signatureUrl = Uri.parse('$prefix$crateHash/$signatureFileName'); _log.fine('Downloading signature from $signatureUrl'); final signature = await _get(signatureUrl); if (signature.statusCode == 404) { _log.warning( 'Precompiled binaries not available for crate hash $crateHash ($fileName)'); return; } if (signature.statusCode != 200) { _log.severe( 'Failed to download signature $signatureUrl: status ${signature.statusCode}'); return; } _log.fine('Downloading binary from $url'); final res = await _get(url); if (res.statusCode != 200) { _log.severe('Failed to download binary $url: status ${res.statusCode}'); return; } if (verify( precompiledBinaries.publicKey, res.bodyBytes, signature.bodyBytes)) { File(finalPath).writeAsBytesSync(res.bodyBytes); } else { _log.shout('Signature verification failed! Ignoring binary.'); } } } enum AritifactType { staticlib, dylib, } AritifactType artifactTypeForTarget(Target target) { if (target.darwinPlatform != null) { return AritifactType.staticlib; } else { return AritifactType.dylib; } } List getArtifactNames({ required Target target, required String libraryName, required bool remote, AritifactType? aritifactType, }) { aritifactType ??= artifactTypeForTarget(target); if (target.darwinArch != null) { if (aritifactType == AritifactType.staticlib) { return ['lib$libraryName.a']; } else { return ['lib$libraryName.dylib']; } } else if (target.rust.contains('-windows-')) { if (aritifactType == AritifactType.staticlib) { return ['$libraryName.lib']; } else { return [ '$libraryName.dll', '$libraryName.dll.lib', if (!remote) '$libraryName.pdb' ]; } } else if (target.rust.contains('-linux-')) { if (aritifactType == AritifactType.staticlib) { return ['lib$libraryName.a']; } else { return ['lib$libraryName.so']; } } else { throw Exception("Unsupported target: ${target.rust}"); } } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/build_cmake.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:io'; import 'package:path/path.dart' as path; import 'artifacts_provider.dart'; import 'builder.dart'; import 'environment.dart'; import 'options.dart'; import 'target.dart'; class BuildCMake { final CargokitUserOptions userOptions; BuildCMake({required this.userOptions}); Future build() async { final targetPlatform = Environment.targetPlatform; final target = Target.forFlutterName(Environment.targetPlatform); if (target == null) { throw Exception("Unknown target platform: $targetPlatform"); } final environment = BuildEnvironment.fromEnvironment(isAndroid: false); final provider = ArtifactProvider(environment: environment, userOptions: userOptions); final artifacts = await provider.getArtifacts([target]); final libs = artifacts[target]!; for (final lib in libs) { if (lib.type == AritifactType.dylib) { File(lib.path) .copySync(path.join(Environment.outputDir, lib.finalFileName)); } } } } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/build_gradle.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:io'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; import 'artifacts_provider.dart'; import 'builder.dart'; import 'environment.dart'; import 'options.dart'; import 'target.dart'; final log = Logger('build_gradle'); class BuildGradle { BuildGradle({required this.userOptions}); final CargokitUserOptions userOptions; Future build() async { final targets = Environment.targetPlatforms.map((arch) { final target = Target.forFlutterName(arch); if (target == null) { throw Exception( "Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}"); } return target; }).toList(); final environment = BuildEnvironment.fromEnvironment(isAndroid: true); final provider = ArtifactProvider(environment: environment, userOptions: userOptions); final artifacts = await provider.getArtifacts(targets); for (final target in targets) { final libs = artifacts[target]!; final outputDir = path.join(Environment.outputDir, target.android!); Directory(outputDir).createSync(recursive: true); for (final lib in libs) { if (lib.type == AritifactType.dylib) { File(lib.path).copySync(path.join(outputDir, lib.finalFileName)); } } } } } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/build_pod.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:io'; import 'package:path/path.dart' as path; import 'artifacts_provider.dart'; import 'builder.dart'; import 'environment.dart'; import 'options.dart'; import 'target.dart'; import 'util.dart'; class BuildPod { BuildPod({required this.userOptions}); final CargokitUserOptions userOptions; Future build() async { final targets = Environment.darwinArchs.map((arch) { final target = Target.forDarwin( platformName: Environment.darwinPlatformName, darwinAarch: arch); if (target == null) { throw Exception( "Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}"); } return target; }).toList(); final environment = BuildEnvironment.fromEnvironment(isAndroid: false); final provider = ArtifactProvider(environment: environment, userOptions: userOptions); final artifacts = await provider.getArtifacts(targets); void performLipo(String targetFile, Iterable sourceFiles) { runCommand("lipo", [ '-create', ...sourceFiles, '-output', targetFile, ]); } final outputDir = Environment.outputDir; Directory(outputDir).createSync(recursive: true); final staticLibs = artifacts.values .expand((element) => element) .where((element) => element.type == AritifactType.staticlib) .toList(); final dynamicLibs = artifacts.values .expand((element) => element) .where((element) => element.type == AritifactType.dylib) .toList(); final libName = environment.crateInfo.packageName; // If there is static lib, use it and link it with pod if (staticLibs.isNotEmpty) { final finalTargetFile = path.join(outputDir, "lib$libName.a"); performLipo(finalTargetFile, staticLibs.map((e) => e.path)); } else { // Otherwise try to replace bundle dylib with our dylib final bundlePaths = [ '$libName.framework/Versions/A/$libName', '$libName.framework/$libName', ]; for (final bundlePath in bundlePaths) { final targetFile = path.join(outputDir, bundlePath); if (File(targetFile).existsSync()) { performLipo(targetFile, dynamicLibs.map((e) => e.path)); // Replace absolute id with @rpath one so that it works properly // when moved to Frameworks. runCommand("install_name_tool", [ '-id', '@rpath/$bundlePath', targetFile, ]); return; } } throw Exception('Unable to find bundle for dynamic library'); } } } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/build_tool.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:io'; import 'package:args/command_runner.dart'; import 'package:ed25519_edwards/ed25519_edwards.dart'; import 'package:github/github.dart'; import 'package:hex/hex.dart'; import 'package:logging/logging.dart'; import 'android_environment.dart'; import 'build_cmake.dart'; import 'build_gradle.dart'; import 'build_pod.dart'; import 'logging.dart'; import 'options.dart'; import 'precompile_binaries.dart'; import 'target.dart'; import 'util.dart'; import 'verify_binaries.dart'; final log = Logger('build_tool'); abstract class BuildCommand extends Command { Future runBuildCommand(CargokitUserOptions options); @override Future run() async { final options = CargokitUserOptions.load(); if (options.verboseLogging || Platform.environment['CARGOKIT_VERBOSE'] == '1') { enableVerboseLogging(); } await runBuildCommand(options); } } class BuildPodCommand extends BuildCommand { @override final name = 'build-pod'; @override final description = 'Build cocoa pod library'; @override Future runBuildCommand(CargokitUserOptions options) async { final build = BuildPod(userOptions: options); await build.build(); } } class BuildGradleCommand extends BuildCommand { @override final name = 'build-gradle'; @override final description = 'Build android library'; @override Future runBuildCommand(CargokitUserOptions options) async { final build = BuildGradle(userOptions: options); await build.build(); } } class BuildCMakeCommand extends BuildCommand { @override final name = 'build-cmake'; @override final description = 'Build CMake library'; @override Future runBuildCommand(CargokitUserOptions options) async { final build = BuildCMake(userOptions: options); await build.build(); } } class GenKeyCommand extends Command { @override final name = 'gen-key'; @override final description = 'Generate key pair for signing precompiled binaries'; @override void run() { final kp = generateKey(); final private = HEX.encode(kp.privateKey.bytes); final public = HEX.encode(kp.publicKey.bytes); print("Private Key: $private"); print("Public Key: $public"); } } class PrecompileBinariesCommand extends Command { PrecompileBinariesCommand() { argParser ..addOption( 'repository', mandatory: true, help: 'Github repository slug in format owner/name', ) ..addOption( 'manifest-dir', mandatory: true, help: 'Directory containing Cargo.toml', ) ..addMultiOption('target', help: 'Rust target triple of artifact to build.\n' 'Can be specified multiple times or omitted in which case\n' 'all targets for current platform will be built.') ..addOption( 'android-sdk-location', help: 'Location of Android SDK (if available)', ) ..addOption( 'android-ndk-version', help: 'Android NDK version (if available)', ) ..addOption( 'android-min-sdk-version', help: 'Android minimum rquired version (if available)', ) ..addOption( 'temp-dir', help: 'Directory to store temporary build artifacts', ) ..addFlag( "verbose", abbr: "v", defaultsTo: false, help: "Enable verbose logging", ); } @override final name = 'precompile-binaries'; @override final description = 'Prebuild and upload binaries\n' 'Private key must be passed through PRIVATE_KEY environment variable. ' 'Use gen_key through generate priave key.\n' 'Github token must be passed as GITHUB_TOKEN environment variable.\n'; @override Future run() async { final verbose = argResults!['verbose'] as bool; if (verbose) { enableVerboseLogging(); } final privateKeyString = Platform.environment['PRIVATE_KEY']; if (privateKeyString == null) { throw ArgumentError('Missing PRIVATE_KEY environment variable'); } final githubToken = Platform.environment['GITHUB_TOKEN']; if (githubToken == null) { throw ArgumentError('Missing GITHUB_TOKEN environment variable'); } final privateKey = HEX.decode(privateKeyString); if (privateKey.length != 64) { throw ArgumentError('Private key must be 64 bytes long'); } final manifestDir = argResults!['manifest-dir'] as String; if (!Directory(manifestDir).existsSync()) { throw ArgumentError('Manifest directory does not exist: $manifestDir'); } String? androidMinSdkVersionString = argResults!['android-min-sdk-version'] as String?; int? androidMinSdkVersion; if (androidMinSdkVersionString != null) { androidMinSdkVersion = int.tryParse(androidMinSdkVersionString); if (androidMinSdkVersion == null) { throw ArgumentError( 'Invalid android-min-sdk-version: $androidMinSdkVersionString'); } } final targetStrigns = argResults!['target'] as List; final targets = targetStrigns.map((target) { final res = Target.forRustTriple(target); if (res == null) { throw ArgumentError('Invalid target: $target'); } return res; }).toList(growable: false); final precompileBinaries = PrecompileBinaries( privateKey: PrivateKey(privateKey), githubToken: githubToken, manifestDir: manifestDir, repositorySlug: RepositorySlug.full(argResults!['repository'] as String), targets: targets, androidSdkLocation: argResults!['android-sdk-location'] as String?, androidNdkVersion: argResults!['android-ndk-version'] as String?, androidMinSdkVersion: androidMinSdkVersion, tempDir: argResults!['temp-dir'] as String?, ); await precompileBinaries.run(); } } class VerifyBinariesCommand extends Command { VerifyBinariesCommand() { argParser.addOption( 'manifest-dir', mandatory: true, help: 'Directory containing Cargo.toml', ); } @override final name = "verify-binaries"; @override final description = 'Verifies published binaries\n' 'Checks whether there is a binary published for each targets\n' 'and checks the signature.'; @override Future run() async { final manifestDir = argResults!['manifest-dir'] as String; final verifyBinaries = VerifyBinaries( manifestDir: manifestDir, ); await verifyBinaries.run(); } } Future runMain(List args) async { try { // Init logging before options are loaded initLogging(); if (Platform.environment['_CARGOKIT_NDK_LINK_TARGET'] != null) { return AndroidEnvironment.clangLinkerWrapper(args); } final runner = CommandRunner('build_tool', 'Cargokit built_tool') ..addCommand(BuildPodCommand()) ..addCommand(BuildGradleCommand()) ..addCommand(BuildCMakeCommand()) ..addCommand(GenKeyCommand()) ..addCommand(PrecompileBinariesCommand()) ..addCommand(VerifyBinariesCommand()); await runner.run(args); } on ArgumentError catch (e) { stderr.writeln(e.toString()); exit(1); } catch (e, s) { log.severe(kDoubleSeparator); log.severe('Cargokit BuildTool failed with error:'); log.severe(kSeparator); log.severe(e); // This tells user to install Rust, there's no need to pollute the log with // stack trace. if (e is! RustupNotFoundException) { log.severe(kSeparator); log.severe(s); log.severe(kSeparator); log.severe('BuildTool arguments: $args'); } log.severe(kDoubleSeparator); exit(1); } } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/builder.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'package:collection/collection.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; import 'android_environment.dart'; import 'cargo.dart'; import 'environment.dart'; import 'options.dart'; import 'rustup.dart'; import 'target.dart'; import 'util.dart'; final _log = Logger('builder'); enum BuildConfiguration { debug, release, profile, } extension on BuildConfiguration { bool get isDebug => this == BuildConfiguration.debug; String get rustName => switch (this) { BuildConfiguration.debug => 'debug', BuildConfiguration.release => 'release', BuildConfiguration.profile => 'release', }; } class BuildException implements Exception { final String message; BuildException(this.message); @override String toString() { return 'BuildException: $message'; } } class BuildEnvironment { final BuildConfiguration configuration; final CargokitCrateOptions crateOptions; final String targetTempDir; final String manifestDir; final CrateInfo crateInfo; final bool isAndroid; final String? androidSdkPath; final String? androidNdkVersion; final int? androidMinSdkVersion; final String? javaHome; BuildEnvironment({ required this.configuration, required this.crateOptions, required this.targetTempDir, required this.manifestDir, required this.crateInfo, required this.isAndroid, this.androidSdkPath, this.androidNdkVersion, this.androidMinSdkVersion, this.javaHome, }); static BuildConfiguration parseBuildConfiguration(String value) { // XCode configuration adds the flavor to configuration name. final firstSegment = value.split('-').first; final buildConfiguration = BuildConfiguration.values.firstWhereOrNull( (e) => e.name == firstSegment, ); if (buildConfiguration == null) { _log.warning('Unknown build configuraiton $value, will assume release'); return BuildConfiguration.release; } return buildConfiguration; } static BuildEnvironment fromEnvironment({ required bool isAndroid, }) { final buildConfiguration = parseBuildConfiguration(Environment.configuration); final manifestDir = Environment.manifestDir; final crateOptions = CargokitCrateOptions.load( manifestDir: manifestDir, ); final crateInfo = CrateInfo.load(manifestDir); return BuildEnvironment( configuration: buildConfiguration, crateOptions: crateOptions, targetTempDir: Environment.targetTempDir, manifestDir: manifestDir, crateInfo: crateInfo, isAndroid: isAndroid, androidSdkPath: isAndroid ? Environment.sdkPath : null, androidNdkVersion: isAndroid ? Environment.ndkVersion : null, androidMinSdkVersion: isAndroid ? int.parse(Environment.minSdkVersion) : null, javaHome: isAndroid ? Environment.javaHome : null, ); } } class RustBuilder { final Target target; final BuildEnvironment environment; RustBuilder({ required this.target, required this.environment, }); void prepare( Rustup rustup, ) { final toolchain = _toolchain; if (rustup.installedTargets(toolchain) == null) { rustup.installToolchain(toolchain); } if (toolchain == 'nightly') { rustup.installRustSrcForNightly(); } if (!rustup.installedTargets(toolchain)!.contains(target.rust)) { rustup.installTarget(target.rust, toolchain: toolchain); } } CargoBuildOptions? get _buildOptions => environment.crateOptions.cargo[environment.configuration]; String get _toolchain => _buildOptions?.toolchain.name ?? 'stable'; /// Returns the path of directory containing build artifacts. Future build() async { final extraArgs = _buildOptions?.flags ?? []; final manifestPath = path.join(environment.manifestDir, 'Cargo.toml'); runCommand( 'rustup', [ 'run', _toolchain, 'cargo', 'build', ...extraArgs, '--manifest-path', manifestPath, '-p', environment.crateInfo.packageName, if (!environment.configuration.isDebug) '--release', '--target', target.rust, '--target-dir', environment.targetTempDir, ], environment: await _buildEnvironment(), ); return path.join( environment.targetTempDir, target.rust, environment.configuration.rustName, ); } Future> _buildEnvironment() async { if (target.android == null) { return {}; } else { final sdkPath = environment.androidSdkPath; final ndkVersion = environment.androidNdkVersion; final minSdkVersion = environment.androidMinSdkVersion; if (sdkPath == null) { throw BuildException('androidSdkPath is not set'); } if (ndkVersion == null) { throw BuildException('androidNdkVersion is not set'); } if (minSdkVersion == null) { throw BuildException('androidMinSdkVersion is not set'); } final env = AndroidEnvironment( sdkPath: sdkPath, ndkVersion: ndkVersion, minSdkVersion: minSdkVersion, targetTempDir: environment.targetTempDir, target: target, ); if (!env.ndkIsInstalled() && environment.javaHome != null) { env.installNdk(javaHome: environment.javaHome!); } return env.buildEnvironment(); } } } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/cargo.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:io'; import 'package:path/path.dart' as path; import 'package:toml/toml.dart'; class ManifestException { ManifestException(this.message, {required this.fileName}); final String? fileName; final String message; @override String toString() { if (fileName != null) { return 'Failed to parse package manifest at $fileName: $message'; } else { return 'Failed to parse package manifest: $message'; } } } class CrateInfo { CrateInfo({required this.packageName}); final String packageName; static CrateInfo parseManifest(String manifest, {final String? fileName}) { final toml = TomlDocument.parse(manifest); final package = toml.toMap()['package']; if (package == null) { throw ManifestException('Missing package section', fileName: fileName); } final name = package['name']; if (name == null) { throw ManifestException('Missing package name', fileName: fileName); } return CrateInfo(packageName: name); } static CrateInfo load(String manifestDir) { final manifestFile = File(path.join(manifestDir, 'Cargo.toml')); final manifest = manifestFile.readAsStringSync(); return parseManifest(manifest, fileName: manifestFile.path); } } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/crate_hash.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:convert/convert.dart'; import 'package:crypto/crypto.dart'; import 'package:path/path.dart' as path; class CrateHash { /// Computes a hash uniquely identifying crate content. This takes into account /// content all all .rs files inside the src directory, as well as Cargo.toml, /// Cargo.lock, build.rs and cargokit.yaml. /// /// If [tempStorage] is provided, computed hash is stored in a file in that directory /// and reused on subsequent calls if the crate content hasn't changed. static String compute(String manifestDir, {String? tempStorage}) { return CrateHash._( manifestDir: manifestDir, tempStorage: tempStorage, )._compute(); } CrateHash._({ required this.manifestDir, required this.tempStorage, }); String _compute() { final files = getFiles(); final tempStorage = this.tempStorage; if (tempStorage != null) { final quickHash = _computeQuickHash(files); final quickHashFolder = Directory(path.join(tempStorage, 'crate_hash')); quickHashFolder.createSync(recursive: true); final quickHashFile = File(path.join(quickHashFolder.path, quickHash)); if (quickHashFile.existsSync()) { return quickHashFile.readAsStringSync(); } final hash = _computeHash(files); quickHashFile.writeAsStringSync(hash); return hash; } else { return _computeHash(files); } } /// Computes a quick hash based on files stat (without reading contents). This /// is used to cache the real hash, which is slower to compute since it involves /// reading every single file. String _computeQuickHash(List files) { final output = AccumulatorSink(); final input = sha256.startChunkedConversion(output); final data = ByteData(8); for (final file in files) { input.add(utf8.encode(file.path)); final stat = file.statSync(); data.setUint64(0, stat.size); input.add(data.buffer.asUint8List()); data.setUint64(0, stat.modified.millisecondsSinceEpoch); input.add(data.buffer.asUint8List()); } input.close(); return base64Url.encode(output.events.single.bytes); } String _computeHash(List files) { final output = AccumulatorSink(); final input = sha256.startChunkedConversion(output); void addTextFile(File file) { // text Files are hashed by lines in case we're dealing with github checkout // that auto-converts line endings. final splitter = LineSplitter(); if (file.existsSync()) { final data = file.readAsStringSync(); final lines = splitter.convert(data); for (final line in lines) { input.add(utf8.encode(line)); } } } for (final file in files) { addTextFile(file); } input.close(); final res = output.events.single; // Truncate to 128bits. final hash = res.bytes.sublist(0, 16); return hex.encode(hash); } List getFiles() { final src = Directory(path.join(manifestDir, 'src')); final files = src .listSync(recursive: true, followLinks: false) .whereType() .toList(); files.sortBy((element) => element.path); void addFile(String relative) { final file = File(path.join(manifestDir, relative)); if (file.existsSync()) { files.add(file); } } addFile('Cargo.toml'); addFile('Cargo.lock'); addFile('build.rs'); addFile('cargokit.yaml'); return files; } final String manifestDir; final String? tempStorage; } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/environment.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:io'; extension on String { String resolveSymlink() => File(this).resolveSymbolicLinksSync(); } class Environment { /// Current build configuration (debug or release). static String get configuration => _getEnv("CARGOKIT_CONFIGURATION").toLowerCase(); static bool get isDebug => configuration == 'debug'; static bool get isRelease => configuration == 'release'; /// Temporary directory where Rust build artifacts are placed. static String get targetTempDir => _getEnv("CARGOKIT_TARGET_TEMP_DIR"); /// Final output directory where the build artifacts are placed. static String get outputDir => _getEnvPath('CARGOKIT_OUTPUT_DIR'); /// Path to the crate manifest (containing Cargo.toml). static String get manifestDir => _getEnvPath('CARGOKIT_MANIFEST_DIR'); /// Directory inside root project. Not necessarily root folder. Symlinks are /// not resolved on purpose. static String get rootProjectDir => _getEnv('CARGOKIT_ROOT_PROJECT_DIR'); // Pod /// Platform name (macosx, iphoneos, iphonesimulator). static String get darwinPlatformName => _getEnv("CARGOKIT_DARWIN_PLATFORM_NAME"); /// List of architectures to build for (arm64, armv7, x86_64). static List get darwinArchs => _getEnv("CARGOKIT_DARWIN_ARCHS").split(' '); // Gradle static String get minSdkVersion => _getEnv("CARGOKIT_MIN_SDK_VERSION"); static String get ndkVersion => _getEnv("CARGOKIT_NDK_VERSION"); static String get sdkPath => _getEnvPath("CARGOKIT_SDK_DIR"); static String get javaHome => _getEnvPath("CARGOKIT_JAVA_HOME"); static List get targetPlatforms => _getEnv("CARGOKIT_TARGET_PLATFORMS").split(','); // CMAKE static String get targetPlatform => _getEnv("CARGOKIT_TARGET_PLATFORM"); static String _getEnv(String key) { final res = Platform.environment[key]; if (res == null) { throw Exception("Missing environment variable $key"); } return res; } static String _getEnvPath(String key) { final res = _getEnv(key); if (Directory(res).existsSync()) { return res.resolveSymlink(); } else { return res; } } } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/logging.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:io'; import 'package:logging/logging.dart'; const String kSeparator = "--"; const String kDoubleSeparator = "=="; bool _lastMessageWasSeparator = false; void _log(LogRecord rec) { final prefix = '${rec.level.name}: '; final out = rec.level == Level.SEVERE ? stderr : stdout; if (rec.message == kSeparator) { if (!_lastMessageWasSeparator) { out.write(prefix); out.writeln('-' * 80); _lastMessageWasSeparator = true; } return; } else if (rec.message == kDoubleSeparator) { out.write(prefix); out.writeln('=' * 80); _lastMessageWasSeparator = true; return; } out.write(prefix); out.writeln(rec.message); _lastMessageWasSeparator = false; } void initLogging() { Logger.root.level = Level.INFO; Logger.root.onRecord.listen((LogRecord rec) { final lines = rec.message.split('\n'); for (final line in lines) { if (line.isNotEmpty || lines.length == 1 || line != lines.last) { _log(LogRecord( rec.level, line, rec.loggerName, )); } } }); } void enableVerboseLogging() { Logger.root.level = Level.ALL; } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/options.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:io'; import 'package:collection/collection.dart'; import 'package:ed25519_edwards/ed25519_edwards.dart'; import 'package:hex/hex.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; import 'package:source_span/source_span.dart'; import 'package:yaml/yaml.dart'; import 'builder.dart'; import 'environment.dart'; import 'rustup.dart'; final _log = Logger('options'); /// A class for exceptions that have source span information attached. class SourceSpanException implements Exception { // This is a getter so that subclasses can override it. /// A message describing the exception. String get message => _message; final String _message; // This is a getter so that subclasses can override it. /// The span associated with this exception. /// /// This may be `null` if the source location can't be determined. SourceSpan? get span => _span; final SourceSpan? _span; SourceSpanException(this._message, this._span); /// Returns a string representation of `this`. /// /// [color] may either be a [String], a [bool], or `null`. If it's a string, /// it indicates an ANSI terminal color escape that should be used to /// highlight the span's text. If it's `true`, it indicates that the text /// should be highlighted using the default color. If it's `false` or `null`, /// it indicates that the text shouldn't be highlighted. @override String toString({Object? color}) { if (span == null) return message; return 'Error on ${span!.message(message, color: color)}'; } } enum Toolchain { stable, beta, nightly, } class CargoBuildOptions { final Toolchain toolchain; final List flags; CargoBuildOptions({ required this.toolchain, required this.flags, }); static Toolchain _toolchainFromNode(YamlNode node) { if (node case YamlScalar(value: String name)) { final toolchain = Toolchain.values.firstWhereOrNull((element) => element.name == name); if (toolchain != null) { return toolchain; } } throw SourceSpanException( 'Unknown toolchain. Must be one of ${Toolchain.values.map((e) => e.name)}.', node.span); } static CargoBuildOptions parse(YamlNode node) { if (node is! YamlMap) { throw SourceSpanException('Cargo options must be a map', node.span); } Toolchain toolchain = Toolchain.stable; List flags = []; for (final MapEntry(:key, :value) in node.nodes.entries) { if (key case YamlScalar(value: 'toolchain')) { toolchain = _toolchainFromNode(value); } else if (key case YamlScalar(value: 'extra_flags')) { if (value case YamlList(nodes: List list)) { if (list.every((element) { if (element case YamlScalar(value: String _)) { return true; } return false; })) { flags = list.map((e) => e.value as String).toList(); continue; } } throw SourceSpanException( 'Extra flags must be a list of strings', value.span); } else { throw SourceSpanException( 'Unknown cargo option type. Must be "toolchain" or "extra_flags".', key.span); } } return CargoBuildOptions(toolchain: toolchain, flags: flags); } } extension on YamlMap { /// Map that extracts keys so that we can do map case check on them. Map get valueMap => nodes.map((key, value) => MapEntry(key.value, value)); } class PrecompiledBinaries { final String uriPrefix; final PublicKey publicKey; PrecompiledBinaries({ required this.uriPrefix, required this.publicKey, }); static PublicKey _publicKeyFromHex(String key, SourceSpan? span) { final bytes = HEX.decode(key); if (bytes.length != 32) { throw SourceSpanException( 'Invalid public key. Must be 32 bytes long.', span); } return PublicKey(bytes); } static PrecompiledBinaries parse(YamlNode node) { if (node case YamlMap(valueMap: Map map)) { if (map case { 'url_prefix': YamlNode urlPrefixNode, 'public_key': YamlNode publicKeyNode, }) { final urlPrefix = switch (urlPrefixNode) { YamlScalar(value: String urlPrefix) => urlPrefix, _ => throw SourceSpanException( 'Invalid URL prefix value.', urlPrefixNode.span), }; final publicKey = switch (publicKeyNode) { YamlScalar(value: String publicKey) => _publicKeyFromHex(publicKey, publicKeyNode.span), _ => throw SourceSpanException( 'Invalid public key value.', publicKeyNode.span), }; return PrecompiledBinaries( uriPrefix: urlPrefix, publicKey: publicKey, ); } } throw SourceSpanException( 'Invalid precompiled binaries value. ' 'Expected Map with "url_prefix" and "public_key".', node.span); } } /// Cargokit options specified for Rust crate. class CargokitCrateOptions { CargokitCrateOptions({ this.cargo = const {}, this.precompiledBinaries, }); final Map cargo; final PrecompiledBinaries? precompiledBinaries; static CargokitCrateOptions parse(YamlNode node) { if (node is! YamlMap) { throw SourceSpanException('Cargokit options must be a map', node.span); } final options = {}; PrecompiledBinaries? precompiledBinaries; for (final entry in node.nodes.entries) { if (entry case MapEntry( key: YamlScalar(value: 'cargo'), value: YamlNode node, )) { if (node is! YamlMap) { throw SourceSpanException('Cargo options must be a map', node.span); } for (final MapEntry(:YamlNode key, :value) in node.nodes.entries) { if (key case YamlScalar(value: String name)) { final configuration = BuildConfiguration.values .firstWhereOrNull((element) => element.name == name); if (configuration != null) { options[configuration] = CargoBuildOptions.parse(value); continue; } } throw SourceSpanException( 'Unknown build configuration. Must be one of ${BuildConfiguration.values.map((e) => e.name)}.', key.span); } } else if (entry.key case YamlScalar(value: 'precompiled_binaries')) { precompiledBinaries = PrecompiledBinaries.parse(entry.value); } else { throw SourceSpanException( 'Unknown cargokit option type. Must be "cargo" or "precompiled_binaries".', entry.key.span); } } return CargokitCrateOptions( cargo: options, precompiledBinaries: precompiledBinaries, ); } static CargokitCrateOptions load({ required String manifestDir, }) { final uri = Uri.file(path.join(manifestDir, "cargokit.yaml")); final file = File.fromUri(uri); if (file.existsSync()) { final contents = loadYamlNode(file.readAsStringSync(), sourceUrl: uri); return parse(contents); } else { return CargokitCrateOptions(); } } } class CargokitUserOptions { // When Rustup is installed always build locally unless user opts into // using precompiled binaries. static bool defaultUsePrecompiledBinaries() { return Rustup.executablePath() == null; } CargokitUserOptions({ required this.usePrecompiledBinaries, required this.verboseLogging, }); CargokitUserOptions._() : usePrecompiledBinaries = defaultUsePrecompiledBinaries(), verboseLogging = false; static CargokitUserOptions parse(YamlNode node) { if (node is! YamlMap) { throw SourceSpanException('Cargokit options must be a map', node.span); } bool usePrecompiledBinaries = defaultUsePrecompiledBinaries(); bool verboseLogging = false; for (final entry in node.nodes.entries) { if (entry.key case YamlScalar(value: 'use_precompiled_binaries')) { if (entry.value case YamlScalar(value: bool value)) { usePrecompiledBinaries = value; continue; } throw SourceSpanException( 'Invalid value for "use_precompiled_binaries". Must be a boolean.', entry.value.span); } else if (entry.key case YamlScalar(value: 'verbose_logging')) { if (entry.value case YamlScalar(value: bool value)) { verboseLogging = value; continue; } throw SourceSpanException( 'Invalid value for "verbose_logging". Must be a boolean.', entry.value.span); } else { throw SourceSpanException( 'Unknown cargokit option type. Must be "use_precompiled_binaries" or "verbose_logging".', entry.key.span); } } return CargokitUserOptions( usePrecompiledBinaries: usePrecompiledBinaries, verboseLogging: verboseLogging, ); } static CargokitUserOptions load() { String fileName = "cargokit_options.yaml"; var userProjectDir = Directory(Environment.rootProjectDir); while (userProjectDir.parent.path != userProjectDir.path) { final configFile = File(path.join(userProjectDir.path, fileName)); if (configFile.existsSync()) { final contents = loadYamlNode( configFile.readAsStringSync(), sourceUrl: configFile.uri, ); final res = parse(contents); if (res.verboseLogging) { _log.info('Found user options file at ${configFile.path}'); } return res; } userProjectDir = userProjectDir.parent; } return CargokitUserOptions._(); } final bool usePrecompiledBinaries; final bool verboseLogging; } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/precompile_binaries.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:io'; import 'package:ed25519_edwards/ed25519_edwards.dart'; import 'package:github/github.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; import 'artifacts_provider.dart'; import 'builder.dart'; import 'cargo.dart'; import 'crate_hash.dart'; import 'options.dart'; import 'rustup.dart'; import 'target.dart'; final _log = Logger('precompile_binaries'); class PrecompileBinaries { PrecompileBinaries({ required this.privateKey, required this.githubToken, required this.repositorySlug, required this.manifestDir, required this.targets, this.androidSdkLocation, this.androidNdkVersion, this.androidMinSdkVersion, this.tempDir, }); final PrivateKey privateKey; final String githubToken; final RepositorySlug repositorySlug; final String manifestDir; final List targets; final String? androidSdkLocation; final String? androidNdkVersion; final int? androidMinSdkVersion; final String? tempDir; static String fileName(Target target, String name) { return '${target.rust}_$name'; } static String signatureFileName(Target target, String name) { return '${target.rust}_$name.sig'; } Future run() async { final crateInfo = CrateInfo.load(manifestDir); final targets = List.of(this.targets); if (targets.isEmpty) { targets.addAll([ ...Target.buildableTargets(), if (androidSdkLocation != null) ...Target.androidTargets(), ]); } _log.info('Precompiling binaries for $targets'); final hash = CrateHash.compute(manifestDir); _log.info('Computed crate hash: $hash'); final String tagName = 'precompiled_$hash'; final github = GitHub(auth: Authentication.withToken(githubToken)); final repo = github.repositories; final release = await _getOrCreateRelease( repo: repo, tagName: tagName, packageName: crateInfo.packageName, hash: hash, ); final tempDir = this.tempDir != null ? Directory(this.tempDir!) : Directory.systemTemp.createTempSync('precompiled_'); tempDir.createSync(recursive: true); final crateOptions = CargokitCrateOptions.load( manifestDir: manifestDir, ); final buildEnvironment = BuildEnvironment( configuration: BuildConfiguration.release, crateOptions: crateOptions, targetTempDir: tempDir.path, manifestDir: manifestDir, crateInfo: crateInfo, isAndroid: androidSdkLocation != null, androidSdkPath: androidSdkLocation, androidNdkVersion: androidNdkVersion, androidMinSdkVersion: androidMinSdkVersion, ); final rustup = Rustup(); for (final target in targets) { final artifactNames = getArtifactNames( target: target, libraryName: crateInfo.packageName, remote: true, ); if (artifactNames.every((name) { final fileName = PrecompileBinaries.fileName(target, name); return (release.assets ?? []).any((e) => e.name == fileName); })) { _log.info("All artifacts for $target already exist - skipping"); continue; } _log.info('Building for $target'); final builder = RustBuilder(target: target, environment: buildEnvironment); builder.prepare(rustup); final res = await builder.build(); final assets = []; for (final name in artifactNames) { final file = File(path.join(res, name)); if (!file.existsSync()) { throw Exception('Missing artifact: ${file.path}'); } final data = file.readAsBytesSync(); final create = CreateReleaseAsset( name: PrecompileBinaries.fileName(target, name), contentType: "application/octet-stream", assetData: data, ); final signature = sign(privateKey, data); final signatureCreate = CreateReleaseAsset( name: signatureFileName(target, name), contentType: "application/octet-stream", assetData: signature, ); bool verified = verify(public(privateKey), data, signature); if (!verified) { throw Exception('Signature verification failed'); } assets.add(create); assets.add(signatureCreate); } _log.info('Uploading assets: ${assets.map((e) => e.name)}'); for (final asset in assets) { // This seems to be failing on CI so do it one by one int retryCount = 0; while (true) { try { await repo.uploadReleaseAssets(release, [asset]); break; } on Exception catch (e) { if (retryCount == 10) { rethrow; } ++retryCount; _log.shout( 'Upload failed (attempt $retryCount, will retry): ${e.toString()}'); await Future.delayed(Duration(seconds: 2)); } } } } _log.info('Cleaning up'); tempDir.deleteSync(recursive: true); } Future _getOrCreateRelease({ required RepositoriesService repo, required String tagName, required String packageName, required String hash, }) async { Release release; try { _log.info('Fetching release $tagName'); release = await repo.getReleaseByTagName(repositorySlug, tagName); } on ReleaseNotFound { _log.info('Release not found - creating release $tagName'); release = await repo.createRelease( repositorySlug, CreateRelease.from( tagName: tagName, name: 'Precompiled binaries ${hash.substring(0, 8)}', targetCommitish: null, isDraft: false, isPrerelease: false, body: 'Precompiled binaries for crate $packageName, ' 'crate hash $hash.', )); } return release; } } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/rustup.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:io'; import 'package:collection/collection.dart'; import 'package:path/path.dart' as path; import 'util.dart'; class _Toolchain { _Toolchain( this.name, this.targets, ); final String name; final List targets; } class Rustup { List? installedTargets(String toolchain) { final targets = _installedTargets(toolchain); return targets != null ? List.unmodifiable(targets) : null; } void installToolchain(String toolchain) { log.info("Installing Rust toolchain: $toolchain"); runCommand("rustup", ['toolchain', 'install', toolchain]); _installedToolchains .add(_Toolchain(toolchain, _getInstalledTargets(toolchain))); } void installTarget( String target, { required String toolchain, }) { log.info("Installing Rust target: $target"); runCommand("rustup", [ 'target', 'add', '--toolchain', toolchain, target, ]); _installedTargets(toolchain)?.add(target); } final List<_Toolchain> _installedToolchains; Rustup() : _installedToolchains = _getInstalledToolchains(); List? _installedTargets(String toolchain) => _installedToolchains .firstWhereOrNull( (e) => e.name == toolchain || e.name.startsWith('$toolchain-')) ?.targets; static List<_Toolchain> _getInstalledToolchains() { String extractToolchainName(String line) { // ignore (default) after toolchain name final parts = line.split(' '); return parts[0]; } final res = runCommand("rustup", ['toolchain', 'list']); // To list all non-custom toolchains, we need to filter out lines that // don't start with "stable", "beta", or "nightly". Pattern nonCustom = RegExp(r"^(stable|beta|nightly)"); final lines = res.stdout .toString() .split('\n') .where((e) => e.isNotEmpty && e.startsWith(nonCustom)) .map(extractToolchainName) .toList(growable: true); return lines .map( (name) => _Toolchain( name, _getInstalledTargets(name), ), ) .toList(growable: true); } static List _getInstalledTargets(String toolchain) { final res = runCommand("rustup", [ 'target', 'list', '--toolchain', toolchain, '--installed', ]); final lines = res.stdout .toString() .split('\n') .where((e) => e.isNotEmpty) .toList(growable: true); return lines; } bool _didInstallRustSrcForNightly = false; void installRustSrcForNightly() { if (_didInstallRustSrcForNightly) { return; } // Useful for -Z build-std runCommand( "rustup", ['component', 'add', 'rust-src', '--toolchain', 'nightly'], ); _didInstallRustSrcForNightly = true; } static String? executablePath() { final envPath = Platform.environment['PATH']; final envPathSeparator = Platform.isWindows ? ';' : ':'; final home = Platform.isWindows ? Platform.environment['USERPROFILE'] : Platform.environment['HOME']; final paths = [ if (home != null) path.join(home, '.cargo', 'bin'), if (envPath != null) ...envPath.split(envPathSeparator), ]; for (final p in paths) { final rustup = Platform.isWindows ? 'rustup.exe' : 'rustup'; final rustupPath = path.join(p, rustup); if (File(rustupPath).existsSync()) { return rustupPath; } } return null; } } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/target.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:io'; import 'package:collection/collection.dart'; import 'util.dart'; class Target { Target({ required this.rust, this.flutter, this.android, this.androidMinSdkVersion, this.darwinPlatform, this.darwinArch, }); static final all = [ Target( rust: 'armv7-linux-androideabi', flutter: 'android-arm', android: 'armeabi-v7a', androidMinSdkVersion: 16, ), Target( rust: 'aarch64-linux-android', flutter: 'android-arm64', android: 'arm64-v8a', androidMinSdkVersion: 21, ), Target( rust: 'i686-linux-android', flutter: 'android-x86', android: 'x86', androidMinSdkVersion: 16, ), Target( rust: 'x86_64-linux-android', flutter: 'android-x64', android: 'x86_64', androidMinSdkVersion: 21, ), Target( rust: 'x86_64-pc-windows-msvc', flutter: 'windows-x64', ), Target( rust: 'x86_64-unknown-linux-gnu', flutter: 'linux-x64', ), Target( rust: 'aarch64-unknown-linux-gnu', flutter: 'linux-arm64', ), Target( rust: 'x86_64-apple-darwin', darwinPlatform: 'macosx', darwinArch: 'x86_64', ), Target( rust: 'aarch64-apple-darwin', darwinPlatform: 'macosx', darwinArch: 'arm64', ), Target( rust: 'aarch64-apple-ios', darwinPlatform: 'iphoneos', darwinArch: 'arm64', ), Target( rust: 'aarch64-apple-ios-sim', darwinPlatform: 'iphonesimulator', darwinArch: 'arm64', ), Target( rust: 'x86_64-apple-ios', darwinPlatform: 'iphonesimulator', darwinArch: 'x86_64', ), ]; static Target? forFlutterName(String flutterName) { return all.firstWhereOrNull((element) => element.flutter == flutterName); } static Target? forDarwin({ required String platformName, required String darwinAarch, }) { return all.firstWhereOrNull((element) => // element.darwinPlatform == platformName && element.darwinArch == darwinAarch); } static Target? forRustTriple(String triple) { return all.firstWhereOrNull((element) => element.rust == triple); } static List androidTargets() { return all .where((element) => element.android != null) .toList(growable: false); } /// Returns buildable targets on current host platform ignoring Android targets. static List buildableTargets() { if (Platform.isLinux) { // Right now we don't support cross-compiling on Linux. So we just return // the host target. final arch = runCommand('arch', []).stdout as String; if (arch.trim() == 'aarch64') { return [Target.forRustTriple('aarch64-unknown-linux-gnu')!]; } else { return [Target.forRustTriple('x86_64-unknown-linux-gnu')!]; } } return all.where((target) { if (Platform.isWindows) { return target.rust.contains('-windows-'); } else if (Platform.isMacOS) { return target.darwinPlatform != null; } return false; }).toList(growable: false); } @override String toString() { return rust; } final String? flutter; final String rust; final String? android; final int? androidMinSdkVersion; final String? darwinPlatform; final String? darwinArch; } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/util.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:convert'; import 'dart:io'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; import 'logging.dart'; import 'rustup.dart'; final log = Logger("process"); class CommandFailedException implements Exception { final String executable; final List arguments; final ProcessResult result; CommandFailedException({ required this.executable, required this.arguments, required this.result, }); @override String toString() { final stdout = result.stdout.toString().trim(); final stderr = result.stderr.toString().trim(); return [ "External Command: $executable ${arguments.map((e) => '"$e"').join(' ')}", "Returned Exit Code: ${result.exitCode}", kSeparator, "STDOUT:", if (stdout.isNotEmpty) stdout, kSeparator, "STDERR:", if (stderr.isNotEmpty) stderr, ].join('\n'); } } class TestRunCommandArgs { final String executable; final List arguments; final String? workingDirectory; final Map? environment; final bool includeParentEnvironment; final bool runInShell; final Encoding? stdoutEncoding; final Encoding? stderrEncoding; TestRunCommandArgs({ required this.executable, required this.arguments, this.workingDirectory, this.environment, this.includeParentEnvironment = true, this.runInShell = false, this.stdoutEncoding, this.stderrEncoding, }); } class TestRunCommandResult { TestRunCommandResult({ this.pid = 1, this.exitCode = 0, this.stdout = '', this.stderr = '', }); final int pid; final int exitCode; final String stdout; final String stderr; } TestRunCommandResult Function(TestRunCommandArgs args)? testRunCommandOverride; ProcessResult runCommand( String executable, List arguments, { String? workingDirectory, Map? environment, bool includeParentEnvironment = true, bool runInShell = false, Encoding? stdoutEncoding = systemEncoding, Encoding? stderrEncoding = systemEncoding, }) { if (testRunCommandOverride != null) { final result = testRunCommandOverride!(TestRunCommandArgs( executable: executable, arguments: arguments, workingDirectory: workingDirectory, environment: environment, includeParentEnvironment: includeParentEnvironment, runInShell: runInShell, stdoutEncoding: stdoutEncoding, stderrEncoding: stderrEncoding, )); return ProcessResult( result.pid, result.exitCode, result.stdout, result.stderr, ); } log.finer('Running command $executable ${arguments.join(' ')}'); final res = Process.runSync( _resolveExecutable(executable), arguments, workingDirectory: workingDirectory, environment: environment, includeParentEnvironment: includeParentEnvironment, runInShell: runInShell, stderrEncoding: stderrEncoding, stdoutEncoding: stdoutEncoding, ); if (res.exitCode != 0) { throw CommandFailedException( executable: executable, arguments: arguments, result: res, ); } else { return res; } } class RustupNotFoundException implements Exception { @override String toString() { return [ ' ', 'rustup not found in PATH.', ' ', 'Maybe you need to install Rust? It only takes a minute:', ' ', if (Platform.isWindows) 'https://www.rust-lang.org/tools/install', if (hasHomebrewRustInPath()) ...[ '\$ brew unlink rust # Unlink homebrew Rust from PATH', ], if (!Platform.isWindows) "\$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh", ' ', ].join('\n'); } static bool hasHomebrewRustInPath() { if (!Platform.isMacOS) { return false; } final envPath = Platform.environment['PATH'] ?? ''; final paths = envPath.split(':'); return paths.any((p) { return p.contains('homebrew') && File(path.join(p, 'rustc')).existsSync(); }); } } String _resolveExecutable(String executable) { if (executable == 'rustup') { final resolved = Rustup.executablePath(); if (resolved != null) { return resolved; } throw RustupNotFoundException(); } else { return executable; } } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/lib/src/verify_binaries.dart ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import 'dart:io'; import 'package:ed25519_edwards/ed25519_edwards.dart'; import 'package:http/http.dart'; import 'artifacts_provider.dart'; import 'cargo.dart'; import 'crate_hash.dart'; import 'options.dart'; import 'precompile_binaries.dart'; import 'target.dart'; class VerifyBinaries { VerifyBinaries({ required this.manifestDir, }); final String manifestDir; Future run() async { final crateInfo = CrateInfo.load(manifestDir); final config = CargokitCrateOptions.load(manifestDir: manifestDir); final precompiledBinaries = config.precompiledBinaries; if (precompiledBinaries == null) { stdout.writeln('Crate does not support precompiled binaries.'); } else { final crateHash = CrateHash.compute(manifestDir); stdout.writeln('Crate hash: $crateHash'); for (final target in Target.all) { final message = 'Checking ${target.rust}...'; stdout.write(message.padRight(40)); stdout.flush(); final artifacts = getArtifactNames( target: target, libraryName: crateInfo.packageName, remote: true, ); final prefix = precompiledBinaries.uriPrefix; bool ok = true; for (final artifact in artifacts) { final fileName = PrecompileBinaries.fileName(target, artifact); final signatureFileName = PrecompileBinaries.signatureFileName(target, artifact); final url = Uri.parse('$prefix$crateHash/$fileName'); final signatureUrl = Uri.parse('$prefix$crateHash/$signatureFileName'); final signature = await get(signatureUrl); if (signature.statusCode != 200) { stdout.writeln('MISSING'); ok = false; break; } final asset = await get(url); if (asset.statusCode != 200) { stdout.writeln('MISSING'); ok = false; break; } if (!verify(precompiledBinaries.publicKey, asset.bodyBytes, signature.bodyBytes)) { stdout.writeln('INVALID SIGNATURE'); ok = false; } } if (ok) { stdout.writeln('OK'); } } } } } ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/pubspec.lock ================================================ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: _fe_analyzer_shared: dependency: transitive description: name: _fe_analyzer_shared sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted version: "67.0.0" adaptive_number: dependency: transitive description: name: adaptive_number sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77" url: "https://pub.dev" source: hosted version: "1.0.0" analyzer: dependency: transitive description: name: analyzer sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted version: "6.4.1" args: dependency: "direct main" description: name: args sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted version: "2.4.2" async: dependency: transitive description: name: async sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted version: "2.13.0" boolean_selector: dependency: transitive description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted version: "2.1.2" cli_config: dependency: transitive description: name: cli_config sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec url: "https://pub.dev" source: hosted version: "0.2.0" collection: dependency: "direct main" description: name: collection sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted version: "1.18.0" convert: dependency: "direct main" description: name: convert sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" url: "https://pub.dev" source: hosted version: "3.1.1" coverage: dependency: transitive description: name: coverage sha256: "802bd084fb82e55df091ec8ad1553a7331b61c08251eef19a508b6f3f3a9858d" url: "https://pub.dev" source: hosted version: "1.13.1" crypto: dependency: "direct main" description: name: crypto sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted version: "3.0.3" ed25519_edwards: dependency: "direct main" description: name: ed25519_edwards sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd" url: "https://pub.dev" source: hosted version: "0.3.1" file: dependency: transitive description: name: file sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" url: "https://pub.dev" source: hosted version: "6.1.4" fixnum: dependency: transitive description: name: fixnum sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted version: "1.1.1" frontend_server_client: dependency: transitive description: name: frontend_server_client sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted version: "4.0.0" github: dependency: "direct main" description: name: github sha256: "9966bc13bf612342e916b0a343e95e5f046c88f602a14476440e9b75d2295411" url: "https://pub.dev" source: hosted version: "9.17.0" glob: dependency: transitive description: name: glob sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted version: "2.1.3" hex: dependency: "direct main" description: name: hex sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" url: "https://pub.dev" source: hosted version: "0.2.0" http: dependency: "direct main" description: name: http sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" url: "https://pub.dev" source: hosted version: "1.1.0" http_multi_server: dependency: transitive description: name: http_multi_server sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted version: "3.2.2" http_parser: dependency: transitive description: name: http_parser sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" url: "https://pub.dev" source: hosted version: "4.0.2" io: dependency: transitive description: name: io sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted version: "1.0.5" js: dependency: transitive description: name: js sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted version: "0.7.2" json_annotation: dependency: transitive description: name: json_annotation sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted version: "4.9.0" lints: dependency: "direct dev" description: name: lints sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" url: "https://pub.dev" source: hosted version: "2.1.1" logging: dependency: "direct main" description: name: logging sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted version: "1.2.0" matcher: dependency: transitive description: name: matcher sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted version: "0.12.17" meta: dependency: transitive description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted version: "1.17.0" mime: dependency: transitive description: name: mime sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted version: "2.0.0" node_preamble: dependency: transitive description: name: node_preamble sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" url: "https://pub.dev" source: hosted version: "2.0.2" package_config: dependency: transitive description: name: package_config sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted version: "2.2.0" path: dependency: "direct main" description: name: path sha256: "2ad4cddff7f5cc0e2d13069f2a3f7a73ca18f66abd6f5ecf215219cdb3638edb" url: "https://pub.dev" source: hosted version: "1.8.0" petitparser: dependency: transitive description: name: petitparser sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 url: "https://pub.dev" source: hosted version: "5.4.0" pool: dependency: transitive description: name: pool sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" url: "https://pub.dev" source: hosted version: "1.5.2" pub_semver: dependency: transitive description: name: pub_semver sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted version: "2.2.0" shelf: dependency: transitive description: name: shelf sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 url: "https://pub.dev" source: hosted version: "1.4.1" shelf_packages_handler: dependency: transitive description: name: shelf_packages_handler sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" url: "https://pub.dev" source: hosted version: "3.0.2" shelf_static: dependency: transitive description: name: shelf_static sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 url: "https://pub.dev" source: hosted version: "1.1.3" shelf_web_socket: dependency: transitive description: name: shelf_web_socket sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted version: "3.0.0" source_map_stack_trace: dependency: transitive description: name: source_map_stack_trace sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b url: "https://pub.dev" source: hosted version: "2.1.2" source_maps: dependency: transitive description: name: source_maps sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" url: "https://pub.dev" source: hosted version: "0.10.13" source_span: dependency: "direct main" description: name: source_span sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted version: "1.2.2" test: dependency: "direct dev" description: name: test sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" url: "https://pub.dev" source: hosted version: "1.26.3" test_api: dependency: transitive description: name: test_api sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted version: "0.7.7" test_core: dependency: transitive description: name: test_core sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" url: "https://pub.dev" source: hosted version: "0.6.12" toml: dependency: "direct main" description: name: toml sha256: "157c5dca5160fced243f3ce984117f729c788bb5e475504f3dbcda881accee44" url: "https://pub.dev" source: hosted version: "0.14.0" typed_data: dependency: transitive description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted version: "1.4.0" version: dependency: "direct main" description: name: version sha256: "2307e23a45b43f96469eeab946208ed63293e8afca9c28cd8b5241ff31c55f55" url: "https://pub.dev" source: hosted version: "3.0.0" vm_service: dependency: transitive description: name: vm_service sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted version: "15.0.2" watcher: dependency: transitive description: name: watcher sha256: f52385d4f73589977c80797e60fe51014f7f2b957b5e9a62c3f6ada439889249 url: "https://pub.dev" source: hosted version: "1.2.0" web: dependency: transitive description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted version: "1.1.1" web_socket: dependency: transitive description: name: web_socket sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 url: "https://pub.dev" source: hosted version: "3.0.3" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" url: "https://pub.dev" source: hosted version: "1.2.1" yaml: dependency: "direct main" description: name: yaml sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted version: "3.1.2" sdks: dart: ">=3.7.0-0 <4.0.0" ================================================ FILE: simple_live_app/rust_builder/cargokit/build_tool/pubspec.yaml ================================================ # This is copied from Cargokit (which is the official way to use it currently) # Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin name: build_tool description: Cargokit build_tool. Facilitates the build of Rust crate during Flutter application build. publish_to: none version: 1.0.0 environment: sdk: ">=3.0.0 <4.0.0" # Add regular dependencies here. dependencies: # these are pinned on purpose because the bundle_tool_runner doesn't have # pubspec.lock. See run_build_tool.sh logging: 1.2.0 path: 1.8.0 version: 3.0.0 collection: 1.18.0 ed25519_edwards: 0.3.1 hex: 0.2.0 yaml: 3.1.2 source_span: 1.10.0 github: 9.17.0 args: 2.4.2 crypto: 3.0.3 convert: 3.1.1 http: 1.1.0 toml: 0.14.0 dev_dependencies: lints: ^2.1.0 test: ^1.24.0 ================================================ FILE: simple_live_app/rust_builder/cargokit/cmake/cargokit.cmake ================================================ SET(cargokit_cmake_root "${CMAKE_CURRENT_LIST_DIR}/..") # Workaround for https://github.com/dart-lang/pub/issues/4010 get_filename_component(cargokit_cmake_root "${cargokit_cmake_root}" REALPATH) if(WIN32) # REALPATH does not properly resolve symlinks on windows :-/ execute_process(COMMAND powershell -ExecutionPolicy Bypass -File "${CMAKE_CURRENT_LIST_DIR}/resolve_symlinks.ps1" "${cargokit_cmake_root}" OUTPUT_VARIABLE cargokit_cmake_root OUTPUT_STRIP_TRAILING_WHITESPACE) endif() # Arguments # - target: CMAKE target to which rust library is linked # - manifest_dir: relative path from current folder to directory containing cargo manifest # - lib_name: cargo package name # - any_symbol_name: name of any exported symbol from the library. # used on windows to force linking with library. function(apply_cargokit target manifest_dir lib_name any_symbol_name) set(CARGOKIT_LIB_NAME "${lib_name}") set(CARGOKIT_LIB_FULL_NAME "${CMAKE_SHARED_MODULE_PREFIX}${CARGOKIT_LIB_NAME}${CMAKE_SHARED_MODULE_SUFFIX}") if (CMAKE_CONFIGURATION_TYPES) set(CARGOKIT_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/$") set(OUTPUT_LIB "${CMAKE_CURRENT_BINARY_DIR}/$/${CARGOKIT_LIB_FULL_NAME}") else() set(CARGOKIT_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}") set(OUTPUT_LIB "${CMAKE_CURRENT_BINARY_DIR}/${CARGOKIT_LIB_FULL_NAME}") endif() set(CARGOKIT_TEMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/cargokit_build") if (FLUTTER_TARGET_PLATFORM) set(CARGOKIT_TARGET_PLATFORM "${FLUTTER_TARGET_PLATFORM}") else() set(CARGOKIT_TARGET_PLATFORM "windows-x64") endif() set(CARGOKIT_ENV "CARGOKIT_CMAKE=${CMAKE_COMMAND}" "CARGOKIT_CONFIGURATION=$" "CARGOKIT_MANIFEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/${manifest_dir}" "CARGOKIT_TARGET_TEMP_DIR=${CARGOKIT_TEMP_DIR}" "CARGOKIT_OUTPUT_DIR=${CARGOKIT_OUTPUT_DIR}" "CARGOKIT_TARGET_PLATFORM=${CARGOKIT_TARGET_PLATFORM}" "CARGOKIT_TOOL_TEMP_DIR=${CARGOKIT_TEMP_DIR}/tool" "CARGOKIT_ROOT_PROJECT_DIR=${CMAKE_SOURCE_DIR}" ) if (WIN32) set(SCRIPT_EXTENSION ".cmd") set(IMPORT_LIB_EXTENSION ".lib") else() set(SCRIPT_EXTENSION ".sh") set(IMPORT_LIB_EXTENSION "") execute_process(COMMAND chmod +x "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}") endif() # Using generators in custom command is only supported in CMake 3.20+ if (CMAKE_CONFIGURATION_TYPES AND ${CMAKE_VERSION} VERSION_LESS "3.20.0") foreach(CONFIG IN LISTS CMAKE_CONFIGURATION_TYPES) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${CONFIG}/${CARGOKIT_LIB_FULL_NAME}" "${CMAKE_CURRENT_BINARY_DIR}/_phony_" COMMAND ${CMAKE_COMMAND} -E env ${CARGOKIT_ENV} "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}" build-cmake VERBATIM ) endforeach() else() add_custom_command( OUTPUT ${OUTPUT_LIB} "${CMAKE_CURRENT_BINARY_DIR}/_phony_" COMMAND ${CMAKE_COMMAND} -E env ${CARGOKIT_ENV} "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}" build-cmake VERBATIM ) endif() set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/_phony_" PROPERTIES SYMBOLIC TRUE) if (TARGET ${target}) # If we have actual cmake target provided create target and make existing # target depend on it add_custom_target("${target}_cargokit" DEPENDS ${OUTPUT_LIB}) add_dependencies("${target}" "${target}_cargokit") target_link_libraries("${target}" PRIVATE "${OUTPUT_LIB}${IMPORT_LIB_EXTENSION}") if(WIN32) target_link_options(${target} PRIVATE "/INCLUDE:${any_symbol_name}") endif() else() # Otherwise (FFI) just use ALL to force building always add_custom_target("${target}_cargokit" ALL DEPENDS ${OUTPUT_LIB}) endif() # Allow adding the output library to plugin bundled libraries set("${target}_cargokit_lib" ${OUTPUT_LIB} PARENT_SCOPE) endfunction() ================================================ FILE: simple_live_app/rust_builder/cargokit/cmake/resolve_symlinks.ps1 ================================================ function Resolve-Symlinks { [CmdletBinding()] [OutputType([string])] param( [Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [string] $Path ) [string] $separator = '/' [string[]] $parts = $Path.Split($separator) [string] $realPath = '' foreach ($part in $parts) { if ($realPath -and !$realPath.EndsWith($separator)) { $realPath += $separator } $realPath += $part $item = Get-Item $realPath if ($item.Target) { $realPath = $item.Target.Replace('\', '/') } } $realPath } $path=Resolve-Symlinks -Path $args[0] Write-Host $path ================================================ FILE: simple_live_app/rust_builder/cargokit/gradle/plugin.gradle ================================================ /// This is copied from Cargokit (which is the official way to use it currently) /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin import java.nio.file.Paths import org.apache.tools.ant.taskdefs.condition.Os CargoKitPlugin.file = buildscript.sourceFile apply plugin: CargoKitPlugin class CargoKitExtension { String manifestDir; // Relative path to folder containing Cargo.toml String libname; // Library name within Cargo.toml. Must be a cdylib } abstract class CargoKitBuildTask extends DefaultTask { @Input String buildMode @Input String buildDir @Input String outputDir @Input String ndkVersion @Input String sdkDirectory @Input int compileSdkVersion; @Input int minSdkVersion; @Input String pluginFile @Input List targetPlatforms @TaskAction def build() { if (project.cargokit.manifestDir == null) { throw new GradleException("Property 'manifestDir' must be set on cargokit extension"); } if (project.cargokit.libname == null) { throw new GradleException("Property 'libname' must be set on cargokit extension"); } def executableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "run_build_tool.cmd" : "run_build_tool.sh" def path = Paths.get(new File(pluginFile).parent, "..", executableName); def manifestDir = Paths.get(project.buildscript.sourceFile.parent, project.cargokit.manifestDir) def rootProjectDir = project.rootProject.projectDir if (!Os.isFamily(Os.FAMILY_WINDOWS)) { project.exec { commandLine 'chmod', '+x', path } } project.exec { executable path args "build-gradle" environment "CARGOKIT_ROOT_PROJECT_DIR", rootProjectDir environment "CARGOKIT_TOOL_TEMP_DIR", "${buildDir}/build_tool" environment "CARGOKIT_MANIFEST_DIR", manifestDir environment "CARGOKIT_CONFIGURATION", buildMode environment "CARGOKIT_TARGET_TEMP_DIR", buildDir environment "CARGOKIT_OUTPUT_DIR", outputDir environment "CARGOKIT_NDK_VERSION", ndkVersion environment "CARGOKIT_SDK_DIR", sdkDirectory environment "CARGOKIT_COMPILE_SDK_VERSION", compileSdkVersion environment "CARGOKIT_MIN_SDK_VERSION", minSdkVersion environment "CARGOKIT_TARGET_PLATFORMS", targetPlatforms.join(",") environment "CARGOKIT_JAVA_HOME", System.properties['java.home'] } } } class CargoKitPlugin implements Plugin { static String file; private Plugin findFlutterPlugin(Project rootProject) { _findFlutterPlugin(rootProject.childProjects) } private Plugin _findFlutterPlugin(Map projects) { for (project in projects) { for (plugin in project.value.getPlugins()) { if (plugin.class.name == "com.flutter.gradle.FlutterPlugin") { return plugin; } } def plugin = _findFlutterPlugin(project.value.childProjects); if (plugin != null) { return plugin; } } return null; } @Override void apply(Project project) { def plugin = findFlutterPlugin(project.rootProject); project.extensions.create("cargokit", CargoKitExtension) if (plugin == null) { print("Flutter plugin not found, CargoKit plugin will not be applied.") return; } def cargoBuildDir = "${project.buildDir}/build" // Determine if the project is an application or library def isApplication = plugin.project.plugins.hasPlugin('com.android.application') def variants = isApplication ? plugin.project.android.applicationVariants : plugin.project.android.libraryVariants variants.all { variant -> final buildType = variant.buildType.name def cargoOutputDir = "${project.buildDir}/jniLibs/${buildType}"; def jniLibs = project.android.sourceSets.maybeCreate(buildType).jniLibs; jniLibs.srcDir(new File(cargoOutputDir)) def platforms = com.flutter.gradle.FlutterPluginUtils.getTargetPlatforms(project).collect() // Same thing addFlutterDependencies does in flutter.gradle if (buildType == "debug") { platforms.add("android-x86") platforms.add("android-x64") } // The task name depends on plugin properties, which are not available // at this point project.getGradle().afterProject { def taskName = "cargokitCargoBuild${project.cargokit.libname.capitalize()}${buildType.capitalize()}"; if (project.tasks.findByName(taskName)) { return } if (plugin.project.android.ndkVersion == null) { throw new GradleException("Please set 'android.ndkVersion' in 'app/build.gradle'.") } def task = project.tasks.create(taskName, CargoKitBuildTask.class) { buildMode = variant.buildType.name buildDir = cargoBuildDir outputDir = cargoOutputDir ndkVersion = plugin.project.android.ndkVersion sdkDirectory = plugin.project.android.sdkDirectory minSdkVersion = plugin.project.android.defaultConfig.minSdkVersion.apiLevel as int compileSdkVersion = plugin.project.android.compileSdkVersion.substring(8) as int targetPlatforms = platforms pluginFile = CargoKitPlugin.file } def onTask = { newTask -> if (newTask.name == "merge${buildType.capitalize()}NativeLibs") { newTask.dependsOn task // Fix gradle 7.4.2 not picking up JNI library changes newTask.outputs.upToDateWhen { false } } } project.tasks.each onTask project.tasks.whenTaskAdded onTask } } } } ================================================ FILE: simple_live_app/rust_builder/cargokit/run_build_tool.cmd ================================================ @echo off setlocal setlocal ENABLEDELAYEDEXPANSION SET BASEDIR=%~dp0 if not exist "%CARGOKIT_TOOL_TEMP_DIR%" ( mkdir "%CARGOKIT_TOOL_TEMP_DIR%" ) cd /D "%CARGOKIT_TOOL_TEMP_DIR%" SET BUILD_TOOL_PKG_DIR=%BASEDIR%build_tool SET DART=%FLUTTER_ROOT%\bin\cache\dart-sdk\bin\dart set BUILD_TOOL_PKG_DIR_POSIX=%BUILD_TOOL_PKG_DIR:\=/% ( echo name: build_tool_runner echo version: 1.0.0 echo publish_to: none echo. echo environment: echo sdk: '^>=3.0.0 ^<4.0.0' echo. echo dependencies: echo build_tool: echo path: %BUILD_TOOL_PKG_DIR_POSIX% ) >pubspec.yaml if not exist bin ( mkdir bin ) ( echo import 'package:build_tool/build_tool.dart' as build_tool; echo void main^(List^ args^) ^{ echo build_tool.runMain^(args^); echo ^} ) >bin\build_tool_runner.dart SET PRECOMPILED=bin\build_tool_runner.dill REM To detect changes in package we compare output of DIR /s (recursive) set PREV_PACKAGE_INFO=.dart_tool\package_info.prev set CUR_PACKAGE_INFO=.dart_tool\package_info.cur DIR "%BUILD_TOOL_PKG_DIR%" /s > "%CUR_PACKAGE_INFO%_orig" REM Last line in dir output is free space on harddrive. That is bound to REM change between invocation so we need to remove it ( Set "Line=" For /F "UseBackQ Delims=" %%A In ("%CUR_PACKAGE_INFO%_orig") Do ( SetLocal EnableDelayedExpansion If Defined Line Echo !Line! EndLocal Set "Line=%%A") ) >"%CUR_PACKAGE_INFO%" DEL "%CUR_PACKAGE_INFO%_orig" REM Compare current directory listing with previous FC /B "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%" > nul 2>&1 If %ERRORLEVEL% neq 0 ( REM Changed - copy current to previous and remove precompiled kernel if exist "%PREV_PACKAGE_INFO%" ( DEL "%PREV_PACKAGE_INFO%" ) MOVE /Y "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%" if exist "%PRECOMPILED%" ( DEL "%PRECOMPILED%" ) ) REM There is no CUR_PACKAGE_INFO it was renamed in previous step to %PREV_PACKAGE_INFO% REM which means we need to do pub get and precompile if not exist "%PRECOMPILED%" ( echo Running pub get in "%cd%" "%DART%" pub get --no-precompile "%DART%" compile kernel bin/build_tool_runner.dart ) "%DART%" "%PRECOMPILED%" %* REM 253 means invalid snapshot version. If %ERRORLEVEL% equ 253 ( "%DART%" pub get --no-precompile "%DART%" compile kernel bin/build_tool_runner.dart "%DART%" "%PRECOMPILED%" %* ) ================================================ FILE: simple_live_app/rust_builder/cargokit/run_build_tool.sh ================================================ #!/usr/bin/env bash set -e BASEDIR=$(dirname "$0") mkdir -p "$CARGOKIT_TOOL_TEMP_DIR" cd "$CARGOKIT_TOOL_TEMP_DIR" # Write a very simple bin package in temp folder that depends on build_tool package # from Cargokit. This is done to ensure that we don't pollute Cargokit folder # with .dart_tool contents. BUILD_TOOL_PKG_DIR="$BASEDIR/build_tool" if [[ -z $FLUTTER_ROOT ]]; then # not defined DART=dart else DART="$FLUTTER_ROOT/bin/cache/dart-sdk/bin/dart" fi cat << EOF > "pubspec.yaml" name: build_tool_runner version: 1.0.0 publish_to: none environment: sdk: '>=3.0.0 <4.0.0' dependencies: build_tool: path: "$BUILD_TOOL_PKG_DIR" EOF mkdir -p "bin" cat << EOF > "bin/build_tool_runner.dart" import 'package:build_tool/build_tool.dart' as build_tool; void main(List args) { build_tool.runMain(args); } EOF # Create alias for `shasum` if it does not exist and `sha1sum` exists if ! [ -x "$(command -v shasum)" ] && [ -x "$(command -v sha1sum)" ]; then shopt -s expand_aliases alias shasum="sha1sum" fi # Dart run will not cache any package that has a path dependency, which # is the case for our build_tool_runner. So instead we precompile the package # ourselves. # To invalidate the cached kernel we use the hash of ls -LR of the build_tool # package directory. This should be good enough, as the build_tool package # itself is not meant to have any path dependencies. if [[ "$OSTYPE" == "darwin"* ]]; then PACKAGE_HASH=$(ls -lTR "$BUILD_TOOL_PKG_DIR" | shasum) else PACKAGE_HASH=$(ls -lR --full-time "$BUILD_TOOL_PKG_DIR" | shasum) fi PACKAGE_HASH_FILE=".package_hash" if [ -f "$PACKAGE_HASH_FILE" ]; then EXISTING_HASH=$(cat "$PACKAGE_HASH_FILE") if [ "$PACKAGE_HASH" != "$EXISTING_HASH" ]; then rm "$PACKAGE_HASH_FILE" fi fi # Run pub get if needed. if [ ! -f "$PACKAGE_HASH_FILE" ]; then "$DART" pub get --no-precompile "$DART" compile kernel bin/build_tool_runner.dart echo "$PACKAGE_HASH" > "$PACKAGE_HASH_FILE" fi # Rebuild the tool if it was deleted by Android Studio if [ ! -f "bin/build_tool_runner.dill" ]; then "$DART" compile kernel bin/build_tool_runner.dart fi set +e "$DART" bin/build_tool_runner.dill "$@" exit_code=$? # 253 means invalid snapshot version. if [ $exit_code == 253 ]; then "$DART" pub get --no-precompile "$DART" compile kernel bin/build_tool_runner.dart "$DART" bin/build_tool_runner.dill "$@" exit_code=$? fi exit $exit_code ================================================ FILE: simple_live_app/rust_builder/ios/Classes/dummy_file.c ================================================ // This is an empty file to force CocoaPods to create a framework. ================================================ FILE: simple_live_app/rust_builder/ios/rust_lib_simple_live_app.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint rust_lib_simple_live_app.podspec` to validate before publishing. # Pod::Spec.new do |s| s.name = 'rust_lib_simple_live_app' s.version = '0.0.1' s.summary = 'A new Flutter FFI plugin project.' s.description = <<-DESC A new Flutter FFI plugin project. DESC s.homepage = 'http://example.com' s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } # This will ensure the source files in Classes/ are included in the native # builds of apps using this FFI plugin. Podspec does not support relative # paths, so Classes contains a forwarder C file that relatively imports # `../src/*` so that the C sources can be shared among all target platforms. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' s.platform = :ios, '11.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } s.swift_version = '5.0' s.script_phase = { :name => 'Build Rust library', # First argument is relative path to the `rust` folder, second is name of rust library :script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../../rust rust_lib_simple_live_app', :execution_position => :before_compile, :input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'], # Let XCode know that the static library referenced in -force_load below is # created by this build step. :output_files => ["${BUILT_PRODUCTS_DIR}/librust_lib_simple_live_app.a"], } s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', # Flutter.framework does not contain a i386 slice. 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', 'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/librust_lib_simple_live_app.a', } end ================================================ FILE: simple_live_app/rust_builder/linux/CMakeLists.txt ================================================ # The Flutter tooling requires that developers have CMake 3.10 or later # installed. You should not increase this version, as doing so will cause # the plugin to fail to compile for some customers of the plugin. cmake_minimum_required(VERSION 3.10) # Project-level configuration. set(PROJECT_NAME "rust_lib_simple_live_app") project(${PROJECT_NAME} LANGUAGES CXX) include("../cargokit/cmake/cargokit.cmake") apply_cargokit(${PROJECT_NAME} ../../rust rust_lib_simple_live_app "") # List of absolute paths to libraries that should be bundled with the plugin. # This list could contain prebuilt libraries, or libraries created by an # external build triggered from this build file. set(rust_lib_simple_live_app_bundled_libraries "${${PROJECT_NAME}_cargokit_lib}" PARENT_SCOPE ) ================================================ FILE: simple_live_app/rust_builder/macos/Classes/dummy_file.c ================================================ // This is an empty file to force CocoaPods to create a framework. ================================================ FILE: simple_live_app/rust_builder/macos/rust_lib_simple_live_app.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint rust_lib_simple_live_app.podspec` to validate before publishing. # Pod::Spec.new do |s| s.name = 'rust_lib_simple_live_app' s.version = '0.0.1' s.summary = 'A new Flutter FFI plugin project.' s.description = <<-DESC A new Flutter FFI plugin project. DESC s.homepage = 'http://example.com' s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } # This will ensure the source files in Classes/ are included in the native # builds of apps using this FFI plugin. Podspec does not support relative # paths, so Classes contains a forwarder C file that relatively imports # `../src/*` so that the C sources can be shared among all target platforms. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' s.script_phase = { :name => 'Build Rust library', # First argument is relative path to the `rust` folder, second is name of rust library :script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../../rust rust_lib_simple_live_app', :execution_position => :before_compile, :input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'], # Let XCode know that the static library referenced in -force_load below is # created by this build step. :output_files => ["${BUILT_PRODUCTS_DIR}/librust_lib_simple_live_app.a"], } s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', # Flutter.framework does not contain a i386 slice. 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', 'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/librust_lib_simple_live_app.a', } end ================================================ FILE: simple_live_app/rust_builder/pubspec.yaml ================================================ name: rust_lib_simple_live_app description: "Utility to build Rust code" version: 0.0.1 publish_to: none environment: sdk: '>=3.3.0 <4.0.0' flutter: '>=3.3.0' dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.0.2 dev_dependencies: ffi: ^2.0.2 ffigen: ^11.0.0 flutter_test: sdk: flutter flutter_lints: ^2.0.0 flutter: plugin: platforms: android: ffiPlugin: true ios: ffiPlugin: true linux: ffiPlugin: true macos: ffiPlugin: true windows: ffiPlugin: true ================================================ FILE: simple_live_app/rust_builder/windows/.gitignore ================================================ flutter/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: simple_live_app/rust_builder/windows/CMakeLists.txt ================================================ # The Flutter tooling requires that developers have a version of Visual Studio # installed that includes CMake 3.14 or later. You should not increase this # version, as doing so will cause the plugin to fail to compile for some # customers of the plugin. cmake_minimum_required(VERSION 3.14) # Project-level configuration. set(PROJECT_NAME "rust_lib_simple_live_app") project(${PROJECT_NAME} LANGUAGES CXX) include("../cargokit/cmake/cargokit.cmake") apply_cargokit(${PROJECT_NAME} ../../../../../../rust rust_lib_simple_live_app "") # List of absolute paths to libraries that should be bundled with the plugin. # This list could contain prebuilt libraries, or libraries created by an # external build triggered from this build file. set(rust_lib_simple_live_app_bundled_libraries "${${PROJECT_NAME}_cargokit_lib}" PARENT_SCOPE ) ================================================ FILE: simple_live_app/shorebird.yaml ================================================ # This file is used to configure the Shorebird updater used by your app. # Learn more at https://docs.shorebird.dev # This file does not contain any sensitive information and should be checked into version control. # Your app_id is the unique identifier assigned to your app. # It is used to identify your app when requesting patches from Shorebird's servers. # It is not a secret and can be shared publicly. app_id: b5d433ae-4762-463d-88b0-b6e18efc5891 # auto_update controls if Shorebird should automatically update in the background on launch. # If auto_update: false, you will need to use package:shorebird_code_push to trigger updates. # https://pub.dev/packages/shorebird_code_push # Uncomment the following line to disable automatic updates. # auto_update: false ================================================ FILE: simple_live_app/test/tool_test.dart ================================================ import 'package:flutter_test/flutter_test.dart'; import 'package:pinyin/pinyin.dart'; void testPinyin(){ test("测试拼音", (){ var str = "zzz你好啊"; var res = PinyinHelper.getShortPinyin(str); print(res); }); } void main(){ testPinyin(); } ================================================ FILE: simple_live_app/test/widget_test.dart ================================================ // This is a basic Flutter widget test. // // To perform an interaction with a widget in your test, use the WidgetTester // utility in the flutter_test package. For example, you can send tap and scroll // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:simple_live_app/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(const MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); // Tap the '+' icon and trigger a frame. await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); } ================================================ FILE: simple_live_app/test_driver/integration_test.dart ================================================ import 'package:integration_test/integration_test_driver.dart'; Future main() => integrationDriver(); ================================================ FILE: simple_live_app/windows/.gitignore ================================================ flutter/ephemeral/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: simple_live_app/windows/CMakeLists.txt ================================================ # Project-level configuration. cmake_minimum_required(VERSION 3.14) project(simple_live_app LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. set(BINARY_NAME "slive") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(SET CMP0063 NEW) # Define build configuration option. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() # Define settings for the Profile build mode. set(CMAKE_POLICY_VERSION_MINIMUM 3.15) set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. # # Be cautious about adding new options here, as plugins use this function by # default. In most cases, you should add new options to specific targets instead # of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() # Flutter library and tool build rules. set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build; see runner/CMakeLists.txt. add_subdirectory("runner") # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) ================================================ FILE: simple_live_app/windows/flutter/CMakeLists.txt ================================================ # This file controls Flutter-level build steps. It should not be edited. cmake_minimum_required(VERSION 3.14) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # Set fallback configurations for older versions of the flutter tool. if (NOT DEFINED FLUTTER_TARGET_PLATFORM) set(FLUTTER_TARGET_PLATFORM "windows-x64") endif() # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" "flutter_texture_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: simple_live_app/windows/flutter/generated_plugin_registrant.cc ================================================ // // Generated file. Do not edit. // // clang-format off #include "generated_plugin_registrant.h" #include #include #include #include #include #include #include #include #include #include #include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); DynamicColorPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); FirebaseCorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); FlutterQjsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterQjsPlugin")); MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); MediaKitVideoPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi")); ScreenBrightnessWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); VolumeControllerPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("VolumeControllerPluginCApi")); WindowManagerPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("WindowManagerPlugin")); } ================================================ FILE: simple_live_app/windows/flutter/generated_plugin_registrant.h ================================================ // // Generated file. Do not edit. // // clang-format off #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ #include // Registers Flutter plugins. void RegisterPlugins(flutter::PluginRegistry* registry); #endif // GENERATED_PLUGIN_REGISTRANT_ ================================================ FILE: simple_live_app/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST connectivity_plus dynamic_color firebase_core flutter_inappwebview_windows flutter_qjs media_kit_libs_windows_video media_kit_video screen_brightness_windows screen_retriever_windows share_plus url_launcher_windows volume_controller window_manager ) list(APPEND FLUTTER_FFI_PLUGIN_LIST rust_lib_simple_live_app ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: simple_live_app/windows/packaging/exe/ChineseSimplified.isl ================================================ ; *** Inno Setup version 6.4.0+ Chinese Simplified messages *** ; ; To download user-contributed translations of this file, go to: ; https://jrsoftware.org/files/istrans/ ; ; Note: When translating this text, do not add periods (.) to the end of ; messages that didn't have them already, because on those messages Inno ; Setup adds the periods automatically (appending a period would result in ; two periods being displayed). ; ; Maintained by Zhenghan Yang ; Email: 847320916@QQ.com ; Translation based on network resource ; The latest Translation is on https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation ; [LangOptions] ; The following three entries are very important. Be sure to read and ; understand the '[LangOptions] section' topic in the help file. LanguageName=简体中文 ; If Language Name display incorrect, uncomment next line ; LanguageName=<7B80><4F53><4E2D><6587> ; About LanguageID, to reference link: ; https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c LanguageID=$0804 ; About CodePage, to reference link: ; https://docs.microsoft.com/en-us/windows/win32/intl/code-page-identifiers LanguageCodePage=936 ; If the language you are translating to requires special font faces or ; sizes, uncomment any of the following entries and change them accordingly. ;DialogFontName= ;DialogFontSize=8 ;WelcomeFontName=Verdana ;WelcomeFontSize=12 ;TitleFontName=Arial ;TitleFontSize=29 ;CopyrightFontName=Arial ;CopyrightFontSize=8 [Messages] ; *** 应用程序标题 SetupAppTitle=安装 SetupWindowTitle=安装 - %1 UninstallAppTitle=卸载 UninstallAppFullTitle=%1 卸载 ; *** Misc. common InformationTitle=信息 ConfirmTitle=确认 ErrorTitle=错误 ; *** SetupLdr messages SetupLdrStartupMessage=现在将安装 %1。您想要继续吗? LdrCannotCreateTemp=无法创建临时文件。安装程序已中止 LdrCannotExecTemp=无法执行临时目录中的文件。安装程序已中止 HelpTextNote= ; *** 启动错误消息 LastErrorMessage=%1。%n%n错误 %2: %3 SetupFileMissing=安装目录中缺少文件 %1。请修正这个问题或者获取程序的新副本。 SetupFileCorrupt=安装文件已损坏。请获取程序的新副本。 SetupFileCorruptOrWrongVer=安装文件已损坏,或是与这个安装程序的版本不兼容。请修正这个问题或获取新的程序副本。 InvalidParameter=无效的命令行参数:%n%n%1 SetupAlreadyRunning=安装程序正在运行。 WindowsVersionNotSupported=此程序不支持当前计算机运行的 Windows 版本。 WindowsServicePackRequired=此程序需要 %1 服务包 %2 或更高版本。 NotOnThisPlatform=此程序不能在 %1 上运行。 OnlyOnThisPlatform=此程序只能在 %1 上运行。 OnlyOnTheseArchitectures=此程序只能安装到为下列处理器架构设计的 Windows 版本中:%n%n%1 WinVersionTooLowError=此程序需要 %1 版本 %2 或更高。 WinVersionTooHighError=此程序不能安装于 %1 版本 %2 或更高。 AdminPrivilegesRequired=在安装此程序时您必须以管理员身份登录。 PowerUserPrivilegesRequired=在安装此程序时您必须以管理员身份或有权限的用户组身份登录。 SetupAppRunningError=安装程序发现 %1 当前正在运行。%n%n请先关闭正在运行的程序,然后点击“确定”继续,或点击“取消”退出。 UninstallAppRunningError=卸载程序发现 %1 当前正在运行。%n%n请先关闭正在运行的程序,然后点击“确定”继续,或点击“取消”退出。 ; *** 启动问题 PrivilegesRequiredOverrideTitle=选择安装程序模式 PrivilegesRequiredOverrideInstruction=选择安装模式 PrivilegesRequiredOverrideText1=%1 可以为所有用户安装(需要管理员权限),或仅为您安装。 PrivilegesRequiredOverrideText2=%1 只能为您安装,或为所有用户安装(需要管理员权限)。 PrivilegesRequiredOverrideAllUsers=为所有用户安装(&A) PrivilegesRequiredOverrideAllUsersRecommended=为所有用户安装(&A) (建议选项) PrivilegesRequiredOverrideCurrentUser=只为我安装(&M) PrivilegesRequiredOverrideCurrentUserRecommended=只为我安装(&M) (建议选项) ; *** 其他错误 ErrorCreatingDir=安装程序无法创建目录“%1” ErrorTooManyFilesInDir=无法在目录“%1”中创建文件,因为里面包含太多文件 ; *** 安装程序公共消息 ExitSetupTitle=退出安装程序 ExitSetupMessage=安装程序尚未完成。如果现在退出,将不会安装该程序。%n%n您之后可以再次运行安装程序完成安装。%n%n现在退出安装程序吗? AboutSetupMenuItem=关于安装程序(&A)... AboutSetupTitle=关于安装程序 AboutSetupMessage=%1 版本 %2%n%3%n%n%1 主页:%n%4 AboutSetupNote= TranslatorNote=简体中文翻译由Kira(847320916@qq.com)维护。项目地址:https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation ; *** 按钮 ButtonBack=< 上一步(&B) ButtonNext=下一步(&N) > ButtonInstall=安装(&I) ButtonOK=确定 ButtonCancel=取消 ButtonYes=是(&Y) ButtonYesToAll=全是(&A) ButtonNo=否(&N) ButtonNoToAll=全否(&O) ButtonFinish=完成(&F) ButtonBrowse=浏览(&B)... ButtonWizardBrowse=浏览(&R)... ButtonNewFolder=新建文件夹(&M) ; *** “选择语言”对话框消息 SelectLanguageTitle=选择安装语言 SelectLanguageLabel=选择安装时使用的语言。 ; *** 公共向导文字 ClickNext=点击“下一步”继续,或点击“取消”退出安装程序。 BeveledLabel= BrowseDialogTitle=浏览文件夹 BrowseDialogLabel=在下面的列表中选择一个文件夹,然后点击“确定”。 NewFolderName=新建文件夹 ; *** “欢迎”向导页 WelcomeLabel1=欢迎使用 [name] 安装向导 WelcomeLabel2=现在将安装 [name/ver] 到您的电脑中。%n%n建议您在继续安装前关闭所有其他应用程序。 ; *** “密码”向导页 WizardPassword=密码 PasswordLabel1=这个安装程序有密码保护。 PasswordLabel3=请输入密码,然后点击“下一步”继续。密码区分大小写。 PasswordEditLabel=密码(&P): IncorrectPassword=您输入的密码不正确,请重新输入。 ; *** “许可协议”向导页 WizardLicense=许可协议 LicenseLabel=请在继续安装前阅读以下重要信息。 LicenseLabel3=请仔细阅读下列许可协议。在继续安装前您必须同意这些协议条款。 LicenseAccepted=我同意此协议(&A) LicenseNotAccepted=我不同意此协议(&D) ; *** “信息”向导页 WizardInfoBefore=信息 InfoBeforeLabel=请在继续安装前阅读以下重要信息。 InfoBeforeClickLabel=准备好继续安装后,点击“下一步”。 WizardInfoAfter=信息 InfoAfterLabel=请在继续安装前阅读以下重要信息。 InfoAfterClickLabel=准备好继续安装后,点击“下一步”。 ; *** “用户信息”向导页 WizardUserInfo=用户信息 UserInfoDesc=请输入您的信息。 UserInfoName=用户名(&U): UserInfoOrg=组织(&O): UserInfoSerial=序列号(&S): UserInfoNameRequired=您必须输入用户名。 ; *** “选择目标目录”向导页 WizardSelectDir=选择目标位置 SelectDirDesc=您想将 [name] 安装在哪里? SelectDirLabel3=安装程序将安装 [name] 到下面的文件夹中。 SelectDirBrowseLabel=点击“下一步”继续。如果您想选择其他文件夹,点击“浏览”。 DiskSpaceGBLabel=至少需要有 [gb] GB 的可用磁盘空间。 DiskSpaceMBLabel=至少需要有 [mb] MB 的可用磁盘空间。 CannotInstallToNetworkDrive=安装程序无法安装到一个网络驱动器。 CannotInstallToUNCPath=安装程序无法安装到一个 UNC 路径。 InvalidPath=您必须输入一个带驱动器卷标的完整路径,例如:%n%nC:\APP%n%n或UNC路径:%n%n\\server\share InvalidDrive=您选定的驱动器或 UNC 共享不存在或不能访问。请选择其他位置。 DiskSpaceWarningTitle=磁盘空间不足 DiskSpaceWarning=安装程序至少需要 %1 KB 的可用空间才能安装,但选定驱动器只有 %2 KB 的可用空间。%n%n您一定要继续吗? DirNameTooLong=文件夹名称或路径太长。 InvalidDirName=文件夹名称无效。 BadDirName32=文件夹名称不能包含下列任何字符:%n%n%1 DirExistsTitle=文件夹已存在 DirExists=文件夹:%n%n%1%n%n已经存在。您一定要安装到这个文件夹中吗? DirDoesntExistTitle=文件夹不存在 DirDoesntExist=文件夹:%n%n%1%n%n不存在。您想要创建此文件夹吗? ; *** “选择组件”向导页 WizardSelectComponents=选择组件 SelectComponentsDesc=您想安装哪些程序组件? SelectComponentsLabel2=选中您想安装的组件;取消您不想安装的组件。然后点击“下一步”继续。 FullInstallation=完全安装 ; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) CompactInstallation=简洁安装 CustomInstallation=自定义安装 NoUninstallWarningTitle=组件已存在 NoUninstallWarning=安装程序检测到下列组件已安装在您的电脑中:%n%n%1%n%n取消选中这些组件不会卸载它们。%n%n确定要继续吗? ComponentSize1=%1 KB ComponentSize2=%1 MB ComponentsDiskSpaceGBLabel=当前选择的组件需要至少 [gb] GB 的磁盘空间。 ComponentsDiskSpaceMBLabel=当前选择的组件需要至少 [mb] MB 的磁盘空间。 ; *** “选择附加任务”向导页 WizardSelectTasks=选择附加任务 SelectTasksDesc=您想要安装程序执行哪些附加任务? SelectTasksLabel2=选择您想要安装程序在安装 [name] 时执行的附加任务,然后点击“下一步”。 ; *** “选择开始菜单文件夹”向导页 WizardSelectProgramGroup=选择开始菜单文件夹 SelectStartMenuFolderDesc=安装程序应该在哪里放置程序的快捷方式? SelectStartMenuFolderLabel3=安装程序将在下列“开始”菜单文件夹中创建程序的快捷方式。 SelectStartMenuFolderBrowseLabel=点击“下一步”继续。如果您想选择其他文件夹,点击“浏览”。 MustEnterGroupName=您必须输入一个文件夹名。 GroupNameTooLong=文件夹名或路径太长。 InvalidGroupName=无效的文件夹名字。 BadGroupName=文件夹名不能包含下列任何字符:%n%n%1 NoProgramGroupCheck2=不创建开始菜单文件夹(&D) ; *** “准备安装”向导页 WizardReady=准备安装 ReadyLabel1=安装程序准备就绪,现在可以开始安装 [name] 到您的电脑。 ReadyLabel2a=点击“安装”继续此安装程序。如果您想重新考虑或修改任何设置,点击“上一步”。 ReadyLabel2b=点击“安装”继续此安装程序。 ReadyMemoUserInfo=用户信息: ReadyMemoDir=目标位置: ReadyMemoType=安装类型: ReadyMemoComponents=已选择组件: ReadyMemoGroup=开始菜单文件夹: ReadyMemoTasks=附加任务: ; *** TExtractionWizardPage wizard page and Extract7ZipArchive ExtractionLabel=正在提取附加文件... ButtonStopExtraction=停止提取(&S) StopExtraction=您确定要停止提取吗? ErrorExtractionAborted=提取已中止 ErrorExtractionFailed=提取失败:%1 ; *** TDownloadWizardPage wizard page and DownloadTemporaryFile DownloadingLabel=正在下载附加文件... ButtonStopDownload=停止下载(&S) StopDownload=您确定要停止下载吗? ErrorDownloadAborted=下载已中止 ErrorDownloadFailed=下载失败:%1 %2 ErrorDownloadSizeFailed=获取下载大小失败:%1 %2 ErrorFileHash1=校验文件哈希失败:%1 ErrorFileHash2=无效的文件哈希:预期 %1,实际 %2 ErrorProgress=无效的进度:%1 / %2 ErrorFileSize=文件大小错误:预期 %1,实际 %2 ; *** “正在准备安装”向导页 WizardPreparing=正在准备安装 PreparingDesc=安装程序正在准备安装 [name] 到您的电脑。 PreviousInstallNotCompleted=先前的程序安装或卸载未完成,您需要重启您的电脑以完成。%n%n在重启电脑后,再次运行安装程序以完成 [name] 的安装。 CannotContinue=安装程序不能继续。请点击“取消”退出。 ApplicationsFound=以下应用程序正在使用将由安装程序更新的文件。建议您允许安装程序自动关闭这些应用程序。 ApplicationsFound2=以下应用程序正在使用将由安装程序更新的文件。建议您允许安装程序自动关闭这些应用程序。安装完成后,安装程序将尝试重新启动这些应用程序。 CloseApplications=自动关闭应用程序(&A) DontCloseApplications=不要关闭应用程序(&D) ErrorCloseApplications=安装程序无法自动关闭所有应用程序。建议您在继续之前,关闭所有在使用需要由安装程序更新的文件的应用程序。 PrepareToInstallNeedsRestart=安装程序必须重启您的计算机。计算机重启后,请再次运行安装程序以完成 [name] 的安装。%n%n是否立即重新启动? ; *** “正在安装”向导页 WizardInstalling=正在安装 InstallingLabel=安装程序正在安装 [name] 到您的电脑,请稍候。 ; *** “安装完成”向导页 FinishedHeadingLabel=[name] 安装完成 FinishedLabelNoIcons=安装程序已在您的电脑中安装了 [name]。 FinishedLabel=安装程序已在您的电脑中安装了 [name]。您可以通过已安装的快捷方式运行此应用程序。 ClickFinish=点击“完成”退出安装程序。 FinishedRestartLabel=为完成 [name] 的安装,安装程序必须重新启动您的电脑。要立即重启吗? FinishedRestartMessage=为完成 [name] 的安装,安装程序必须重新启动您的电脑。%n%n要立即重启吗? ShowReadmeCheck=是,我想查阅自述文件 YesRadio=是,立即重启电脑(&Y) NoRadio=否,稍后重启电脑(&N) ; used for example as 'Run MyProg.exe' RunEntryExec=运行 %1 ; used for example as 'View Readme.txt' RunEntryShellExec=查阅 %1 ; *** “安装程序需要下一张磁盘”提示 ChangeDiskTitle=安装程序需要下一张磁盘 SelectDiskLabel2=请插入磁盘 %1 并点击“确定”。%n%n如果这个磁盘中的文件可以在下列文件夹之外的文件夹中找到,请输入正确的路径或点击“浏览”。 PathLabel=路径(&P): FileNotInDir2=“%2”中找不到文件“%1”。请插入正确的磁盘或选择其他文件夹。 SelectDirectoryLabel=请指定下一张磁盘的位置。 ; *** 安装状态消息 SetupAborted=安装程序未完成安装。%n%n请修正这个问题并重新运行安装程序。 AbortRetryIgnoreSelectAction=选择操作 AbortRetryIgnoreRetry=重试(&T) AbortRetryIgnoreIgnore=忽略错误并继续(&I) AbortRetryIgnoreCancel=关闭安装程序 ; *** 安装状态消息 StatusClosingApplications=正在关闭应用程序... StatusCreateDirs=正在创建目录... StatusExtractFiles=正在解压缩文件... StatusCreateIcons=正在创建快捷方式... StatusCreateIniEntries=正在创建 INI 条目... StatusCreateRegistryEntries=正在创建注册表条目... StatusRegisterFiles=正在注册文件... StatusSavingUninstall=正在保存卸载信息... StatusRunProgram=正在完成安装... StatusRestartingApplications=正在重启应用程序... StatusRollback=正在撤销更改... ; *** 其他错误 ErrorInternal2=内部错误:%1 ErrorFunctionFailedNoCode=%1 失败 ErrorFunctionFailed=%1 失败;错误代码 %2 ErrorFunctionFailedWithMessage=%1 失败;错误代码 %2.%n%3 ErrorExecutingProgram=无法执行文件:%n%1 ; *** 注册表错误 ErrorRegOpenKey=打开注册表项时出错:%n%1\%2 ErrorRegCreateKey=创建注册表项时出错:%n%1\%2 ErrorRegWriteKey=写入注册表项时出错:%n%1\%2 ; *** INI 错误 ErrorIniEntry=在文件“%1”中创建 INI 条目时出错。 ; *** 文件复制错误 FileAbortRetryIgnoreSkipNotRecommended=跳过此文件(&S) (不推荐) FileAbortRetryIgnoreIgnoreNotRecommended=忽略错误并继续(&I) (不推荐) SourceIsCorrupted=源文件已损坏 SourceDoesntExist=源文件“%1”不存在 ExistingFileReadOnly2=无法替换现有文件,它是只读的。 ExistingFileReadOnlyRetry=移除只读属性并重试(&R) ExistingFileReadOnlyKeepExisting=保留现有文件(&K) ErrorReadingExistingDest=尝试读取现有文件时出错: FileExistsSelectAction=选择操作 FileExists2=文件已经存在。 FileExistsOverwriteExisting=覆盖已存在的文件(&O) FileExistsKeepExisting=保留现有的文件(&K) FileExistsOverwriteOrKeepAll=为所有冲突文件执行此操作(&D) ExistingFileNewerSelectAction=选择操作 ExistingFileNewer2=现有的文件比安装程序将要安装的文件还要新。 ExistingFileNewerOverwriteExisting=覆盖已存在的文件(&O) ExistingFileNewerKeepExisting=保留现有的文件(&K) (推荐) ExistingFileNewerOverwriteOrKeepAll=为所有冲突文件执行此操作(&D) ErrorChangingAttr=尝试更改下列现有文件的属性时出错: ErrorCreatingTemp=尝试在目标目录创建文件时出错: ErrorReadingSource=尝试读取下列源文件时出错: ErrorCopying=尝试复制下列文件时出错: ErrorReplacingExistingFile=尝试替换现有文件时出错: ErrorRestartReplace=重启并替换失败: ErrorRenamingTemp=尝试重命名下列目标目录中的一个文件时出错: ErrorRegisterServer=无法注册 DLL/OCX:%1 ErrorRegSvr32Failed=RegSvr32 失败;退出代码 %1 ErrorRegisterTypeLib=无法注册类库:%1 ; *** 卸载显示名字标记 ; used for example as 'My Program (32-bit)' UninstallDisplayNameMark=%1 (%2) ; used for example as 'My Program (32-bit, All users)' UninstallDisplayNameMarks=%1 (%2, %3) UninstallDisplayNameMark32Bit=32 位 UninstallDisplayNameMark64Bit=64 位 UninstallDisplayNameMarkAllUsers=所有用户 UninstallDisplayNameMarkCurrentUser=当前用户 ; *** 安装后错误 ErrorOpeningReadme=尝试打开自述文件时出错。 ErrorRestartingComputer=安装程序无法重启电脑,请手动重启。 ; *** 卸载消息 UninstallNotFound=文件“%1”不存在。无法卸载。 UninstallOpenError=文件“%1”不能被打开。无法卸载。 UninstallUnsupportedVer=此版本的卸载程序无法识别卸载日志文件“%1”的格式。无法卸载 UninstallUnknownEntry=卸载日志中遇到一个未知条目 (%1) ConfirmUninstall=您确认要完全移除 %1 及其所有组件吗? UninstallOnlyOnWin64=仅允许在 64 位 Windows 中卸载此程序。 OnlyAdminCanUninstall=仅使用管理员权限的用户能完成此卸载。 UninstallStatusLabel=正在从您的电脑中移除 %1,请稍候。 UninstalledAll=已顺利从您的电脑中移除 %1。 UninstalledMost=%1 卸载完成。%n%n有部分内容未能被删除,但您可以手动删除它们。 UninstalledAndNeedsRestart=为完成 %1 的卸载,需要重启您的电脑。%n%n立即重启电脑吗? UninstallDataCorrupted=文件“%1”已损坏。无法卸载 ; *** 卸载状态消息 ConfirmDeleteSharedFileTitle=删除共享的文件吗? ConfirmDeleteSharedFile2=系统表示下列共享的文件已不有其他程序使用。您希望卸载程序删除这些共享的文件吗?%n%n如果删除这些文件,但仍有程序在使用这些文件,则这些程序可能出现异常。如果您不能确定,请选择“否”,在系统中保留这些文件以免引发问题。 SharedFileNameLabel=文件名: SharedFileLocationLabel=位置: WizardUninstalling=卸载状态 StatusUninstalling=正在卸载 %1... ; *** Shutdown block reasons ShutdownBlockReasonInstallingApp=正在安装 %1。 ShutdownBlockReasonUninstallingApp=正在卸载 %1。 ; The custom messages below aren't used by Setup itself, but if you make ; use of them in your scripts, you'll want to translate them. [CustomMessages] NameAndVersion=%1 版本 %2 AdditionalIcons=附加快捷方式: CreateDesktopIcon=创建桌面快捷方式(&D) CreateQuickLaunchIcon=创建快速启动栏快捷方式(&Q) ProgramOnTheWeb=%1 网站 UninstallProgram=卸载 %1 LaunchProgram=运行 %1 AssocFileExtension=将 %2 文件扩展名与 %1 建立关联(&A) AssocingFileExtension=正在将 %2 文件扩展名与 %1 建立关联... AutoStartProgramGroupDescription=启动: AutoStartProgram=自动启动 %1 AddonHostProgramNotFound=您选择的文件夹中无法找到 %1。%n%n您要继续吗? ================================================ FILE: simple_live_app/windows/packaging/exe/make_config.yaml ================================================ display_name: Slive app_id: 45F6FA98-DA23-4795-8685-60F607317A1F publisher: slotsun publisher_url: https://github.com/fastforgedev/fastforge create_desktop_icon: true # See: https://jrsoftware.org/ishelp/index.php?topic=setup_defaultdirname install_dir_name: "D:\\Program Files (x86)\\simple_live" # 这里的路径是相对于项目根目录的路径; 图标格式必须是ico格式,不能是png或其它 setup_icon_file: assets/icons/app_icon.ico locales: - en - zh ================================================ FILE: simple_live_app/windows/packaging/msix/make_config.yaml ================================================ display_name: Slive publisher_display_name: slotsun identity_name: com.slotsun.slive logo_path: assets/logo_400.png capabilities: internetClient languages: zh-cn install_certificate: "false" signtool_options: /td SHA256 ================================================ FILE: simple_live_app/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) project(runner LANGUAGES CXX) # Define the application target. To change its name, change BINARY_NAME in the # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer # work. # # Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) # Apply the standard set of build settings. This can be removed for applications # that need different build settings. apply_standard_settings(${BINARY_NAME}) # Add preprocessor definitions for the build version. target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") # Disable Windows macros that collide with C++ standard library functions. target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") # Add dependency libraries and include directories. Add any application-specific # dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: simple_live_app/windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD #else #define VERSION_AS_NUMBER 1,0,0,0 #endif #if defined(FLUTTER_VERSION) #define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.slotsun" "\0" VALUE "FileDescription", "slive" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "slive" "\0" VALUE "LegalCopyright", "Copyright (C) 2023 com.xycz and com.slotsun. All rights reserved." "\0" VALUE "OriginalFilename", "slive.exe" "\0" VALUE "ProductName", "slive" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: simple_live_app/windows/runner/flutter_window.cpp ================================================ #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); flutter_controller_->engine()->SetNextFrameCallback([&]() { this->Show(); }); // Flutter can complete the first frame before the "show window" callback is // registered. The following call ensures a frame is pending to ensure the // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opportunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: simple_live_app/windows/runner/flutter_window.h ================================================ #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow hosting a Flutter view running |project|. explicit FlutterWindow(const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: simple_live_app/windows/runner/main.cpp ================================================ #include #include #include #include "flutter_window.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t *command_line, _In_ int show_command) { HWND hwnd = ::FindWindow(NULL, L"Slive"); if (hwnd != NULL) { ::ShowWindow(hwnd, SW_NORMAL); ::SetForegroundWindow(hwnd); return EXIT_FAILURE; } // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); flutter::DartProject project(L"data"); std::vector command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.Create(L"Slive", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); ::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: simple_live_app/windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: simple_live_app/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: simple_live_app/windows/runner/utils.cpp ================================================ #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE *unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } std::vector GetCommandLineArguments() { // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. int argc; wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); if (argv == nullptr) { return std::vector(); } std::vector command_line_arguments; // Skip the first argument as it's the binary name. for (int i = 1; i < argc; i++) { command_line_arguments.push_back(Utf8FromUtf16(argv[i])); } ::LocalFree(argv); return command_line_arguments; } std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } int target_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, nullptr, 0, nullptr, nullptr) -1; // remove the trailing null character int input_length = (int)wcslen(utf16_string); std::string utf8_string; if (target_length <= 0 || target_length > utf8_string.max_size()) { return utf8_string; } utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, input_length, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } return utf8_string; } ================================================ FILE: simple_live_app/windows/runner/utils.h ================================================ #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ #include #include // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string // encoded in UTF-8. Returns an empty std::string on failure. std::string Utf8FromUtf16(const wchar_t* utf16_string); // Gets the command line arguments passed in as a std::vector, // encoded in UTF-8. Returns an empty std::vector on failure. std::vector GetCommandLineArguments(); #endif // RUNNER_UTILS_H_ ================================================ FILE: simple_live_app/windows/runner/win32_window.cpp ================================================ #include "win32_window.h" #include #include #include "resource.h" namespace { /// Window attribute that enables dark mode window decorations. /// /// Redefined in case the developer's machine has a Windows SDK older than /// version 10.0.22000.0. /// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 #endif constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; /// Registry key for app theme preference. /// /// A value of 0 indicates apps should use dark mode. A non-zero or missing /// value indicates apps should use light mode. constexpr const wchar_t kGetPreferredBrightnessRegKey[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); } FreeLibrary(user32_module); } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registrar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::Create(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } UpdateTheme(window); return OnCreate(); } bool Win32Window::Show() { return ShowWindow(window_handle_, SW_SHOWNORMAL); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: { RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; } case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; case WM_DWMCOLORIZATIONCOLORCHANGED: UpdateTheme(hwnd); return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } void Win32Window::UpdateTheme(HWND const window) { DWORD light_mode; DWORD light_mode_size = sizeof(light_mode); LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, kGetPreferredBrightnessRegValue, RRF_RT_REG_DWORD, nullptr, &light_mode, &light_mode_size); if (result == ERROR_SUCCESS) { BOOL enable_dark_mode = light_mode == 0; DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, &enable_dark_mode, sizeof(enable_dark_mode)); } } ================================================ FILE: simple_live_app/windows/runner/win32_window.h ================================================ #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates a win32 window with |title| that is positioned and sized using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size this function will scale the inputted width and height as // as appropriate for the default monitor. The window is invisible until // |Show| is called. Returns true if the window was created successfully. bool Create(const std::wstring& title, const Point& origin, const Size& size); // Show the current window. Returns true if the window was successfully shown. bool Show(); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; // Update the window frame's theme to match the system theme. static void UpdateTheme(HWND const window); bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_ ================================================ FILE: simple_live_console/.fvmrc ================================================ { "flutter": "3.35.7" } ================================================ FILE: simple_live_console/.gitignore ================================================ # https://dart.dev/guides/libraries/private-files # Created by `dart pub` .dart_tool/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ ================================================ FILE: simple_live_console/CHANGELOG.md ================================================ ## 1.0.0 - Initial version. ================================================ FILE: simple_live_console/README.md ================================================ # simple_live_console 基于simple_live_core的控制台程序。 输入直播间播放直链获取信息及播放地址: ``` simple_live.exe -i [URL] ``` 输入直播间链接获取弹幕: ``` simple_live.exe -d [URL] ``` ================================================ FILE: simple_live_console/analysis_options.yaml ================================================ # This file configures the static analysis results for your project (errors, # warnings, and lints). # # This enables the 'recommended' set of lints from `package:lints`. # This set helps identify many issues that may lead to problems when running # or consuming Dart code, and enforces writing Dart using a single, idiomatic # style and format. # # If you want a smaller set of lints you can change this to specify # 'package:lints/core.yaml'. These are just the most critical lints # (the recommended set includes the core lints). # The core lints are also what is used by pub.dev for scoring packages. include: package:lints/recommended.yaml # Uncomment the following section to specify additional rules. # linter: # rules: # - camel_case_types # analyzer: # exclude: # - path/to/excluded/files/** # For more information about the core and recommended set of lints, see # https://dart.dev/go/core-lints # For additional information about configuring this file, see # https://dart.dev/guides/language/analysis-options ================================================ FILE: simple_live_console/bin/simple_live_console.dart ================================================ import 'dart:io'; import 'package:simple_live_core/simple_live_core.dart'; void main(List arguments) async { CoreLog.enableLog = false; if (arguments.isEmpty) { printHelp(); return; } var action = arguments.first.toLowerCase().replaceAll("-", ""); if (arguments.length < 2) { print("错误的参数"); printHelp(); return; } var url = arguments[1]; if (url.isEmpty) { print("[URL]不能为空"); printHelp(); return; } if (action == "i") { await printInfo(url); } else if (action == "d") { printDanmaku(url); } else if (action == "h") { printHelp(); } else { print("未知指令:$action"); printHelp(); } } void printHelp() { print("-i [URL] :获取直播间信息及播放直链"); print("-d [URL] :持续输出直播间弹幕"); } Future printInfo(String url) async { var urlInfo = parseUrl(url); LiveSite site = urlInfo.first; var id = urlInfo.last; var detail = await site.getRoomDetail(roomId: id); print("来源:${site.name}"); print("房间号:${detail.roomId}"); print("房间标题:${detail.title}"); print("直播用户:${detail.userName}"); print("人气值:${detail.online}"); print("状态:${(detail.status ? "直播中" : "未开播")}"); if (detail.status) { print("可用清晰度:"); var quality = await site.getPlayQualites(detail: detail); for (int i = 0; i < quality.length; i++) { print("【${i + 1}】${quality[i].quality}"); } print("请输入【】内数字,获取对应清晰度的直链"); var input = stdin.readLineSync() ?? ""; print("正在获取直链..."); var index = int.tryParse(input) ?? 0; if (index > 0) { var url = await site.getPlayUrls(detail: detail, quality: quality[index - 1]); for (int i = 0; i < url.urls.length; i++) { print("线路${i + 1}:\r\n${url.urls[i]}"); } } } } Future printDanmaku(String url) async { var urlInfo = parseUrl(url); LiveSite site = urlInfo.first; var id = urlInfo.last; var detail = await site.getRoomDetail(roomId: id); print("来源:${site.name}"); print("房间号:${detail.roomId}"); print("房间标题:${detail.title}"); print("直播用户:${detail.userName}"); print("状态:${(detail.status ? "直播中" : "未开播")}"); var danmaku = site.getDanmaku(); danmaku.onMessage = (LiveMessage e) { if (e.type == LiveMessageType.online) { print("-----人气值:${e.data}-----"); } else if (e.type == LiveMessageType.chat) { print("${e.userName}:${e.message}"); } }; danmaku.onClose = (String e) { print(e); }; print("【开始获取弹幕】"); await danmaku.start(detail.danmakuData); await Future(() {}); } List parseUrl(String url) { if (url.contains("bilibili.com")) { var id = RegExp(r"bilibili\.com/([\d|\w]+)").firstMatch(url)?.group(1) ?? ""; return [BiliBiliSite(), id]; } if (url.contains("huya.com")) { var id = RegExp(r"huya\.com/([\d|\w]+)").firstMatch(url)?.group(1) ?? ""; return [HuyaSite(), id]; } if (url.contains("douyu.com")) { var id = RegExp(r"douyu\.com/([\d|\w]+)").firstMatch(url)?.group(1) ?? ""; return [DouyuSite(), id]; } if (url.contains("live.douyin.com")) { var id = RegExp(r"live\.douyin\.com/([\d|\w]+)").firstMatch(url)?.group(1) ?? ""; return [DouyinSite(), id]; } throw Exception("链接解析失败"); } ================================================ FILE: simple_live_console/pubspec.yaml ================================================ name: simple_live_console description: A sample command-line application. version: 1.0.0 publish_to: "none" # repository: https://github.com/my_org/my_repo environment: sdk: '>=2.19.1 <3.0.0' dependencies: simple_live_core: path: ../simple_live_core dev_dependencies: lints: ^6.0.0 test: ^1.21.0 ================================================ FILE: simple_live_console/test/all_live_console_test.dart ================================================ import 'package:test/test.dart'; void main() { test('calculate', () {}); } ================================================ FILE: simple_live_core/.fvmrc ================================================ { "flutter": "3.38.4" } ================================================ FILE: simple_live_core/.gitattributes ================================================ * text=auto eol=lf ================================================ FILE: simple_live_core/.gitignore ================================================ # https://dart.dev/guides/libraries/private-files # Created by `dart pub` .dart_tool/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ ================================================ FILE: simple_live_core/CHANGELOG.md ================================================ ## 1.0.0 - Initial version. ================================================ FILE: simple_live_core/README.md ================================================ # simple_live_core 项目核心库,实现获取各个网站的信息及弹幕。 ================================================ FILE: simple_live_core/analysis_options.yaml ================================================ # This file configures the static analysis results for your project (errors, # warnings, and lints). # # This enables the 'recommended' set of lints from `package:lints`. # This set helps identify many issues that may lead to problems when running # or consuming Dart code, and enforces writing Dart using a single, idiomatic # style and format. # # If you want a smaller set of lints you can change this to specify # 'package:lints/core.yaml'. These are just the most critical lints # (the recommended set includes the core lints). # The core lints are also what is used by pub.dev for scoring packages. include: package:lints/recommended.yaml # Uncomment the following section to specify additional rules. linter: rules: constant_identifier_names: false # analyzer: # exclude: # - path/to/excluded/files/** # For more information about the core and recommended set of lints, see # https://dart.dev/go/core-lints # For additional information about configuring this file, see # https://dart.dev/guides/language/analysis-options ================================================ FILE: simple_live_core/assets/js/a_bogus.js ================================================ // All the content in this article is only for learning and communication use, not for any other purpose, strictly prohibited for commercial use and illegal use, otherwise all the consequences are irrelevant to the author! function rc4_encrypt(plaintext, key) { var s = []; for (var i = 0; i < 256; i++) { s[i] = i; } var j = 0; for (var i = 0; i < 256; i++) { j = (j + s[i] + key.charCodeAt(i % key.length)) % 256; var temp = s[i]; s[i] = s[j]; s[j] = temp; } var i = 0; var j = 0; var cipher = []; for (var k = 0; k < plaintext.length; k++) { i = (i + 1) % 256; j = (j + s[i]) % 256; var temp = s[i]; s[i] = s[j]; s[j] = temp; var t = (s[i] + s[j]) % 256; cipher.push(String.fromCharCode(s[t] ^ plaintext.charCodeAt(k))); } return cipher.join(''); } function le(e, r) { return (e << (r %= 32) | e >>> 32 - r) >>> 0 } function de(e) { return 0 <= e && e < 16 ? 2043430169 : 16 <= e && e < 64 ? 2055708042 : void console['error']("invalid j for constant Tj") } function pe(e, r, t, n) { return 0 <= e && e < 16 ? (r ^ t ^ n) >>> 0 : 16 <= e && e < 64 ? (r & t | r & n | t & n) >>> 0 : (console['error']('invalid j for bool function FF'), 0) } function he(e, r, t, n) { return 0 <= e && e < 16 ? (r ^ t ^ n) >>> 0 : 16 <= e && e < 64 ? (r & t | ~r & n) >>> 0 : (console['error']('invalid j for bool function GG'), 0) } function reset() { this.reg[0] = 1937774191, this.reg[1] = 1226093241, this.reg[2] = 388252375, this.reg[3] = 3666478592, this.reg[4] = 2842636476, this.reg[5] = 372324522, this.reg[6] = 3817729613, this.reg[7] = 2969243214, this["chunk"] = [], this["size"] = 0 } function write(e) { var a = "string" == typeof e ? function (e) { n = encodeURIComponent(e)['replace'](/%([0-9A-F]{2})/g, (function (e, r) { return String['fromCharCode']("0x" + r) } )) , a = new Array(n['length']); return Array['prototype']['forEach']['call'](n, (function (e, r) { a[r] = e.charCodeAt(0) } )), a }(e) : e; this.size += a.length; var f = 64 - this['chunk']['length']; if (a['length'] < f) this['chunk'] = this['chunk'].concat(a); else for (this['chunk'] = this['chunk'].concat(a.slice(0, f)); this['chunk'].length >= 64;) this['_compress'](this['chunk']), f < a['length'] ? this['chunk'] = a['slice'](f, Math['min'](f + 64, a['length'])) : this['chunk'] = [], f += 64 } function sum(e, t) { e && (this['reset'](), this['write'](e)), this['_fill'](); for (var f = 0; f < this.chunk['length']; f += 64) this._compress(this['chunk']['slice'](f, f + 64)); var i = null; if (t == 'hex') { i = ""; for (f = 0; f < 8; f++) i += se(this['reg'][f]['toString'](16), 8, "0") } else for (i = new Array(32), f = 0; f < 8; f++) { var c = this.reg[f]; i[4 * f + 3] = (255 & c) >>> 0, c >>>= 8, i[4 * f + 2] = (255 & c) >>> 0, c >>>= 8, i[4 * f + 1] = (255 & c) >>> 0, c >>>= 8, i[4 * f] = (255 & c) >>> 0 } return this['reset'](), i } function _compress(t) { if (t < 64) console.error("compress error: not enough data"); else { for (var f = function (e) { for (var r = new Array(132), t = 0; t < 16; t++) r[t] = e[4 * t] << 24, r[t] |= e[4 * t + 1] << 16, r[t] |= e[4 * t + 2] << 8, r[t] |= e[4 * t + 3], r[t] >>>= 0; for (var n = 16; n < 68; n++) { var a = r[n - 16] ^ r[n - 9] ^ le(r[n - 3], 15); a = a ^ le(a, 15) ^ le(a, 23), r[n] = (a ^ le(r[n - 13], 7) ^ r[n - 6]) >>> 0 } for (n = 0; n < 64; n++) r[n + 68] = (r[n] ^ r[n + 4]) >>> 0; return r }(t), i = this['reg'].slice(0), c = 0; c < 64; c++) { var o = le(i[0], 12) + i[4] + le(de(c), c) , s = ((o = le(o = (4294967295 & o) >>> 0, 7)) ^ le(i[0], 12)) >>> 0 , u = pe(c, i[0], i[1], i[2]); u = (4294967295 & (u = u + i[3] + s + f[c + 68])) >>> 0; var b = he(c, i[4], i[5], i[6]); b = (4294967295 & (b = b + i[7] + o + f[c])) >>> 0, i[3] = i[2], i[2] = le(i[1], 9), i[1] = i[0], i[0] = u, i[7] = i[6], i[6] = le(i[5], 19), i[5] = i[4], i[4] = (b ^ le(b, 9) ^ le(b, 17)) >>> 0 } for (var l = 0; l < 8; l++) this['reg'][l] = (this['reg'][l] ^ i[l]) >>> 0 } } function _fill() { var a = 8 * this['size'] , f = this['chunk']['push'](128) % 64; for (64 - f < 8 && (f -= 64); f < 56; f++) this.chunk['push'](0); for (var i = 0; i < 4; i++) { var c = Math['floor'](a / 4294967296); this['chunk'].push(c >>> 8 * (3 - i) & 255) } for (i = 0; i < 4; i++) this['chunk']['push'](a >>> 8 * (3 - i) & 255) } function SM3() { this.reg = []; this.chunk = []; this.size = 0; this.reset() } SM3.prototype.reset = reset; SM3.prototype.write = write; SM3.prototype.sum = sum; SM3.prototype._compress = _compress; SM3.prototype._fill = _fill; function result_encrypt(long_str, num = null) { let s_obj = { "s0": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", "s1": "Dkdpgh4ZKsQB80/Mfvw36XI1R25+WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=", "s2": "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=", "s3": "ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe", "s4": "Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe" } let constant = { "0": 16515072, "1": 258048, "2": 4032, "str": s_obj[num], } let result = ""; let lound = 0; let long_int = get_long_int(lound, long_str); for (let i = 0; i < long_str.length / 3 * 4; i++) { if (Math.floor(i / 4) !== lound) { lound += 1; long_int = get_long_int(lound, long_str); } let key = i % 4; switch (key) { case 0: temp_int = (long_int & constant["0"]) >> 18; result += constant["str"].charAt(temp_int); break; case 1: temp_int = (long_int & constant["1"]) >> 12; result += constant["str"].charAt(temp_int); break; case 2: temp_int = (long_int & constant["2"]) >> 6; result += constant["str"].charAt(temp_int); break; case 3: temp_int = long_int & 63; result += constant["str"].charAt(temp_int); break; default: break; } } return result; } function get_long_int(round, long_str) { round = round * 3; return (long_str.charCodeAt(round) << 16) | (long_str.charCodeAt(round + 1) << 8) | (long_str.charCodeAt(round + 2)); } function gener_random(random, option) { return [ (random & 255 & 170) | option[0] & 85, // 163 (random & 255 & 85) | option[0] & 170, //87 (random >> 8 & 255 & 170) | option[1] & 85, //37 (random >> 8 & 255 & 85) | option[1] & 170, //41 ] } ////////////////////////////////////////////// function generate_rc4_bb_str(url_search_params, user_agent, window_env_str, suffix = "cus", Arguments = [0, 1, 14]) { let sm3 = new SM3() let start_time = Date.now() /** * 进行3次加密处理 * 1: url_search_params两次sm3之的结果 * 2: 对后缀两次sm3之的结果 * 3: 对ua处理之后的结果 */ // url_search_params两次sm3之的结果 let url_search_params_list = sm3.sum(sm3.sum(url_search_params + suffix)) // 对后缀两次sm3之的结果 let cus = sm3.sum(sm3.sum(suffix)) // 对ua处理之后的结果 let ua = sm3.sum(result_encrypt(rc4_encrypt(user_agent, String.fromCharCode.apply(null, [0.00390625, 1, 14])), "s3")) // let end_time = Date.now() // b let b = { 8: 3, // 固定 10: end_time, //3次加密结束时间 15: { "aid": 6383, "pageId": 6241, "boe": false, "ddrt": 7, "paths": { "include": [ {}, {}, {}, {}, {}, {}, {} ], "exclude": [] }, "track": { "mode": 0, "delay": 300, "paths": [] }, "dump": true, "rpU": "" }, 16: start_time, //3次加密开始时间 18: 44, //固定 19: [1, 0, 1, 5], } //3次加密开始时间 b[20] = (b[16] >> 24) & 255 b[21] = (b[16] >> 16) & 255 b[22] = (b[16] >> 8) & 255 b[23] = b[16] & 255 b[24] = (b[16] / 256 / 256 / 256 / 256) >> 0 b[25] = (b[16] / 256 / 256 / 256 / 256 / 256) >> 0 // 参数Arguments [0, 1, 14, ...] // let Arguments = [0, 1, 14] b[26] = (Arguments[0] >> 24) & 255 b[27] = (Arguments[0] >> 16) & 255 b[28] = (Arguments[0] >> 8) & 255 b[29] = Arguments[0] & 255 b[30] = (Arguments[1] / 256) & 255 b[31] = (Arguments[1] % 256) & 255 b[32] = (Arguments[1] >> 24) & 255 b[33] = (Arguments[1] >> 16) & 255 b[34] = (Arguments[2] >> 24) & 255 b[35] = (Arguments[2] >> 16) & 255 b[36] = (Arguments[2] >> 8) & 255 b[37] = Arguments[2] & 255 // (url_search_params + "cus") 两次sm3之的结果 /**let url_search_params_list = [ 91, 186, 35, 86, 143, 253, 6, 76, 34, 21, 167, 148, 7, 42, 192, 219, 188, 20, 182, 85, 213, 74, 213, 147, 37, 155, 93, 139, 85, 118, 228, 213 ]*/ b[38] = url_search_params_list[21] b[39] = url_search_params_list[22] // ("cus") 对后缀两次sm3之的结果 /** * let cus = [ 136, 101, 114, 147, 58, 77, 207, 201, 215, 162, 154, 93, 248, 13, 142, 160, 105, 73, 215, 241, 83, 58, 51, 43, 255, 38, 168, 141, 216, 194, 35, 236 ]*/ b[40] = cus[21] b[41] = cus[22] // 对ua处理之后的结果 /** * let ua = [ 129, 190, 70, 186, 86, 196, 199, 53, 99, 38, 29, 209, 243, 17, 157, 69, 147, 104, 53, 23, 114, 126, 66, 228, 135, 30, 168, 185, 109, 156, 251, 88 ]*/ b[42] = ua[23] b[43] = ua[24] //3次加密结束时间 b[44] = (b[10] >> 24) & 255 b[45] = (b[10] >> 16) & 255 b[46] = (b[10] >> 8) & 255 b[47] = b[10] & 255 b[48] = b[8] b[49] = (b[10] / 256 / 256 / 256 / 256) >> 0 b[50] = (b[10] / 256 / 256 / 256 / 256 / 256) >> 0 // object配置项 b[51] = b[15]['pageId'] b[52] = (b[15]['pageId'] >> 24) & 255 b[53] = (b[15]['pageId'] >> 16) & 255 b[54] = (b[15]['pageId'] >> 8) & 255 b[55] = b[15]['pageId'] & 255 b[56] = b[15]['aid'] b[57] = b[15]['aid'] & 255 b[58] = (b[15]['aid'] >> 8) & 255 b[59] = (b[15]['aid'] >> 16) & 255 b[60] = (b[15]['aid'] >> 24) & 255 // 中间进行了环境检测 // 代码索引: 2496 索引值: 17 (索引64关键条件) // '1536|747|1536|834|0|30|0|0|1536|834|1536|864|1525|747|24|24|Win32'.charCodeAt()得到65位数组 /** * let window_env_list = [49, 53, 51, 54, 124, 55, 52, 55, 124, 49, 53, 51, 54, 124, 56, 51, 52, 124, 48, 124, 51, * 48, 124, 48, 124, 48, 124, 49, 53, 51, 54, 124, 56, 51, 52, 124, 49, 53, 51, 54, 124, 56, * 54, 52, 124, 49, 53, 50, 53, 124, 55, 52, 55, 124, 50, 52, 124, 50, 52, 124, 87, 105, 110, * 51, 50] */ let window_env_list = []; for (let index = 0; index < window_env_str.length; index++) { window_env_list.push(window_env_str.charCodeAt(index)) } b[64] = window_env_list.length b[65] = b[64] & 255 b[66] = (b[64] >> 8) & 255 b[69] = [].length b[70] = b[69] & 255 b[71] = (b[69] >> 8) & 255 b[72] = b[18] ^ b[20] ^ b[26] ^ b[30] ^ b[38] ^ b[40] ^ b[42] ^ b[21] ^ b[27] ^ b[31] ^ b[35] ^ b[39] ^ b[41] ^ b[43] ^ b[22] ^ b[28] ^ b[32] ^ b[36] ^ b[23] ^ b[29] ^ b[33] ^ b[37] ^ b[44] ^ b[45] ^ b[46] ^ b[47] ^ b[48] ^ b[49] ^ b[50] ^ b[24] ^ b[25] ^ b[52] ^ b[53] ^ b[54] ^ b[55] ^ b[57] ^ b[58] ^ b[59] ^ b[60] ^ b[65] ^ b[66] ^ b[70] ^ b[71] let bb = [ b[18], b[20], b[52], b[26], b[30], b[34], b[58], b[38], b[40], b[53], b[42], b[21], b[27], b[54], b[55], b[31], b[35], b[57], b[39], b[41], b[43], b[22], b[28], b[32], b[60], b[36], b[23], b[29], b[33], b[37], b[44], b[45], b[59], b[46], b[47], b[48], b[49], b[50], b[24], b[25], b[65], b[66], b[70], b[71] ] bb = bb.concat(window_env_list).concat(b[72]) return rc4_encrypt(String.fromCharCode.apply(null, bb), String.fromCharCode.apply(null, [121])); } function generate_random_str() { let random_str_list = [] random_str_list = random_str_list.concat(gener_random(Math.random() * 10000, [3, 45])) random_str_list = random_str_list.concat(gener_random(Math.random() * 10000, [1, 0])) random_str_list = random_str_list.concat(gener_random(Math.random() * 10000, [1, 5])) return String.fromCharCode.apply(null, random_str_list) } function generate_a_bogus(url_search_params, user_agent) { /** * url_search_params:"device_platform=webapp&aid=6383&channel=channel_pc_web&update_version_code=170400&pc_client_type=1&version_code=170400&version_name=17.4.0&cookie_enabled=true&screen_width=1536&screen_height=864&browser_language=zh-CN&browser_platform=Win32&browser_name=Chrome&browser_version=123.0.0.0&browser_online=true&engine_name=Blink&engine_version=123.0.0.0&os_name=Windows&os_version=10&cpu_core_num=16&device_memory=8&platform=PC&downlink=10&effective_type=4g&round_trip_time=50&webid=7362810250930783783&msToken=VkDUvz1y24CppXSl80iFPr6ez-3FiizcwD7fI1OqBt6IICq9RWG7nCvxKb8IVi55mFd-wnqoNkXGnxHrikQb4PuKob5Q-YhDp5Um215JzlBszkUyiEvR" * user_agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" */ let result_str = generate_random_str() + generate_rc4_bb_str( url_search_params, user_agent, "1536|747|1536|834|0|30|0|0|1536|834|1536|864|1525|747|24|24|Win32" ); return result_encrypt(result_str, "s4") + "="; } //测试调用 // console.log(generate_a_bogus( // "device_platform=webapp&aid=6383&channel=channel_pc_web&update_version_code=170400&pc_client_type=1&version_code=170400&version_name=17.4.0&cookie_enabled=true&screen_width=1536&screen_height=864&browser_language=zh-CN&browser_platform=Win32&browser_name=Chrome&browser_version=123.0.0.0&browser_online=true&engine_name=Blink&engine_version=123.0.0.0&os_name=Windows&os_version=10&cpu_core_num=16&device_memory=8&platform=PC&downlink=10&effective_type=4g&round_trip_time=50&webid=7362810250930783783&msToken=VkDUvz1y24CppXSl80iFPr6ez-3FiizcwD7fI1OqBt6IICq9RWG7nCvxKb8IVi55mFd-wnqoNkXGnxHrikQb4PuKob5Q-YhDp5Um215JzlBszkUyiEvR", // "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" // )); ================================================ FILE: simple_live_core/assets/js/douyin-webmssdk.js ================================================ // extracted from douyin webmssdk.es5.js // Modified by @hua0512 // Date: 2024-06-21 // Below is a modification to make it work without the original environment // function my_handler(target_name, number) { // console.log('my_handler', target_name, number) // return target_name + number; // } // window = {} document = {} navigator = { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36' }; // // _history = {} // _screen = {} // _location = {} // // window = new Proxy(_window, { get: my_handler }); // document = new Proxy(_document, { get: my_handler }); // navigator = new Proxy(_navigator, { get: my_handler }); // history = new Proxy(_history, { get: my_handler }); // screen = new Proxy(_screen, { get: my_handler }); // location = new Proxy(_location, { get: my_handler }); var crawler; /** 1.0.0.53 */ ; if (!window.byted_acrawler) { function w_0x25f3(_0x545d0a, _0xb73ac6) { var _0x4173a9 = w_0x42f5(); return w_0x25f3 = function (_0x138003, _0x35a375) { _0x138003 = _0x138003 - (-0x1 * -0xd15 + -0x1044 + 0x48d); var _0x484578 = _0x4173a9[_0x138003]; return _0x484578; } , w_0x25f3(_0x545d0a, _0xb73ac6); } (function (_0x2afeae, _0x11644f) { var _0x34f31d = w_0x25f3 , _0x222a7c = _0x2afeae(); while (!![]) { try { var _0x5a962 = -parseInt(_0x34f31d(0x31b)) / (-0x1c * 0xac + 0x22 * 0x121 + 0x1 * -0x1391) * (-parseInt(_0x34f31d(0x26a)) / (0xa27 * -0x1 + 0x683 * -0x5 + 0x4 * 0xaae)) + parseInt(_0x34f31d(0x296)) / (-0x18c1 + 0xa9a + 0x715 * 0x2) + -parseInt(_0x34f31d(0x1f3)) / (0x446 * 0x4 + 0x649 + -0x175d) * (parseInt(_0x34f31d(0x31c)) / (0x3 * -0xbbf + 0xf67 * 0x1 + -0x12b * -0x11)) + parseInt(_0x34f31d(0x347)) / (-0x102f * 0x2 + 0x9e * 0x19 + 0x10f6) * (-parseInt(_0x34f31d(0x28f)) / (-0x1 * 0x1817 + -0x1 * -0x184d + -0x2f)) + -parseInt(_0x34f31d(0x241)) / (0x779 * -0x1 + -0x1e1d + 0x259e) * (-parseInt(_0x34f31d(0x2b9)) / (-0x435 + -0x8c9 * 0x2 + 0x1 * 0x15d0)) + -parseInt(_0x34f31d(0x295)) / (0x246e + 0x1 * -0x1c79 + 0x7eb * -0x1) * (-parseInt(_0x34f31d(0x291)) / (-0x1b67 + -0x3c3 * -0x3 + 0x1029)) + parseInt(_0x34f31d(0x307)) / (0x1c3d + -0xaa0 + 0x3 * -0x5db) * (-parseInt(_0x34f31d(0x39d)) / (-0x18de + -0x3 * -0x6f7 + 0x406)); if (_0x5a962 === _0x11644f) break; else _0x222a7c['push'](_0x222a7c['shift']()); } catch (_0x4c4ab0) { _0x222a7c['push'](_0x222a7c['shift']()); } } }(w_0x42f5, -0x131739 + 0x156382 + -0x7 * -0x144df)); function w_0x42f5() { var _0x458f30 = ['\x20can\x27t\x20have\x20a\x20.', '484e4f4a403f5243001f3009ad9ffc90000000dc0b1204fb00000477110001033f2e17000135491102004a120000110001110001031a2747000503414500201100010334274700050347450012110001033e2747000603041d45000303111d184301421101021400020211000211000103182c43010211000211000103122c43011802110002110001030c2c4301180211000211000103062c43011802110002110001430118421100011401010211010311000103022c430142110101031c2b11000103042d2f1400021100011401010211010311000243014202110103110101031a2b11000103062d2f4301021101021100014301184205000000003b0114000205000000473b01140003050000008b3b01140004050000009e3b0114000505000000be3b0114000603001400010300140007030014000811010144004a12000143000403e81b03002d14000911010212000232330033021101030211010303001100090700031843021101041200044a12000511010412000612000703021843014302050000fff11c140008110009110008050000fff11a3103002d4a1200080302430114000a11000a14000b11000a12000703202947001811000a4a12000511000a120007032019430114000b45004511000a12000703202747003907000314000c030014000d11000d032011000a120007192747001411000c0700091817000c354917000d214945ffdc11000c11000b1814000b07000a11000b18140007021101051100070302430214000702110103030011000707000318430214000e02110106430014000f11011807000b25470004014500010011000f07000c1607000314001011011612000d3300131101074a12000e11011612000d430107000f2647006503001400111101161200104700290211010803001101074a12000e0211010911011612000d1101161200104302430143021400114500200211010803001101074a12000e0211010a11011612000d43014301430214001107001111001118070012181400100211010b110116120013430114001211011612001447001511010c4a12001511001211011612001443024500031100121400121100100211010d110012430118140010110010070016180211010e1101161200134301180700121814001011001007001718070018181400100211010f11000f4301140013110102120002323300060211011043001400141101021200023233001811011112001934000f021101120211011307001a430143011400150211000411000743010211000511000706001b1b03002d4301180211000611001411000731430118021100040211010311000e1101021200023233000611011412001c4a12000843004302050000fff11c03102b0211010311000e110010070003184302050000fff11c2f4301180211000511001303082b11010212001d03042b2f110007314301180211000311000843011814001602110006030043014911001547000a1100161100151814001607001e1100161814001702110108030011001743024a120008031043011400181100184a12001f1100181200070302191100181200074302140019110017110019181400171100174200200c6b7f62604e656c7f4e626968076a6879596460680b6962604362795b6c6164690004657f686b097e786f7e797f64636a087d7f6279626e6261066168636a79650879625e797f64636a013d0e3c3d3d3d3d3d3d3d3c3c3d3d3d3d076b627f7f686c610a69647f686e795e646a63046f626974097e797f64636a646b740276700b6f6269745b6c613f7e797f0a6f62697452656c7e6530012b03787f61057c78687f740a6c7e626169527e646a63097d6c7965636c606830097979527a686f646930062b78786469300e526f74796869527e686e52696469077979527e6e64690a393f3439343b3a3f343b09787e687f4c6a686379096b685b687f7e6462630e523d3f4f39573b7a623d3d3d3d3c057e61646e68', '484e4f4a403f5243003c01321067d4bc00000824ebfd74540000087f0211010311000111000243024a12000505000000213b0105000001533b014302421100011200064701251100011200073300191100011200074a12000811030112000912000a430103011d2634000c0211030211000112000743014700f111000112000b4a12000c07000d43011400021100024700d902110303110001120007430114000311000311030412000e2547005511000211030515000f1100031103051500100211030607000f110002430249021103071100024301491100031101032947001f1103051200111200120300294700100211030811030903020403e81a4302494500161101031103051200102a47000911000211030515000f1101031103041200132533000c110305120011120012030a274700361103051200114a120014110002430149110305120011120012030125470017021103071100024301490211030607000f110002430249110001421100014008421100023400010d14000211020a33000711000111020b3714000307001514000407001614000507001514000611020c33000711000111020d374701c411000112000a14000411000212001747000f1100021200174a12001843004500030700161400050211020e1100044301330011110005070016253400071100050700192547017d11020512001014000711020512001a1400081100080700152347000f07000f11020512000f0c000245001207000f11020512000f07001a1100080c00041400090211020f021102101100044301110009430214000a0211021111000a430114000b0211021211000b11000212001b430214000c0211020f11000a11010111000c0c0002430214000d07001514000e11021312001c47000911000d14000e4500b10d021102140211000d43020e000714000f110005070019254700710211021511000111000243024a12001d07001e43010300134a12001f430014000602110216110006430147003b0211021711000f11000611000212001b4303490211021811000f0807002043031400100211020f11000d1101021100100c0002430214000e45000611000d14000e4500250211021811000f0807002043031400110211020f11000d1101021100110c0002430214000e1102131200214700130211021a430011000212000b110219120022160211010411000e1100021100074303421100034701e91100011200071400041100011200174700091100011200174500030700161400050211020e1100044301330011110005070016253400071100050700192547019811020512001014001211020512001a1400131100130700152347000f07000f11020512000f0c000245001207000f11020512000f07001a1100130c00041400140211020f021102101100044301110014430214001502110211110015430114001611000112000b1400171102131200214700161100174a1200231102191200220211021a4300430249110005070019254700480211021511000111000243024a12001d07001e43010300134a12001f43001400061100014a12002443004a12002543004a12000505000007293b01050000081e3b014302424500bd021102121100160243021400180211020f1100151101011100180c000243021400190d021102140211001943020e000714001a0211021811001a08070020430314001b0211020f11001911010211001b0c0002430214001c11020b11001c0d1100170e000b080e001b1100011200260e00261100011200270e00271100011200280e00281100011200290e002911000112002a0e002a11000112002b0e002b11000112002c0e002c440214001d0211010411001d110002110012430342021101031100011100024302424501df11000212000b324700070d11000215000b11000114000411000212001747000f1100021200174a12001843004500030700161400050211020e1100044301330011110005070016253400071100050700192547017d11020512001014001e11020512001a14001f11001f0700152347000f07000f11020512000f0c000245001207000f11020512000f07001a11001f0c00041400200211020f02110210110004430111002043021400210211021111002143011400220211021211002211000212001b43021400230211020f1100211101011100230c0002430214002407001514002511021312001c4700091100241400254500b10d021102140211002443020e0007140026110005070019254700710211021511000111000243024a12001d07001e43010300134a12001f430014000602110216110006430147003b0211021711002611000611000212001b430349021102181100260807002043031400270211020f1100241101021100270c00024302140025450006110024140025450025021102181100260807002043031400280211020f1100241101021100280c000243021400251102131200214700130211021a430011000212000b110219120022160211010411002511000211001e4303420211010311000111000243024208420700151400020211031211011611000143021400030211030f1101151102011100030c000243021400040211031611010643014700490d021103140211000443020e000714000502110317110005110106110001430349021103181100050807002043031400060211030f1100041102021100060c0002430214000245000611000414000211030b1100020d1101011200170e00171101170e000b1100010e001b1101011200260e00261101011200270e00271101011200280e00281101011200290e002911010112002a0e002a11010112002b0e002b11010112002c0e002c44021400070211020411000711010211011243034211000140084205000000003b0314000405000001593b021400050700001400010700011400020211010043003247000208421101011200024700020842001101011500021101011200031400031100031101011500041100051101011500030842002d0754214e636b797f0a537f656b626d78797e691653536d6f53656278697e6f697c786968536a69786f64056a69786f6406536a69786f64047864696202636703797e60076562686974436a0860636f6d7865636204647e696a0764696d68697e7f036b69780a7421617f217863676962037f696f07617f586367696208617f5f786d78797f0e617f42697b586367696240657f78066069626b78640465626578047c797f6400034b4958066169786463680b7863597c7c697e4f6d7f69045c435f580b53536d6f5378697f786568046e636875017a057f7c60657801370b786340637b697e4f6d7f69076a637e7e696d60037f68650d7f696f45626a6344696d68697e037f6978056f606362690478697478087e696a697e7e697e0e7e696a697e7e697e5c6360656f7504616368690b6f7e6968696278656d607f056f6d6f6469087e6968657e696f7809656278696b7e657875', 'own', 'Super\x20expression\x20must\x20either\x20be\x20null\x20or\x20a\x20function', 'getTimezoneOffset', 'array', 'toDataURL', 'enumerable', 'setPrototypeOf', 'field', 'WEBGL', 'wID', 'illegal\x20catch\x20attempt', 'TouchEvent', '3160hBpQVk', '484e4f4a403f5243000d0a13c08652000000000f3be74070000003930211021611010111000143024908421101003300031101013300031101023247000208420d0700000e000103040e00021101181200000e00030d0700040e000103030e00021101030e00050d0700060e000103030e00021101040e00050d0700070e000103030e00021101050e00050d0700080e000103030e00021101030e00050d0700090e000103000e00020d07000a0e000103000e00020d07000b0e000103000e00020d07000c0e000103000e00020d07000d0e000103000e00020d07000e0e000103030e00021101060e00050d07000f0e000103030e00021101070e00050d0700100e000103010e00020d0700110e000103010e00020d0700120e000103010e00020d0700130e000103000e00020d0700140e000103030e00021101080e000503010e00150d0700160e000103030e00021101090e00050d0700170e000103030e000211010a0e00050d0700180e000103030e00021101030e00050d0700190e000103030e000211010b0e00050d07001a0e000103030e000211010c0e00050d07001b0e000103030e000211010d0e00050d07001c0e000103030e00021101030e00050d07001d0e000103000e00020d07001e0e000103030e000211010e0e000507001f0e00200d0700210e000103030e000211010f0e00050d0700220e000103030e00021101100e00050d0700230e000103030e00021101110e000503010e00150d0700240e000103010e00020d0700250e000103040e00021101121200260e00030d0700270e000103030e00021101130e00050d0700280e000103030e00021101030e00050d0700290e000103040e00020c00221400010c0000140002030014000311000311000112002a274700eb110001110003131200020300480013030148002f0302480045030348005b494500be0211011411010011000111000313120001134301110001110003131500034500a011010111000111000313120001131100011100031315000345008511010211000111000313120001131100011100031315000345006a110001110003131200154700321101153a07002b264700241100024a12002c110001110003131200054a12002d110001110003131200204301430149450025110001110003131200054a12002d0211000111000313120020430211000111000313150003450003450000170003214945ff081101153a07002b2647001d1101154a12002e11000243014a12002f05000000003b0143014945000a02110116110001430149084200300349414c0146014e015a095b5c495a5c7c41454d015c09494a4144415c414d5b064b49465e495b0a5c41454d5b5c49455819085844495c4e475a451340495a4c5f495a4d6b47464b5d5a5a4d464b510c4c4d5e414b4d654d45475a51084449464f5d494f4d094449464f5d494f4d5b0a5a4d5b47445d5c4147460f495e4941447a4d5b47445d5c414746095b4b5a4d4d467c47580a5b4b5a4d4d46644d4e5c104c4d5e414b4d7841504d447a495c41470a585a474c5d4b5c7b5d4a074a495c5c4d5a510158095c475d4b4061464e47085c41454d5247464d0a5c41454d5b5c4945581a074f585d61464e470b425b6e47465c5b64415b5c0b58445d4f41465b64415b5c0a5c41454d5b5c4945581b095d5b4d5a694f4d465c0a4d5e4d5a6b474743414d075c5c775b4b414c01450b5b51465c49506d5a5a475a0c46495c415e4d644d464f5c40055a5c4b61780844474b495c414746094e587e4d5a5b4147460b77775e4d5a5b4147467777084b44414d465c614c0a5c41454d5b5c4945581c0b4d505c4d464c6e414d444c06444d464f5c40095d464c4d4e41464d4c04585d5b40044b49444403494444045c404d46', 'rewriteUrl\x20', 'setTTWid', 'height', 'product', 'Vrinda', 'X-Mssdk-Info', 'arrayBuffer', 'vibrate', 'sendBeacon', '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 'canvas', 'Character\x20outside\x20valid\x20Unicode\x20range:\x200x', 'join', 'throw', 'iterator\x20result\x20is\x20not\x20an\x20object', 'DEPTH_BITS', 'compatMode', 'forEach', 'Attempted\x20to\x20access\x20private\x20element\x20on\x20non-instance', 'unsupport\x20type', 'attempted\x20to\x20call\x20addInitializer\x20after\x20decoration\x20was\x20finished', 'pageXOffset', 'length', 'method', 'bytes', 'charAt', 'Sylfaen', 'toElementDescriptor', 'base64', 'createElement', 'private', 'ttcid', 'msStatus', 'MAX_VARYING_VECTORS', 'elements', '484e4f4a403f524300232a0d2ebaf9a00000000042410a740000019d110100002347000200421101011200004700020042070001110102364700351101024a12000111010143011400011100014a120002070000430103002a34000f1100014a120002070003430103002a470002004211010333000611010312000433000911010312000412000533000c1101031200041200051200064700213e000414000c413d00171101031200041200054a1200064300082547000200424107000707000807000907000a07000b07000c07000d07000e07000f0700100700110c000b1400020700120700130700140c000314000303001400041100041100031200152747001e11000311000413140005110103110005134700020042170004214945ffd503001400061100061100021200152747002111000211000613140007110103120016110007134700020042170006214945ffd21101024a1200171101031200164301140008030014000911000814000a11000911000a1200152747003911000a1100091314000b11000b4a1200181101050700194401430133000e11010312001611000b1307001a134700020042170009214945ffba0142001b096d7f787e68736c7f68137d7f6e556d744a68756a7f686e63547b777f690773747e7f62557c09767b747d6f7b7d7f690679726875777f07686f746e73777f07797574747f796e1445456d7f787e68736c7f68457f6c7b766f7b6e7f134545697f767f74736f77457f6c7b766f7b6e7f1b45456d7f787e68736c7f6845697968736a6e457c6f74796e7375741745456d7f787e68736c7f6845697968736a6e457c6f74791545456d7f787e68736c7f6845697968736a6e457c741345457c627e68736c7f68457f6c7b766f7b6e7f1245457e68736c7f68456f746d687b6a6a7f7e1545456d7f787e68736c7f68456f746d687b6a6a7f7e1145457e68736c7f68457f6c7b766f7b6e7f144545697f767f74736f77456f746d687b6a6a7f7e1445457c627e68736c7f68456f746d687b6a6a7f7e0945697f767f74736f770c797b7676497f767f74736f771645497f767f74736f7745535e5f45487f7975687e7f6806767f747d6e72087e75796f777f746e04717f636905777b6e79720a463e417b3760477e794506797b79727f45', 'pop', 'showOffsetX', 'MAX_TEXTURE_IMAGE_UNITS', '2571598OPFKfY', 'webgl', 'appendChild', 'outerHeight', 'screenX', 'kind', 'navigator', 'attempted\x20to\x20use\x20private\x20field\x20on\x20non-instance', 'cookie', 'AsyncIterator', 'crypto', 'buffer', 'availHeight', 'The\x20property\x20descriptor\x20of\x20a\x20field\x20descriptor', 'resolve', 'react.element', 'close', 'monospace', 'stun:stun.l.google.com:19302', 'acc', 'BLUE_BITS', 'Arguments', 'style', 'nextLoc', 'pageYOffset', '72px', 'webkitRequestAnimationFrame', 'hidden', 'MAX_FRAGMENT_UNIFORM_VECTORS', 'node', 'Parchment', 'kWebsocket', 'xmst', 'number', 'altKey', 'A\x20method\x20descriptor', 'Leelawadee', '6300OtYFrs', 'href', '1155KnHwvu', 'storage', 'sTm', 'setMonth', '20690wmYtVy', '266076LNZfld', 'Malformed\x20string', 'setConfig', 'touchmove', 'rval', 'unload', 'from', 'Image', 'tryLoc', 'ActiveXObject', 'assign', '\x20property.', '_sent', 'mmmmmmmmmmlli', 'offsetHeight', 'slice', 'getOwnPropertyDescriptor', 'mhe', 'utf8', 'Object\x20is\x20not\x20async\x20iterable', 'reset', 'raw', 'constructor', 'attempted\x20to\x20', 'Invalid\x20attempt\x20to\x20iterate\x20non-iterable\x20instance.\x0aIn\x20order\x20to\x20be\x20iterable,\x20non-array\x20objects\x20must\x20have\x20a\x20[Symbol.iterator]()\x20method.', 'isArray', 'chargingTime', '__await', '484e4f4a403f524300071336bc6677450000001613b112b6000003090b4a12000911021607000a07000b4402070001430242070000140001110115082633000511011502263300071101150700012647001d3e000a140029070002140001413d000d021101001101154301140001411101013234000611010212000347000b001401010211010343004902110104430049110105120004140002110106120005140003030214000411000414000503401400060211010011011443011400071101074a120006021101001101074a1200061100074301430143011400081101074a120006021101001101074a1200061100014301430143011400091101081200071200083247001005000000003b0011010812000715000811010912000c14000a11000a33000811000a3a07000d2547000c11000a4a120008430014000a0211010a110003110002430214000b0211010b11000b11000a430214000c0211010c11000c07000e430214000d1101074a1200060211010011000d4301430114000e11010d44004a12000f43000403e81b14000f0211010e43001400101100061400111100030401001b1400121100030401001c140013110002140014110008030e13140015110008030f13140016110009030e13140017110009030f1314001811000e030e1314001911000e030f1314001a11000f03182c0400ff2e14001b11000f03102c0400ff2e14001c11000f03082c0400ff2e14001d11000f03002c0400ff2e14001e11001003182c0400ff2e14001f11001003102c0400ff2e14002011001003082c0400ff2e14002111001003002c0400ff2e140022110011110012311100133111001431110015311100163111001731110018311100193111001a3111001b3111001c3111001d3111001e3111001f311100203111002131110022311400230400ff1400240211010f11001111001311001511001711001911001b11001d11001f11002111002311001211001411001611001811001a11001c11001e11002011002243131400250211010b0211011011002443011100254302140026021101111100051100241100264303140027021101121100270700104302140028110028420011201c4c491c401b1c41401e48481a4a484c1d414048484141401d1b1e404c4a4f1d00201e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e010e060d1a1b171c1d071d160e1b171c1d061c1d1b171c1d09080a170c170c01081d040c0a1115070a1d0814191b1d212623240b240d3e3d3e3e2400394825530423240b240d3e3d3e3e2400394825535c011f090d0b1d0a391f1d160c060b0c0a11161f020b48071f1d0c2c11151d020b4a', 'Arial\x20Hebrew', 'msToken', 'An\x20element\x20descriptor\x27s\x20.placement\x20property\x20must\x20be\x20one\x20of\x20\x22static\x22,\x20\x22prototype\x22\x20or\x20\x22own\x22,\x20but\x20a\x20decorator\x20created\x20an\x20element\x20descriptor\x20with\x20.placement\x20\x22', 'visible', 'decode', 'concat', '30762fvQkGV', 'hash', 'STENCIL_BITS', 'configurable', '\x20private\x20field\x20on\x20non-instance', 'T_MOVE', 'clientX', 'images', 'version', 'renderer', 'removeItem', 'catchLoc', 'indexOf', 'msHidden', 'isView', 'toLocaleString', 'https://mssdk.bytedance.com', 'B4Z6wo', 'dispatchException', 'msvisibilitychange', '\x20decorators\x20must\x20return\x20a\x20function\x20or\x20void\x200', 'Dkdpgh4ZKsQB80/Mfvw36XI1R25+WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe', 'Cannot\x20convert\x20undefined\x20or\x20null\x20to\x20object', 'initializer', 'awrap', 'continue', 'mousedown', 'mousemove', 'update', '__ac_blank', '0123456789abcdef', 'getItem', 'Futura', 'MAX_RENDERBUFFER_SIZE', 'GPUINFO', 'host', '484e4f4a403f524300010a1106afb0650000000079a66ec20000008c1101001200004a12000143001400011100014a120002070003430103002a470002014211010307000444011400021101013300061101011200053300091101011200051200064700411101011200051200061400031100034a120002070007430103002534000f1100034a120002070008430103002534000c1100024a120009110003430147000200420142000a093b3d2b3c0f292b203a0b3a210221392b3c0d2f3d2b0727202a2b360128082b222b2d3a3c21204a10263a3a3e3d71741261126166157e637713357f627d33661260157e637713357f627d3367357d3332152f63287e637713357f627a336674152f63287e637713357f627a3367357933670822212d2f3a27212004263c2b28042827222b10263a3a3e74616122212d2f2226213d3a043a2b3d3a', 'BluetoothUUID', 'decorateClass', 'mozRTCPeerConnection', 'defineClassElement', 'credentials', 'writable', 'value', 'WEBKIT_EXT_texture_filter_anisotropic', 'JS_MD5_NO_ARRAY_BUFFER', 'Metadata\x20keys\x20must\x20be\x20symbols,\x20received:\x20', 'getReferer', 'indexDB', '__proto__', 'Object', 'splice', 'symbol', 'offsetWidth', 'executing', 'mozBattery', 'normal', 'kFakeOperations', 'reverse', 'finisher', 'createOffer', 'addEventListener', 'Cannot\x20call\x20a\x20class\x20as\x20a\x20function', '\x20must\x20be\x20a\x20function', 'getContext', 'referrer', 'afterLoc', 'has', 'return', 'kNoMove', 'netscape', 'MAX_CUBE_MAP_TEXTURE_SIZE', 'bind', 'width', 'valueOf', 'off', 'JS_MD5_NO_ARRAY_BUFFER_IS_VIEW', 'innerWidth', '257232gkndOM', 'null', 'isSecureContext', 'pixelDepth', '.initializer\x20has\x20been\x20renamed\x20to\x20.init\x20as\x20of\x20March\x202022', '/web/report', 'removeChild', '[object\x20Boolean]', 'getSupportedExtensions', 'error', 'GeneratorFunction', 'message', 'initializeInstanceElements', 'systemLanguage', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 'tt_webid', 'sqrt', '__ac_referer', 'blocks', 'MOZ_EXT_texture_filter_anisotropic', '1hcbEHL', '308350OyvEoV', '484e4f4a403f5243003a20169967f185000000000b49c93c000000761101001200004a12000143001400011100014a120002070003430103002a47000201421101013a070004263300191101021200051200064a12000711010112000843010700092534002b1101033a0700042547000607000445000902110104110103430107000a2533000a11010312000b07000c2542000d09282e382f1c3a3833290b293211322a382f1e3c2e38073433393825123b083831383e292f323309283339383b34333839092d2f32293229242d380829320e292f34333a043e3c3131072d2f323e382e2e1006323f37383e297d2d2f323e382e2e0006323f37383e290529342931380433323938', 'for', 'break', 'construct', 'Constantia', 'webkitvisibilitychange', 'activeState', 'toClassDescriptor', 'layers', 'bogusIndex', 'arg', 'screen', 'buffer8', 'getOwnPropertyNames', 'get', 'prev', 'buildID', 'lastByteIndex', '\x27\x20method', 'Bad\x20UTF-8\x20encoding\x200x', '[object\x20Array]', 'add', '484e4f4a403f5243000027194f9666590000000044a16fed000000270700001400013e000a140002070001140001413d000d0211010011010243011400014111000142000200200d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d', 'call', 'characterSet', 'bodyVal2str', 'tryEntries', '\x22\x20is\x20read-only', 'tt_webid_v2', 'locationbar', 'maxTouchPoints', 'string', 'addElementPlacement', 'try\x20statement\x20without\x20catch\x20or\x20finally', 'mozVisibilityState', 'showColor', 'name', 'split', 'xmstr', 'prototype', 'AcroPDF.PDF.1', 'Tunga', '4218tDAtHd', 'AVENIR', 'getMetadata', 'track', 'touchEvent', 'decorateConstructor', 'now', 'failed\x20to\x20set\x20property', '484e4f4a403f524300263203ec75a3740000000817718683000003051100011100022e4211011507000013002547000a070001110115070002160d03000e0003000e00040c00000e00050c00000e0006010e0007010e00000700080e0002010e00090d0305033c1a0e000a03020e000b0305033c1a0e000c0e000d0700080e000e000e000f03030e00101400011101004a1200111100011101154302491100011200030300253400161101014a120012110001120003430111000112000326470009110102070013440140110001120014330007110001120015324700091101020700164401401101031200174a12001811000112000343014911010412000303002547000c1100011200031101041500031100011200043247009c110001120002070008254700091101020700194401401100011200020700012447000911010207001a44014011000112000211010415000202110105110001430111010415001b021101061101071100011200100403e81a43024911000112001c0826330005110001022647002e11010412001d4a12001811000112001c43014911010412001d4a12001e05000000003b024301323211010415001c11000112000d4700a60011010415001f11010847006411000112000d12000a33001311000112000d12000a11010412000d12000a2947003f021101091101084301491101004a1200110d11010412000d11000112000d430311010415000d0211010a11010b11010412000d12000a0403e81a43021401084500351101004a1200110d11010412000d11000112000d430311010415000d0211010a11010b11010412000d12000a0403e81a430214010811000112002047001c1100011200201101041500200211010611010c03050403e81a4302491100010b1500210211010d4300490211010e1100011200054301490211010f1100011200064301490211011043004911011132330006110001120007470020001401111100011200071101041500070211010611011203050403e81a43024911000112000f4700241101041200223247001a0011010415002202110106110113030a0403e81a11000143034900110104150023084200240350524402575a064651535d5b5a03555d50055d4767707f0e515a555658516455405c785d47400f414658665143465d405166415851470347505d0003505142035246510a415a5d4075595b415a4008415a5d40605d595105404655575f04595b5051044c4c56530450504640065547475d535a0552585b5b461e5b44405d5b5a14555d501c7d5a40515351461d145d47145a51515051501503565b5107565b517c5b474024565b517c5b474014594147401456511444465b425d505150145d5a14565b5114595b505107555d50785d4740044441475c0f4651535d5b5a145d47145a41585815124651535d5b5a145d47145d5a4255585d50150a4651535d5b5a775b5a520142106b515a55565851675d535a5540414651064651504157510b515a55565851604655575f0444514652075b44405d5b5a47046b5052440b5d5a5d405d55585d4e5150', 'appMinorVersion', 'Attempted\x20to\x20decorate\x20a\x20public\x20method/accessor\x20that\x20has\x20the\x20same\x20name\x20as\x20a\x20previously\x20decorated\x20public\x20method/accessor.\x20This\x20is\x20not\x20currently\x20supported\x20by\x20the\x20decorators\x20plugin.\x20Property\x20name\x20was:\x20', 'hex', 'dischargingTime', 'PLUGIN', 'T_KEYBOARD', 'ret_code', '\x22.\x20Please\x20configure\x20the\x20dynamicRequireTargets\x20or/and\x20ignoreDynamicRequires\x20option\x20of\x20@rollup/plugin-commonjs\x20appropriately\x20for\x20this\x20require\x20call\x20to\x20work.', '@@toPrimitive\x20must\x20return\x20a\x20primitive\x20value.', 'battery', 'Generator\x20is\x20already\x20running', 'headers', 'md5', 'setter', '=;\x20expires=Mon,\x2020\x20Sep\x202010\x2000:00:00\x20UTC;\x20path=/;', '_raw_sec_did', 'hardwareConcurrency', 'script', 'fontSize', '_byted_sec_did', 'keydown', '__private_', 'screenY', 'Duplicated\x20element\x20(', '484e4f4a403f524300211209597bcccc0000053aae77fba6000005e50b1200093247004e0b12000a4a12000b0d0700050e000c1100000e000d43014911021607000e07000f44024a120010110001430147001f1100024a12001143004a12001243004a12001307001443010300130b1500151101054a1200160b1100004302421100000b1500171101074a1200160b1100004302420c00000b15000a0b12000a4a12000b0d0700040e000c1100000e000d4301491100014a12001843000b1500191100020b15001a1101044a1200160b1100004302421101094a1200240b120019430103011d26140002021102010b12001a43013300031100024702fe0b12001a4a120024070025430103011d2947000e1101064a1200160b1100004302421100010b1500260b1200271400030b12001b1400040b12001c1400050b12001d1400060b12001e1400070b12001f1400080b1200201400090b12002114000a0d14000b030014000c11000c1101081200282747001f0b12002911010811000c131311000b11010811000c131617000c214945ffd411020212002a14000d11020212002b14000e11000e07002c2347000f07002d11020212002d0c000245001207002d11020212002d07002b11000e0c000414000f02110203021102040b12001a430111000f4302140010021102051100104301140011021102061100110b1200264302140012021102031100101101011100120c0002430214001307002c14001411020712002e4700091100131400144500910d021102080211001343020e002f1400150b12001907002325470050021102090b120015430147003a0211020a1100150b1200150b1200264303490211020b110015080700304303140016021102031100131101021100160c000243021400144500061100131400144500250211020b110015080700304303140017021102031100131101021100170c000243021400140b12000a33000f0b12000a03001307000c130700042647000202420b12000a14001803001400191100191100181200282747005d11001903002547002d1100141100181100191312000d030116000b1500091101044a1200160b1100181100191312000d43024945001f0b1100181100191307000c13134a1200160b1100181100191312000d430249170019214945ff960b1200174700100b1200074a1200160b0b1200174302490b07000a39491102071200314700140b4a12000511020c1200320211020d43004302491100030b1500271100040b15001b1100050b15001c05000003ed3b010b15001d1100070b15001e1100080b15001f1100090b15002011000a0b150021030014001a11001a1101081200282747001f11000b11010811001a13130b12002911010811001a131617001a214945ffd41101064a1200160b11000043024203001400020b1200333400040b12001a34000307002c1400030211030e110003430147000503011400021100034a120024110300120034120035430103011d2647000503021400021100020300294700ea0b4a12003607003743011400041100044700d70211030f0b12001a43011400051100051103101200382547005511000411030215002d11000511030215002a0211031107002d1100044302490211031211000443014911000511010d2947001f1103021200391200280300294700100211031311031403020403e81a43024945001611010d11030212002a2a47000911000411030215002d11010d11031012003a2533000c110302120039120028030a274700361103021200394a12000b110004430149110302120039120028030125470017021103121100044301490211031107002d11000443024911010647000a02110106110001430149084207000014000107000114000211010012000212000314000311000312000414000411000312000514000511000312000614000611000312000714000711000312000847000208420011000315000805000000003b0211000315000505000000643b0011000315000705000000793b0211000315000407001b07001c07001d07001e07001f0700200700210c00071400080700220700230c000214000905000000ba3b011100031500060842003b0755204f626a787e0a527e646a636c79787f680e5540414579797d5f687c78687e79097d7f62796279747d6804627d6863107e68795f687c78687e7945686c69687f047e68636910627b687f7f6469684064606859747d680f526c6e52646379687f6e687d79686905527e68636915526f7479686952646379687f6e687d795261647e79047d787e65046b78636e096c7f6a78606863797e0e536e6263796863792079747d682901640479687e790879625e797f64636a0b796241627a687f4e6c7e68057e7d61647901360e526f74796869526e626379686379056c7d7d61741552627b687f7f6469684064606859747d684c7f6a7e0b7962587d7d687f4e6c7e680d526f74796869526068796562690a526f7479686952787f610762636c6f627f79076263687f7f627f06626361626c6909626361626c696863690b626361626c697e796c7f790a62637d7f626a7f687e7e09626379646068627879034a4859045d425e59076463696875426b0b527e646a636c79787f68300b526f74796869526f6269741262637f686c69747e796c79686e656c636a68066168636a796506787d61626c6908607e5e796c79787e0b52526c6e5279687e7964690007607e5962666863017b03787f61076b627f7f686c61037e69640d7e686e44636b6245686c69687f0b7f687e7d62637e68585f410861626e6c796462630465627e79116a68795f687e7d62637e6845686c69687f0a7520607e207962666863037e686e0e607e43687a596266686341647e790464636479', 'round', 'innerHTML', 'appCodeName', 'defineProperties', 'Could\x20not\x20dynamically\x20require\x20\x22', 'push', '__destrObj', 'toElementDescriptors', 'defaultProps', 'fromElementDescriptor', 'documentMode', 'Object.keys\x20called\x20on\x20non-object', 'WEBGL_debug_renderer_info', 'reduce', 'replace', 'setUserMode', 'changedTouches', 'JS_MD5_NO_WINDOW', '[object\x20Object]', 'item', 'beforeunload', '%27', 'enableTrack', 'envcode', 'gpu', 'ubcode', 'getParameter', 'undefined', 'start', 'isGeneratorFunction', 'completion', 'getOwnPropertySymbols', 'next', 'availWidth', 'values', 'oscpu', 'Tw\x20Cen\x20MT', 'sort', 'bluetooth', 'requestMediaKeySystemAccess', 'keyboardList', 'key', 'window', 'Unfinished\x20UTF-8\x20octet\x20sequence', 'setMetadata', 'static', 'debug', 'languages', 'toolbar', 'msDoNotTrack', 'reject', 'finishers', '208XZrBOJ', 'UNMASKED_RENDERER_WEBGL', 'external', 'shadowBlur', 'POST', '484e4f4a403f5243003e0d23e5c579310000006248d1745c000002cc0d140001110200070000131400021100020700012447000a11000211000107000016110200070002131400031100030700012447000a11000311000107000316110200070004131400041100040700012447000a110004110001070005161100014205000000003b001400010114000211010f3247000911010112000614010f11010f110101120007254700040014000211010244004a12000843001400030d1101001200094a12000a030043010e000b11010012000c4a12000a030043010e000d11010012000e4a12000a030043010e000f1101001200104a12000a030043010e001114000411000412000b12001203002533000c11000412000d12001203002533000c11000412000f12001203002533000c110004120011120012030025470002084211000412000b12001203101a11000412000d120012030c1a1811000412000f12001203041a1811000412001112001203081a181400051100031101031200131101041200141200150403e81a182747003a1101031200161101041200141200170404001a27470020110103120016110005181101030700163549021101054300490014000245000045001d11000311010315001311000511010315001602110105430049001400021100024700f703021400060d1100040e00181100060e00191400070d11000707001a1611010412001b11000707001a1307001b1607000111010244004a12000843001811000707001a1307001c1611010012001d11000707001a1307001d16030011000707001a1307001e160d11000707001f161101064a12002011000707001f13021100014300430249021101071101081200210211010911010a4a120022110007430111010b120023430243021400081101041200240700251314000911000932470002084211010f110101120026254700190211010c110009110008430214000a11000a3247000045000f0211010d1100091100080d0043044908420027052121223c31000821210a2230373c310721210230371c310b21210a2230373c310a23670921210230373c3103670727203b3b3c3b3205333920263d07323021013c383008383a2330193c2621062625393c3630063730183a23300936393c363e193c262107373016393c363e0c3e302c373a342731193c26210a37301e302c373a3427310b3436213c233006213421300b223c3b313a2206213421300639303b32213d0326013805212734363e08203b3c21013c3830033436360a203b3c2114383a203b210837303d34233c3a2707382632012c253003221c1103343c3109213c3830262134382507343c31193c26210b25273c2334362c183a313006362026213a38063426263c323b0f0210170a1110031c16100a1c1b131a092621273c3b323c332c043f263a3b0a2730323c3a3b163a3b33092730253a272100273904302d3c21', 'metadata', 'productSub', 'mark', '\x20is\x20not\x20an\x20object.', 'substr', 'Invalid\x20attempt\x20to\x20spread\x20non-iterable\x20instance.\x0aIn\x20order\x20to\x20be\x20iterable,\x20non-array\x20objects\x20must\x20have\x20a\x20[Symbol.iterator]()\x20method.', 'Gulim', 'experimental-webgl', 'setRequestHeader', 'charging', '484e4f4a403f52430031032581faca6c0000000093505d60000001c60700001400010d1400020700011100020700021607000311000207000416070005110002070006161100021101021314000307000714000403001400061101011200081100060303182a4700b11101014a1200091700062143010400ff2e03102b1101014a1200091700062143010400ff2e03082b2f1101014a1200091700062143010400ff2e2f1400051100041100034a12000a1100050500fc00002e03122c43011817000435491100041100034a12000a110005050003f0002e030c2c43011817000435491100041100034a12000a110005040fc02e03062c43011817000435491100041100034a12000a110005033f2e430118170004354945ff3f110101120008110006190300294700b41101014a1200091700062143010400ff2e03102b110101120008110006294700161101014a12000911000643010400ff2e03082b45000203002f1400051100041100034a12000a1100050500fc00002e03122c43011817000435491100041100034a12000a110005050003f0002e030c2c4301181700043549110004110101120008110006294700161100034a12000a110005040fc02e03062c430145000311000118170004354911000411000118170004354911000442000b011441686b6a6d6c6f6e616063626564676679787b7a7d7c7f7e717073484b4a4d4c4f4e414043424544474659585b5a5d5c5f5e51505319181b1a1d1c1f1e1110020614025a19416d424d594e411d73625a786b111906644f5f5e1a1f7160187b1b1c027e7c68456c401e67654b4658707d66795c53446f4363475b505110617f6e4a487a5d6a4c14025a18416d424d594e411d73625a786b111906644f5f5e1a1f7160187b1b1c047e7c68456c401e67654b4658707d66795c53446f4363475b505110617f6e4a487a5d6a4c14025a1b0006454c474e5d410a4a41485b6a464d4c685d064a41485b685d', 'toString', 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx', 'warn', 'parse', 'sent', 'iterator', 'PDF.PdfCtrl.', '__ac_testid', 'clickList', 'create', 'hasOwnProperty', 'init', 'An\x20initializer', 'clientHeight', 'extras', '__esModule', 'sessionStorage', 'kKeyboardFast', 'devicePixelRatio', ';\x20path=/;', 'location', 'GREEN_BITS', '484e4f4a403f524300192d11257a6fc000000000d4cf00750000006703011400011101004a12000011010603062b1100012f43011400021101004a1200001101014a1200011101014a12000243000401001a4301430114000302110102110003110105430214000411000211000318110004181400050211010311000507000343024200040c5f4b56547a51584b7a565d5c055f5556564b064b58575d5654024a08', 'Castellar', 'toGMTString', 'mozHidden', 'Set', '[native\x20code]', 'complete', '484e4f4a403f52430012270f0b8aa329000000005cddda270000008a0211010043003247007e1101014a12000007000143011400011100011200024a12000343004a120004110104070005070006440207000743024a120008070009430103002734002c1101021200034a12000343004a120004110104070005070006440207000743024a120008070009430103002734001011010212000a4a120003430007000b26420142000c0d18091e1a0f1e3e171e161e150f06181a150d1a08090f143f1a0f1a2e2937080f14280f0912151c07091e0b171a181e03270851011c000712151f1e03341d0a151a0f120d1e18141f1e070b170e1c12150814201419111e180f5b2b170e1c12153a09091a0226', 'filter', 'runClassFinishers', 'webkitVisibilityState', '2.11.0', '\x20after\x20decoration\x20was\x20finished', 'suffixes', 'completed', 'src', 'Cannot\x20instantiate\x20an\x20arrow\x20function', 'getTime', 'resultName', 'children', 'accessor.get', '484e4f4a403f52430024153c4037f00000000009d722c1e5000000d200110208150002084202110100430047001c1101014a120000070001430114000105000000003b001100011500030211010243004700553e002b140002110002120004110104070005132533000c11010312000612000703002547000700110108150002413d00241101031200064a12000807000907000a4302491101031200064a12000b0700094301494102110105430047002311010312000c3233000f11010312000d34000611010312000e4700070011010815000211010612000f11010812000203022b2f11010607000f35490842001004637c69620478697f780965626f636b62657863076362697e7e637e046f636869125d5943584d5349544f494948494853495e5e0e7f697f7f6563625f78637e6d6b69066069626b7864077f697845786961107f63616947697544697e694e75786968000a7e6961637a69457869610965626869746968484e0c5c63656278697e497a6962780e415f5c63656278697e497a6962780769627a6f636869', 'https://mssdk.bytedance.com/websdk/v1/getInfo', '?q=', '484e4f4a403f5243003e19390bcd41790000000084fc29f10000006111010012000033000d1101001200001200010700022347000303014211010112000311010112000412000326470003030142110101120005110101120006264700030301421101011200071200081101021200071200082447000303014203024200090c3125363a32123b323a3239230723363019363a32061e1105161a12083b383436233e3839062736253239230424323b3103233827063125363a3224063b323930233f', 'defineProperty', '[object\x20Generator]', 'Wingdings', 'hasInstance', 'SHADING_LANGUAGE_VERSION', 'platform', '484e4f4a403f52430007152cc2c1a53c00000061235466970000007f1100010700022534000711000107000325340007110001070004253400071100010700052547000200423e0004140002413d002b1102021100011333001b11020211000113120006082634000c11020211000113120007082647000200424108421101014a12000011010243014a12000105000000003b0143011401000842000813717362596178466479667364626f58777b73650465797b7308757370457e77646608557370457e77646605737977667f16737941737454647961657364527f65667762757e73640f747f787259747c73756257656f78750e7f65535941737454647961657364', 'asyncIterator', 'object', 'colorDepth', 'keys', 'wrapped', 'getter', 'finalized', 'all', 'appName', 'regionConf', 'application/x-www-form-urlencoded', 'attempted\x20to\x20get\x20private\x20field\x20on\x20non-instance', 'createEvent', 'body', 'toElementFinisherExtras', 'open', 'displayName', '_urlRewriteRules', 'random', 'font', '484e4f4a403f52430031033191576c4000000000940c5eb50000005302110100430032470047070000110101363234000b110101120000110102373234000707000111010336340007070002110103363400070700031101033634000f0700041101033607000511010336274201420006077d61786a64637e08527d656c637962600b6e6c61615d656c637962600b525263646a6579606c7f68054c78696462184e6c637b6c7e5f686369687f64636a4e6263796875793f49', 'discharingTime', 'SimSun-ExtB', 'fromClassDescriptor', 'versions', 'charCodeAt', 'fillText', 'fetch', 'freeze', 'Create\x20WebSocket', 'webkitRTCPeerConnection', 'digest', 'class', 'MAX_VERTEX_UNIFORM_VECTORS', 'filename', 'first', 'visibilitychange', 'A\x20class\x20descriptor', 'clientWidth', 'frontierSign', 'msVisibilityState', 'antialias', '484e4f4a403f5243002a3d04fa03273900000000b93145d7000004061101001200004a12000143001400011101001200024a120001430014000203001400030301140004030214000503031400060304140007030514000811000814000907000314000a07000414000b07000514000c07000614000d07000714000e07000814000f07000914001007000a1400111100014a12000b07000c430103002a34000f1100014a12000b07000d430103002a4700091100071400094500de1100014a12000b11000a430103002a4700091100031400094500c31100014a12000b11000c430103002a4700091100041400094500a81100014a12000b11000d430103002a34000f1100014a12000b07000e430103002a34000f1100014a12000b07000f430103002a4700091100051400094500691100014a12000b11000e430103002a34000f1100014a12000b11000f430103002a34000f1100014a12000b110010430103002a34000f1100014a12000b070010430103002a34000f1100014a12000b070011430103002a4700091100061400094500061100081400091100024a12000b11000b430103002a33000711000911000326470005004245012c1100024a12000b11000d430103002a34000f1100024a12000b11000c430103002a34000f1100024a12000b070012430103002a330007110009110005263300071100091100042647000500424500dd1100024a12000b110011430103002a34000f1100024a12000b11000f430103002a34000f1100024a12000b110010430103002a34000f1100024a12000b11000e430103002a3300071100091100072633000711000911000626470005004245007c1100024a12000b11000b430103002733000f1100024a12000b11000d430103002733000f1100024a12000b110011430103002733000f1100024a12000b11000e430103002733000f1100024a12000b11000f430103002733000f1100024a12000b1100104301030027140012110012110009110008252647000200420300140013030114001403021400150303140016030414001703051400181100181400191100014a12000b070013430103002a47000911001514001945008a1100014a12000b070014430103002a34000f1100014a12000b070015430103002a34000c1100014a12000b070016430147000911001414001945004e1100014a12000b070017430103002a4700091100131400194500331100014a12000b070018430103002a34000f1100014a12000b070019430103002a4700091100171400194500061100181400190211010143004a120001430014001a110019110013243300071100191100142433002111010212001a34001811010012001b4a12001c43004a12000b07001d430103002a4700020042110019110013243300071100191100142433000f11001a4a12000b07001a430103002a47000200420142001e090b0d1b0c3f191b100a0b0a113211091b0c3d1f0d1b080e121f0a18110c13070917101a11090d03091710071f101a0c11171a051217100b0606170e1611101b04170e1f1a04170e111a03131f1d0717101a1b06311809131f1d17100a110d160c131f1d210e11091b0c0e1d57041d0c110d03064f4f051d0c17110d05180617110d040e17151b0818170c1b1811065106110e1b0c1f51055e110e0c51055e110e0a51071d160c11131b51080a0c171a1b100a5104130d171b061d160c11131b06081b101a110c080a112d0a0c1710190639111119121b', 'setTTWebid', 'Buffer', '_enablePathListRegex', 'outerWidth', 'type', 'decorateElement', '484e4f4a403f524300040e131c85d3950000064d665eaab9000007ca05000001d03b0014000105000003953b00140002050000046a3b001400031102084400140004021100024300490211000343004907004b07004c07004d07004e07004f07005007005107005207005307005407005507005607005707005807005907005a07005b07005c0c001214000702110209110200110007030043031400051100050211020911020007005d1307005e0c000111000712005f43032f17000535490700600c00011400080211020911020607006113110008030043031400060d1400090211020a4300110009070062160211020b4300110009070063160211020c43001100090700641607001811020844004a1200654300181100090700661611020d4a1200671100044a12006843001d033c1b4301110009070069160211020e430011000907006a160211020f43004a120008430011000907001d1611000511000907006b1611000611000907006c1602110210430011000907006d1602110001430011000907006e1602110211430011000907006f16030114000a11021212007011000907007016021102130700714301110009070072160211021307007343011100090700741611000a1100090700751603001100090700761611021412007711000907007716110009423e000714000a030042413d01b6030014000111030007000013340014110301070001134a07000213070003430103002a47000607000445000203001400020700051103023a24470006070006450002030014000311030307000713070008134a0700091311030007000a1343014a0700021307000b430103002934002e11030007000c1333000b11030007000c1307000d1333001607000e11030007000c1307000d134a0700081343002534000711030007000f131400041100044700060700104500020300140004110004330011110301070001134a0700111307001243014700060700134500020300140005110300070014133300041100023247000607001545000203001400060211030443001400071100073233000711030007001613470006070017450002030014000807001814000911000247000b11000103012f170001354911000347000e110001030103012b2f170001354911000847000e110001030103022b2f170001354911000747000e110001030103032b2f170001354911000647000e110001030103042b2f170001354911000547000e110001030103052b2f170001354911000447000e110001030103062b2f17000135491100014241084211030512001907001a133247000c030011030512001907001a163e0010140003030111030512001907001a16413d004911030007001b1347003e11030007001b1344001400011103064a07001c1307001d43014a07001e1307001f430114000205000004103b0011000107002316070024110001070025164108423e0010140002030111040512001907001a16413d00421101024a070020131101010300030043034903001101024a0700211303000300030103014304070022130303132514000103021100011811040512001907001a164108420c000014000107002607002707002807002907002a07002b07002c07002d07002e07002f0700300700310700320700330700340700350700360700370700380700390c001414000211030107003a133247000e07003b11030512001907003c35423e001214000507003d11030512001907003c3542413d003b05000005203c021400031100024a0700481305000005c63b0243011400041103074a0700491311000443014a0700401305000005d33b0043014941084211050107003a134a07003e130d1100010e003f43014a0700401305000005523b0143014a07004513050000059e3b014301421100010700411307004248001007004348001607004448001c49450024030111030111010216450021030211030111010216450015030011030111010216450009030511030111010216084203011d110001070046134a0700021307004743012647000503044500020303110301110102160842021101031100011100024302421101014a07004a13070018430111040512001907003c35420d140001110214070078131400021100020700182447000a11000211000107007816110214070079131400031100030700182447000a11000311000107007a1611021407007b131400041100040700182447000a11000411000107007c161100014205000000003b0014000105000005eb3b0014000202110115430049021101164300490211011743004902110118430049021101194300491101034a12007d1101051200190211000143004302491101034a12007d11010512007e0211011a43004302491101034a12007d11010512007f0211011b43004302491101034a12007d1101051200800211000243004302491101141200814a120082030043011400030d1100030e00831400040700841400050211011c0211011d1100054301030a430214000611000647000e110006030118170006354945000503011400060211011e11000511000643024911000611010507001913070085161101034a12007d1100041101054302490211011f1101204a1200861100044301110121120087430214000702110122110123120088110007430214000811011212008907008a1314000911000932470002084211012447001b1101244a120040021101251100091100080d00430443014945000f021101251100091100080d004304490842008b051b0411061509010711063513111a00071d1a10110c3b1205543b24265b053b0411061509011a1011121d1a111007321d0611121b0c0904061b001b000d041108001b2700061d1a1304171518180b3c20393831181119111a000b371b1a0700060117001b060607151215061d100401071c3a1b001d121d1715001d1b1a212f1b161e1117005427151215061d2611191b00113a1b001d121d1715001d1b1a290f350404181124150d271107071d1b1a0627151215061d05191500171c0537061d3b270a371c061b1911543d3b2706171c061b191106371c061b19110a27000d18113911101d1504311013110003033d3004181b1510053d191513110d17061115001131181119111a000617151a0215070a131100371b1a00110c0002461009100615033d191513110c1311003d19151311301500150410150015061b1a181b15104e101500154e1d191513115b131d124f16150711424058264418333b30181c35253536353d35353535353535245b5b5b0d3c41363531353535353538353535353535363535313535353d3626353543030706170b13111b181b1715001d1b1a0d1a1b001d121d1715001d1b1a07040401071c04191d101d061715191106150a191d17061b041c1b1a1107070411151f11060b1011021d1711591d1a121b0f1615171f13061b011a1059070d1a170916180111001b1b001c12041106071d0700111a005907001b06151311141519161d111a0059181d131c005907111a071b060d151717111811061b191100110609130d061b07171b04110c1915131a11001b19110011060917181d04161b150610141517171107071d161d181d000d591102111a00070e17181d04161b15061059061115100f17181d04161b1506105903061d00110f04150d19111a00591c151a101811060b041106191d07071d1b1a070142031a1504014305050111060d041a15191104001c111a0507001500110604061b190400071306151a0011100610111a1d111005171500171c0719110707151311301d07541a1b005415540215181d1054111a0119540215180111541b1254000d041154241106191d07071d1b1a3a1519110319150403151818041e1b1d1a0e2c301b19151d1a261105011107000b170611150011241b040104130611191b02113102111a00381d0700111a11060d13181b16151827001b061513110c1b04111a3015001516150711091d1a10110c111030360b15000015171c3102111a000d3517001d02112c3b161e1117000d101d07041500171c3102111a000b15101036111c15021d1b06101510103102111a00381d0700111a11060b10110015171c3102111a0009121d06113102111a001039010015001d1b1a3b16071106021106133c20393839111a013d00111931181119111a00093d1a004c350606150d0b041b0700391107071513110d050111060d2711181117001b060b041106121b0619151a1711031a1b030618111a13001c0b171b1a00110c0039111a010f101b170119111a0031181119111a000c1a15001d021138111a13001c0b1e07321b1a0007381d07000b070d1a00150c3106061b0607131100201d191109001d191107001519040512181b1b0611131100201d19110e1b1a113b121207110008001d19110e1b1a11051915131d17060324061b0407061024061b0407031e07020b16061b03071106200d0411061d120615191103151d10050000171d100617181d111a000700002b07171d1005001b1f111a07190713200d04110b04061d0215170d391b101107151d10381d0700050000031d100800002b0311161d100700002311163d100b00002b0311161d102b02460900002311161d102246061507071d131a07041801131d1a070607170611111a06170107001b190e19073a1103201b1f111a381d0700060704181d171109001b1f111a381d0700040c19071d051d1a10110c090700061d1a131d120d041e071b1a0f2331362b3031223d37312b3d3a323b0a0611131d1b1a371b1a12090611041b0600210618', 'dev', '[object\x20Function]', 'substring', '_invoke', 'getBattery', 'boeHost', 'asgw', 'Generator', 'boe', 'decorators', 'exports', 'accessor', 'plugins', 'this\x20hasn\x27t\x20been\x20initialised\x20-\x20super()\x20hasn\x27t\x20been\x20called', 'delegate', 'attempted\x20to\x20set\x20read\x20only\x20static\x20private\x20field', 'callback=', 'MAX_TEXTURE_MAX_ANISOTROPY_EXT', 'toStringTag', ';\x20expires=', 'match', 'root', 'setItem', 'getContextAttributes', 'withCredentials', 'Class\x20\x22', 'mozvisibilitychange', 'userLanguage', 'createHash', 'map', 'Cannot\x20destructure\x20', 'hBytes', '[object\x20Number]', 'floor', 'access', '484e4f4a403f5243001a3309b621c6a00000000048c0ec7f000000650d14000111010012000047000c1101001200001400014500090211010143001400011101024a1200014300110001150002021101030304430114000211000202110104021101051101064a12000311000143011100024302070004430218140003110003420005077563656f6860690348495109524f4b435552474b56095552544f48414f405f40676465626360616e6f6c6d6a6b686976777475727370717e7f7c474445424340414e4f4c4d4a4b484956575455525350515e5f5c16171415121310111e1f0b08', 'MAX_COMBINED_TEXTURE_IMAGE_UNITS', 'default', 'public', 'vendorSub', 'touchstart', 'getExtension', 'Aparajita', 'finalize', 'propertyIsEnumerable', 'end', 'localStorage', '@@iterator', 'deviceMemory', 'kHttp', '@@toStringTag', 'cookieEnabled', 'fromCharCode', 'done', 'set', 'createDataChannel', 'stringify', 'async', 'Descriptor', 'hashed', 'ontouchstart', 'onicegatheringstatechange', 'getOwnPropertyDescriptors', 'then', 'function', 'CordiaUPC', 'T_CLICK', 'EXT_texture_filter_anisotropic', 'MS\x20Outlook', '80pSJSjK', 'Jokerman', 'byted_acrawler', 'visibilityState', 'span', 'isWebmssdk', '__web_idontknowwhyiwriteit__', 'cpuClass', 'serif', 'initialized', 'accessor\x20decorators\x20must\x20return\x20an\x20object\x20with\x20get,\x20set,\x20or\x20init\x20properties\x20or\x20void\x200', '[object\x20HTMLAllCollection]', 'Decorating\x20class\x20property\x20failed.\x20Please\x20ensure\x20that\x20proposal-class-properties\x20is\x20enabled\x20and\x20runs\x20after\x20the\x20decorators\x20transform.', '\x27\x20to\x20be\x20a\x20function', 'VERSION', 'toLowerCase', 'MAX_TEXTURE_SIZE', 'triggerUnload', 'MAX_VERTEX_TEXTURE_IMAGE_UNITS', 'element', 'apply', 'document', 'exec', 'send', ')\x20can\x27t\x20be\x20decorated.', 'min', '484e4f4a403f5243002814122ddd79950000009eb285a1cb000000e811000114000402110201110001430147007c1102021200041400051100050700052347000f0700061102021200060c00024500120700061102021200060700041100050c0004140006021102030211020411000143011100064302140007021102051100074301140008021102061100080700054302140009021102031100071101011100090c000243021400040211010211000411000211000343034205000000003b03140003070000140001110100120001082334000611010012000247000208421101001200011400021100021101001500030011010015000211000311010015000108420007070d78173a322026043a25303b150a0a34360a3c3b2130273630252130310a3a25303b050a3a25303b0b0a0a34360a213026213c3100073826013a3e303b', 'vivobrowser', 'descriptor', 'language', '484e4f4a403f524300023a25866a0150000000002b0aa01b000001541101001200004a12000143001400011100014a120002070003430103002a47000201420700041400021101013a070004254700060700044500090211010211010143011100022534000d1101014a1200054300070006263400161101031200071200054a12000811010143010700062634001e1101043a07000425470006070004450009021101021101044301110002253400151101044a12000543004a120002070009430103002734001e1101003a070004254700060700044500090211010211010043011100022534000d1101004a120005430007000a263400121101001200004a12000207000b430103002a34001e1101053a07000425470006070004450009021101021101054301110002254700020042021101064300324700331101073a070004254700060700044500090211010211010743011100022534000d1101074a120005430007000c2647000200420142000d096f697f685b7d7f746e0b6e7556756d7f68597b697f0773747e7f62557c087f767f796e687574096f747e7f7c73747f7e086e75496e6873747d0f417578707f796e3a4d73747e756d47096a68756e756e636a7f04797b7676085e75796f777f746e12417578707f796e3a547b6c737d7b6e7568470570697e757710417578707f796e3a5273696e75686347', 'vendor', 'level', 'attempted\x20to\x20call\x20', 'placement', 'JS_MD5_NO_NODE_JS', 'initializeClassElements', 'indexedDB', 'perf', 'disallowProperty', 'lime', '484e4f4a403f524300341b3e336a785800000000dbd5951f000001b50114000111010012000000254700070014000145001b1101001200000125470007011400014500090211010143001400010d010e0001010e0002010e00031100010e0004010e0005010e0006010e0007010e0008010e0009010e000a010e000b000e000c1400020211010243001100021500051100021200053247005c021101031100024301490211010411000243014902110105430011000215000702110106430011000215000802110107430011000215000902110108430011000215000b0211010943001100021500030211010a4300110002150002030014000311000303012f170003354911000311000212000b03012b2f170003354911000311000212000a03022b2f170003354911000311000212000903032b2f170003354911000311000212000803042b2f170003354911000311000212000703052b2f17000335491100031100020700061303062b2f170003354911000311000212000503072b2f17000335491100031100020700041303082b2f170003354911000311000212000303092b2f1700033549110003110002120002030a2b2f170003354911010b12000d1100032f11010b07000d354911000242000e0e547b6a796a66587c627f686344650a6f62796e687f58626c650a6864657862787f6e657f086764686a7f62646506787c627f6863036f6466086f6e697e6c6c6e790465646f6e077b636a657f6466097c6e696f79627d6e7909626568646c65627f640463646460047f6e787f076e657d68646f6e', 'moveList', 'MYRIAD\x20PRO', 'An\x20element\x20descriptor', 'finallyLoc', 'head', 'innerHeight', 'ORIGIN:\x20', 'region', 'addInitializer', '[object\x20SafariRemoteNotification]', 'candidate', 'content-type', 'userAgent', 'webkitHidden', 'test', 'getPrototypeOf', 'setDate', 'shiftKey', 'accessor.init', '@@asyncIterator', 'accessor.set']; w_0x42f5 = function () { return _0x458f30; } ; return w_0x42f5(); } function w_0x5c3140(_0x41676a, _0x3f9548, _0x39b0b8) { var _0x2145df = w_0x25f3; function _0x467cb0(_0x174e0d, _0x4ea737) { var _0x569e4c = w_0x25f3 , _0x3ed868 = parseInt(_0x174e0d[_0x569e4c(0x2a5)](_0x4ea737, _0x4ea737 + (0x30 * 0x1a + 0x1 * 0x1424 + -0xc2 * 0x21)), -0x253b + -0x1ce1 + -0x974 * -0x7); return _0x3ed868 >>> 0x23ca + 0x882 + 0x2c45 * -0x1 == 0x11cb + -0x5c7 * 0x3 + 0x2 * -0x3b ? [-0xc * 0x300 + 0x1 * 0x1067 + 0x139a, _0x3ed868] : _0x3ed868 >>> -0x3 * 0x3b9 + 0x4a * 0x75 + -0x16a1 == -0x742 + -0x429 + 0xb6d ? (_0x3ed868 = (-0x1eee + 0x1175 + 0xdb8 & _0x3ed868) << 0x2 * 0x108d + 0x2 * -0x5e6 + 0x1 * -0x1546, [-0x949 + 0xe2f + -0x4 * 0x139, _0x3ed868 += parseInt(_0x174e0d['slice'](_0x4ea737 + (0x5 * -0x643 + -0x1 * 0x95 + -0x1fe6 * -0x1), _0x4ea737 + (0x9b6 + 0x8 * 0x24a + -0x1c02)), -0xa6 * 0x11 + -0x3f * -0x1 + 0x6f * 0x19)]) : (_0x3ed868 = (0x1af1 + 0x1442 + -0x964 * 0x5 & _0x3ed868) << 0x3f3 + -0xd93 + 0x9b0, [-0x13 * -0x192 + -0x1588 + 0xc1 * -0xb, _0x3ed868 += parseInt(_0x174e0d[_0x569e4c(0x2a5)](_0x4ea737 + (-0x8 * 0x39c + 0x59 * 0x35 + 0x1 * 0xa75), _0x4ea737 + (0x1 * -0xad6 + -0x326 + 0x16 * 0xa3)), 0x20b0 + -0x7d + -0x1b1 * 0x13)]); } var _0x450bf1, _0x55d729 = 0x242c + 0x150c * 0x1 + -0x4 * 0xe4e, _0x26f45f = [], _0x408609 = [], _0x5e254d = parseInt(_0x41676a[_0x2145df(0x2a5)](-0x43 * 0x7a + 0xbac * -0x1 + 0x2 * 0x15cd, -0x18c6 + -0x1515 + 0x2de3), -0x17e6 + 0x4 * -0x269 + 0x1fa * 0x11), _0x56844b = parseInt(_0x41676a[_0x2145df(0x2a5)](0x262 * -0x3 + 0x19e0 + -0x12b2, 0x5 * 0x3e + 0xd * -0x2ec + -0x73 * -0x52), 0x1 * 0xd69 + 0x1 * 0xb9 + -0xe12); if (-0x5eec40a * 0xd + 0x353b2368 + 0x60332064 !== _0x5e254d || 0x7b7 * 0x44245 + -0x712c7271 + 0x1ce9b3ad * 0x5 !== _0x56844b) throw new Error(_0x2145df(0x2a7)); if (0x2 * 0xae1 + -0x1e47 + 0x885 !== parseInt(_0x41676a[_0x2145df(0x2a5)](0x1 * 0x45 + 0x149 * -0x1 + 0x114, -0x1d69 * -0x1 + 0xf47 * 0x1 + -0x2c9e), -0x59 * 0x4b + -0xfdb + -0xd7 * -0x32)) throw new Error('ve'); for (_0x450bf1 = 0x2 * 0xe7d + -0xdf * -0x19 + 0x3d * -0xd5; _0x450bf1 < -0x22db * -0x1 + -0xf98 * -0x1 + -0x1 * 0x326f; ++_0x450bf1) { _0x55d729 += (-0x1a7 * 0x3 + 0x17 * -0x47 + 0xb59 & parseInt(_0x41676a[_0x2145df(0x2a5)](0x751 * -0x4 + 0x1d * 0xfd + 0xb3 + (-0x44b * 0x7 + -0x100 + 0x1f0f) * _0x450bf1, 0x8 * 0x3ae + 0x128d + -0xd * 0x3af + (0x2 * -0x80f + -0xd * 0x24a + 0x2de2) * _0x450bf1), -0x14d6 + -0x255 + 0x173b)) << (-0x1ce2 * -0x1 + 0x11e3 + -0x2ec3) * _0x450bf1; } var _0x537efa = parseInt(_0x41676a[_0x2145df(0x2a5)](0x13 * -0x209 + 0x116b + 0xc * 0x1c8, -0x26a0 + 0x10d * -0x12 + 0x39b2), 0x154e + -0x25e * -0xf + 0xe3 * -0x40) , _0x3d66d8 = (0x1bdb * 0x1 + -0x4e9 * 0x3 + -0xd1e) * parseInt(_0x41676a[_0x2145df(0x2a5)](-0x1 * -0x24f5 + 0x2279 + 0x67a * -0xb, -0x2 * -0x1083 + -0x5 * 0x185 + 0xef * -0x1b), -0x82 + 0x8e6 + 0x4 * -0x215); for (_0x450bf1 = -0x2471 + -0x116 * -0x7 + 0x1d0f * 0x1; _0x450bf1 < _0x3d66d8 + (-0x1c * -0x106 + 0x370 + -0x8 * 0x3fc); _0x450bf1 += 0x13ac * 0x1 + 0x9bd * 0x1 + -0x1 * 0x1d67) _0x26f45f[_0x2145df(0x36e)](parseInt(_0x41676a[_0x2145df(0x2a5)](_0x450bf1, _0x450bf1 + (0x7 * 0x539 + 0x3cb * -0x2 + -0x1 * 0x1cf7)), 0x23fc + -0xa45 + -0x3 * 0x88d)); var _0x50f001 = _0x3d66d8 + (-0x1429 + -0x69d * -0x1 + 0x371 * 0x4) , _0x23f32e = parseInt(_0x41676a[_0x2145df(0x2a5)](_0x50f001, _0x50f001 + (-0x179c + -0x3f + -0x61 * -0x3f)), -0x2a * 0x65 + -0x20e5 + 0x3187); for (_0x50f001 += -0x20c6 + 0x3 * -0x1a5 + 0x25b9, _0x450bf1 = -0x2 * -0x766 + 0x127e + -0x214a; _0x450bf1 < _0x23f32e; ++_0x450bf1) { var _0x37a7c1 = _0x467cb0(_0x41676a, _0x50f001); _0x50f001 += (0xcfd + 0x399 * -0x3 + -0x7 * 0x50) * _0x37a7c1[-0x14ee + -0x912 + 0x1e00]; for (var _0x13d988 = '', _0x4b0c3a = -0x1bcf + 0x16cf + 0x10 * 0x50; _0x4b0c3a < _0x37a7c1[-0x476 * 0x3 + -0x112b + 0x2 * 0xf47]; ++_0x4b0c3a) { var _0x49abb4 = _0x467cb0(_0x41676a, _0x50f001); _0x13d988 += String[_0x2145df(0x1e2)](_0x55d729 ^ _0x49abb4[0x18f1 * -0x1 + -0x17e5 * 0x1 + -0x30d7 * -0x1]), _0x50f001 += (0x2dd * -0xd + -0x8 * 0x227 + 0x3673) * _0x49abb4[-0xd9 + 0x1702 + -0x763 * 0x3]; } _0x408609['push'](_0x13d988); } return _0x3f9548['p'] = null, function _0x970e7(_0x3e87c6, _0x536685, _0xecd4ef, _0x5acf67, _0x560641) { var _0x261736 = _0x2145df, _0x1fc1ed, _0x281ec0, _0x5c0f33, _0xd55c82, _0x37b70b, _0x12cd72 = -(0x1bdd + -0x8 * 0x116 + -0x3 * 0x664), _0x27fcf3 = [], _0x42ef1a = [-0x5 * -0x2b6 + 0x1d6c + -0x2afa, null], _0x428c87 = null, _0x4444cf = [_0x536685]; for (_0x281ec0 = Math['min'](_0x536685[_0x261736(0x259)], _0xecd4ef), _0x5c0f33 = -0x90b + -0x1 * 0x1e2f + 0x273a; _0x5c0f33 < _0x281ec0; ++_0x5c0f33) _0x4444cf[_0x261736(0x36e)](_0x536685[_0x5c0f33]); _0x4444cf['p'] = _0x5acf67; for (var _0x4b7ccd = []; ;) try { var _0x5cd5b9 = _0x26f45f[_0x3e87c6++]; if (_0x5cd5b9 < -0x788 + 0x18c7 + 0x1 * -0x1118) { if (_0x5cd5b9 < 0x208d + -0x1031 + -0x1049 * 0x1) { if (_0x5cd5b9 < 0x1a1c + -0xbff * 0x3 + 0x27a * 0x4) _0x5cd5b9 < 0x3ee + 0x1 * 0x21c3 + -0x2e6 * 0xd ? _0x27fcf3[++_0x12cd72] = _0x5cd5b9 < -0x61 * 0x61 + 0x1115 + 0x13ad || -0x1d * -0x4f + 0x11c9 + 0x3 * -0x8e9 !== _0x5cd5b9 && null : _0x5cd5b9 < -0x783 + 0xa0d + -0x285 ? -0xb * 0x24f + 0x141a + 0x54e === _0x5cd5b9 ? (_0x1fc1ed = _0x26f45f[_0x3e87c6++], _0x27fcf3[++_0x12cd72] = _0x1fc1ed << 0x12b * 0x1f + 0x5 * 0x70c + -0x4759 >> -0x352 + 0x7 * 0x58f + -0x237f) : (_0x1fc1ed = (_0x26f45f[_0x3e87c6] << 0xb02 + 0x2af + -0xda9) + _0x26f45f[_0x3e87c6 + (0x220e + 0x1 * -0x1175 + -0x1098)], _0x3e87c6 += -0x325 * 0x3 + 0xafe + -0x18d, _0x27fcf3[++_0x12cd72] = _0x1fc1ed << -0xe7c * -0x1 + -0xe17 * -0x2 + -0x2a9a >> -0x23ae * 0x1 + -0x1f8f + -0x434d * -0x1) : -0xa * 0x15c + 0x24bc + -0x1 * 0x171f === _0x5cd5b9 ? (_0x1fc1ed = ((_0x1fc1ed = ((_0x1fc1ed = _0x26f45f[_0x3e87c6++]) << -0x1f87 + -0xda8 + 0x2d37) + _0x26f45f[_0x3e87c6++]) << -0x1d32 + -0x2 * 0x124 + 0x1f82) + _0x26f45f[_0x3e87c6++], _0x27fcf3[++_0x12cd72] = (_0x1fc1ed << 0xc * -0x8 + -0x25f7 + 0x265f) + _0x26f45f[_0x3e87c6++]) : (_0x1fc1ed = (_0x26f45f[_0x3e87c6] << 0x1bf7 + 0x4e9 * 0x7 + -0xc76 * 0x5) + _0x26f45f[_0x3e87c6 + (0xba7 + -0x1611 + 0xa6b)], _0x3e87c6 += 0xa1e + -0x1a21 + 0x557 * 0x3, _0x27fcf3[++_0x12cd72] = +_0x408609[_0x1fc1ed]); else { if (_0x5cd5b9 < 0x369 * -0x9 + 0x20de + -0x220) _0x5cd5b9 < -0x2626 + 0x336 * -0x5 + 0x363f ? 0x12b * -0x15 + -0x2 * -0x11f5 + 0x5ae * -0x2 === _0x5cd5b9 ? (_0x1fc1ed = (_0x26f45f[_0x3e87c6] << 0x8 * 0x2 + 0x35 * 0x18 + -0x500) + _0x26f45f[_0x3e87c6 + (-0x330 + -0xec * -0x5 + -0x16b)], _0x3e87c6 += 0x224 + -0x19 * -0xb3 + -0x1 * 0x139d, _0x27fcf3[++_0x12cd72] = _0x408609[_0x1fc1ed]) : _0x27fcf3[++_0x12cd72] = void (0x5 * 0x1ef + 0x2493 * -0x1 + -0x35d * -0x8) : 0x1abb + 0x191 * 0xd + -0x3 * 0xfaf === _0x5cd5b9 ? _0x27fcf3[++_0x12cd72] = _0x560641 : (_0x1fc1ed = (_0x26f45f[_0x3e87c6] << -0x20b0 + -0x3da + 0x2492) + _0x26f45f[_0x3e87c6 + (-0x129d * 0x1 + 0x1 * -0x2316 + 0x35b4)], _0x3e87c6 += -0xcb8 + 0x1bb * -0xa + -0x2 * -0xf04, _0x12cd72 = _0x12cd72 - _0x1fc1ed + (0xb3 * 0x1 + -0x2018 * 0x1 + 0x1 * 0x1f66), _0x281ec0 = _0x27fcf3[_0x261736(0x2a5)](_0x12cd72, _0x12cd72 + _0x1fc1ed), _0x27fcf3[_0x12cd72] = _0x281ec0); else { if (_0x5cd5b9 < 0x2 * -0xf38 + 0xdd0 + 0x10b1) -0x15d2 + 0x26f0 + 0x11 * -0x101 === _0x5cd5b9 ? _0x27fcf3[++_0x12cd72] = {} : (_0x1fc1ed = (_0x26f45f[_0x3e87c6] << -0x9ac + 0x1201 * 0x1 + -0x84d) + _0x26f45f[_0x3e87c6 + (0x1ecb + -0x1bdd + 0x7 * -0x6b)], _0x3e87c6 += 0x6a4 + -0x922 + 0x280, _0x281ec0 = _0x408609[_0x1fc1ed], _0x5c0f33 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72][_0x281ec0] = _0x5c0f33); else { if (0x17 * -0xc3 + 0x1da5 + 0x3 * -0x405 === _0x5cd5b9) { for (_0x281ec0 = _0x26f45f[_0x3e87c6++], _0x5c0f33 = _0x26f45f[_0x3e87c6++], _0xd55c82 = _0x4444cf; _0x281ec0 > -0x3 * 0x4ba + -0x12 * 0xb1 + 0x1aa0; --_0x281ec0) _0xd55c82 = _0xd55c82['p']; _0x27fcf3[++_0x12cd72] = _0xd55c82[_0x5c0f33]; } else _0x1fc1ed = (_0x26f45f[_0x3e87c6] << 0x15c3 + -0x453 + -0x1168) + _0x26f45f[_0x3e87c6 + (-0x1f3f * 0x1 + 0x1f6a + 0x6 * -0x7)], _0x3e87c6 += 0x1 * -0x1f4e + -0x118f * 0x1 + 0x30df, _0x281ec0 = _0x408609[_0x1fc1ed], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72][_0x281ec0]; } } } } else { if (_0x5cd5b9 < -0xf5c + -0x6d7 + 0x164e) { if (_0x5cd5b9 < 0xc8 * -0x2f + 0xee * -0x9 + -0xf * -0x303) { if (_0x5cd5b9 < 0x1 * -0x2426 + 0x224e + 0x11 * 0x1d) { if (-0xb15 + 0x2d5 * 0x1 + -0x853 * -0x1 === _0x5cd5b9) _0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72][_0x281ec0]; else { for (_0x281ec0 = _0x26f45f[_0x3e87c6++], _0x5c0f33 = _0x26f45f[_0x3e87c6++], _0xd55c82 = _0x4444cf; _0x281ec0 > 0x2 * -0x11b8 + 0x240a + -0x9a; --_0x281ec0) _0xd55c82 = _0xd55c82['p']; _0xd55c82[_0x5c0f33] = _0x27fcf3[_0x12cd72--]; } } else 0x1 * 0x17f1 + 0x115 * -0x9 + 0x2d3 * -0x5 === _0x5cd5b9 ? (_0x1fc1ed = (_0x26f45f[_0x3e87c6] << 0x16ff + 0x158a + -0x2c81 * 0x1) + _0x26f45f[_0x3e87c6 + (0x88f * -0x3 + 0x1c82 + 0x16a * -0x2)], _0x3e87c6 += 0x1 * -0xa7 + 0x18d4 + 0x1 * -0x182b, _0x281ec0 = _0x408609[_0x1fc1ed], _0x5c0f33 = _0x27fcf3[_0x12cd72--], _0xd55c82 = _0x27fcf3[_0x12cd72--], _0x5c0f33[_0x281ec0] = _0xd55c82) : (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x5c0f33 = _0x27fcf3[_0x12cd72--], _0xd55c82 = _0x27fcf3[_0x12cd72--], _0x5c0f33[_0x281ec0] = _0xd55c82); } else { if (_0x5cd5b9 < -0x19f3 + 0x1c6b + -0x25f) { if (0x1b * -0xbd + -0x84a * -0x2 + -0x126 * -0x3 === _0x5cd5b9) { for (_0x281ec0 = _0x26f45f[_0x3e87c6++], _0x5c0f33 = _0x26f45f[_0x3e87c6++], _0xd55c82 = _0x4444cf, _0xd55c82 = _0x4444cf; _0x281ec0 > 0x3 * -0x65a + 0xe28 + 0x4e6; --_0x281ec0) _0xd55c82 = _0xd55c82['p']; _0x27fcf3[++_0x12cd72] = _0xd55c82, _0x27fcf3[++_0x12cd72] = _0x5c0f33; } else _0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] += _0x281ec0; } else 0x44b * -0x9 + 0x61 * -0xa + -0x2a86 * -0x1 === _0x5cd5b9 ? (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] -= _0x281ec0) : (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] *= _0x281ec0); } } else _0x5cd5b9 < 0x3 * -0x7ce + 0x907 * -0x1 + -0x1 * -0x2094 ? _0x5cd5b9 < 0xa68 + -0xc5f + 0x214 ? 0x3e * -0x7d + 0x13d1 + 0xa90 === _0x5cd5b9 ? (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] /= _0x281ec0) : (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] %= _0x281ec0) : -0x171d * -0x1 + -0x6a2 * -0x3 + -0x2ae6 === _0x5cd5b9 ? _0x27fcf3[_0x12cd72] = -_0x27fcf3[_0x12cd72] : (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x5c0f33 = _0x27fcf3[_0x12cd72--], _0x27fcf3[++_0x12cd72] = _0x5c0f33[_0x281ec0]++) : _0x5cd5b9 < 0x2565 + -0x2 * -0xd76 + -0x1bc * 0x25 ? 0x951 + 0x13da + -0x1d08 === _0x5cd5b9 ? (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72] == _0x281ec0) : (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72] != _0x281ec0) : -0x144c * -0x1 + 0xd01 + -0x2128 === _0x5cd5b9 ? (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72] === _0x281ec0) : (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72] !== _0x281ec0); } } else { if (_0x5cd5b9 < -0x3bb + 0xdb6 + -0x9c2 * 0x1) _0x5cd5b9 < -0xe16 + -0x2 * -0x409 + 0x633 ? _0x5cd5b9 < 0x1 * 0xeea + -0x3 * 0x863 + 0x2b * 0x3e ? _0x5cd5b9 < -0x2f * -0x15 + 0x185 * 0x2 + -0x35e * 0x2 ? (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72] < _0x281ec0) : -0x2152 + -0x1853 + 0x39ce === _0x5cd5b9 ? (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72] > _0x281ec0) : (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72] >= _0x281ec0) : _0x5cd5b9 < 0x3 * -0x822 + 0xc76 + 0x1bb * 0x7 ? 0x20ae + -0x21ca + -0x6d * -0x3 === _0x5cd5b9 ? (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72] << _0x281ec0) : (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72] >> _0x281ec0) : -0x10 * 0x1a + -0x1431 + 0xa * 0x233 === _0x5cd5b9 ? (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72] >>> _0x281ec0) : (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72] & _0x281ec0) : _0x5cd5b9 < 0x1 * 0x1ca7 + 0x1ea1 * 0x1 + -0x31c * 0x13 ? _0x5cd5b9 < -0xa63 * -0x2 + -0x114d + -0x1 * 0x347 ? -0x12b5 + -0x153 * 0x4 + 0x8 * 0x306 === _0x5cd5b9 ? (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72] | _0x281ec0) : (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72] ^ _0x281ec0) : -0xab + 0x10c1 + 0x9 * -0x1c4 === _0x5cd5b9 ? _0x27fcf3[_0x12cd72] = !_0x27fcf3[_0x12cd72] : (_0x1fc1ed = (_0x1fc1ed = (_0x26f45f[_0x3e87c6] << 0x9 * -0xd0 + -0x1 * -0xa04 + 0x2 * -0x156) + _0x26f45f[_0x3e87c6 + (0xa6a + 0x113c + 0x1ba5 * -0x1)]) << 0x2 * -0x41b + -0x1bf1 + 0x7f * 0x49 >> -0x1bc0 + 0x232e + -0x75e, _0x3e87c6 += -0x62 * 0x31 + 0x26b * 0x7 + 0x1d7 * 0x1, _0x27fcf3[_0x12cd72] ? --_0x12cd72 : _0x3e87c6 += _0x1fc1ed) : _0x5cd5b9 < 0x8 * 0x493 + -0x6c * 0x49 + 0x1a * -0x37 ? -0x21 * 0x1 + -0x3 * -0xc19 + 0x2 * -0x11fb === _0x5cd5b9 ? (_0x1fc1ed = (_0x1fc1ed = (_0x26f45f[_0x3e87c6] << -0x2 * 0x11ff + 0x9d3 + 0x1a33) + _0x26f45f[_0x3e87c6 + (0x1ba6 + -0x134f * -0x1 + -0x2ef4)]) << 0x10dc * -0x1 + -0x1130 + 0x221c >> -0x5 * 0x685 + 0x1a23 + 0x686, _0x3e87c6 += 0x4 * 0xf9 + -0x21c5 + 0x1de3, _0x27fcf3[_0x12cd72] ? _0x3e87c6 += _0x1fc1ed : --_0x12cd72) : (_0x281ec0 = _0x27fcf3[_0x12cd72--], (_0x5c0f33 = _0x27fcf3[_0x12cd72--])[_0x281ec0] = _0x27fcf3[_0x12cd72]) : -0x5 * -0x65c + 0x15 * 0x17b + -0x3ead === _0x5cd5b9 ? (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72] in _0x281ec0) : (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] = _0x27fcf3[_0x12cd72] instanceof _0x281ec0); else { if (_0x5cd5b9 < 0x52 + 0x7 * 0x102 + -0x2 * 0x38f) { if (_0x5cd5b9 < -0x1887 + -0x303 + 0x1bc7) _0x5cd5b9 < 0x2132 + -0x592 * 0x2 + -0x15d3 ? -0x136b + -0x21cd + 0x3571 * 0x1 === _0x5cd5b9 ? (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x5c0f33 = _0x27fcf3[_0x12cd72--], _0x27fcf3[++_0x12cd72] = delete _0x5c0f33[_0x281ec0]) : _0x27fcf3[_0x12cd72] = typeof _0x27fcf3[_0x12cd72] : 0xeed * 0x1 + -0xa1e + 0x2 * -0x24a === _0x5cd5b9 ? (_0x1fc1ed = _0x26f45f[_0x3e87c6++], _0x281ec0 = _0x27fcf3[_0x12cd72--], (_0x5c0f33 = function _0x3b7712() { var _0x295f58 = _0x3b7712['_u'] , _0x29116a = _0x3b7712['_v']; return _0x295f58(_0x29116a[0x28c * 0x4 + -0x6 * 0x1d6 + 0xd4], arguments, _0x29116a[0x1d72 * 0x1 + 0x7b * -0x1b + -0x1078], _0x29116a[0x11e6 + 0x1a1f * -0x1 + 0x83b], this); } )['_v'] = [_0x281ec0, _0x1fc1ed, _0x4444cf], _0x5c0f33['_u'] = _0x970e7, _0x27fcf3[++_0x12cd72] = _0x5c0f33) : (_0x1fc1ed = _0x26f45f[_0x3e87c6++], _0x281ec0 = _0x27fcf3[_0x12cd72--], (_0xd55c82 = [_0x5c0f33 = function _0x465123() { var _0x2f6d57 = _0x465123['_u'] , _0x399ae1 = _0x465123['_v']; return _0x2f6d57(_0x399ae1[-0x113b + 0xb * 0x71 + 0xc60], arguments, _0x399ae1[0xa13 + 0x24b0 + -0x9 * 0x532], _0x399ae1[0x1 * 0x463 + -0x56 * -0x49 + -0x1ce7], this); } ])['p'] = _0x4444cf, _0x5c0f33['_v'] = [_0x281ec0, _0x1fc1ed, _0xd55c82], _0x5c0f33['_u'] = _0x970e7, _0x27fcf3[++_0x12cd72] = _0x5c0f33); else { if (_0x5cd5b9 < 0x62 * -0xd + 0xae3 + -0x5a9) 0x9d1 * -0x1 + 0x2425 + -0x1 * 0x1a17 === _0x5cd5b9 ? (_0x1fc1ed = (_0x1fc1ed = (_0x26f45f[_0x3e87c6] << 0xd1 * 0x2 + -0xe5 * 0x7 + 0x4a9) + _0x26f45f[_0x3e87c6 + (0x33a + -0xb14 + 0x7db * 0x1)]) << -0xb * -0xde + 0x1199 + -0x1b13 >> -0xa3d + 0xc * 0x2aa + -0x15ab, _0x3e87c6 += -0x2f * 0x3 + 0x2 * 0x9a3 + -0x12b7, (_0x281ec0 = _0x4b7ccd[_0x4b7ccd['length'] - (0x427 + 0xa54 + -0xda * 0x11)])[0x5d4 + 0x14f2 + -0x4d * 0x59] = _0x3e87c6 + _0x1fc1ed) : (_0x1fc1ed = (_0x1fc1ed = (_0x26f45f[_0x3e87c6] << 0x349 * -0x2 + 0xa4 * -0x17 + 0x1556) + _0x26f45f[_0x3e87c6 + (-0x86 * -0x2 + -0x1c9 + -0xbe * -0x1)]) << 0x14bf + -0x1aec + -0x1 * -0x63d >> 0x2 * 0x17b + -0x1e3c + 0x1b56, _0x3e87c6 += -0x1 * -0x1052 + 0x10 * -0x17e + 0x790, (_0x281ec0 = _0x4b7ccd[_0x4b7ccd['length'] - (-0x1ad2 + 0x1b9 * 0x2 + 0x39 * 0x69)]) && !_0x281ec0[-0x3 * 0xe8 + -0x5 * -0x142 + -0x1 * 0x391] ? (_0x281ec0[0x25 * -0x7a + 0x21ec + -0x104a] = 0x7bf + -0x3 * -0xa43 + -0x2685, _0x281ec0[_0x261736(0x36e)](_0x3e87c6)) : _0x4b7ccd[_0x261736(0x36e)]([-0x1d * 0x82 + -0x11f4 + 0x20af, 0x15ac + -0x399 * -0x9 + -0x360d, _0x3e87c6]), _0x3e87c6 += _0x1fc1ed); else { if (0x2263 * -0x1 + -0x689 + 0x292c === _0x5cd5b9) throw _0x281ec0 = _0x27fcf3[_0x12cd72--]; if (_0x5c0f33 = (_0x281ec0 = _0x4b7ccd['pop']())[0x1 * 0x6a1 + 0x1ceb + -0x238c], _0xd55c82 = _0x42ef1a[-0x1b8 + 0x1d25 + -0x3eb * 0x7], 0x751 * 0x1 + -0xa4b + 0x7 * 0x6d === _0x5c0f33) _0x3e87c6 = _0x281ec0[0x1 * 0x8e + 0x128c + -0x1319]; else { if (0x11f4 + 0x3 * 0x95 + -0x13b3 === _0x5c0f33) { if (-0x2607 + -0xdc8 + -0x1145 * -0x3 === _0xd55c82) _0x3e87c6 = _0x281ec0[-0x262 + -0x12bf * -0x2 + 0x13 * -0x1d9]; else { if (0x6d * 0x2 + 0x2ff * -0x9 + 0x1a1e !== _0xd55c82) throw _0x42ef1a[0x3 * 0xced + 0x1dd7 + 0x16df * -0x3]; if (!_0x428c87) return _0x42ef1a[0x21b7 + 0x20a5 * -0x1 + -0x5b * 0x3]; _0x3e87c6 = _0x428c87[-0x4bd + -0x3 * -0x353 + 0x53b * -0x1], _0x560641 = _0x428c87[0x149e + 0x1 * -0x751 + -0xd4b], _0x4444cf = _0x428c87[-0x2 * 0x94d + -0x7bf * -0x1 + 0x2 * 0x56f], _0x4b7ccd = _0x428c87[0x15c3 * 0x1 + -0x4 * 0x82e + -0x35 * -0x35], _0x27fcf3[++_0x12cd72] = _0x42ef1a[0x3 * 0xaf3 + -0x983 + -0x1755], _0x42ef1a = [-0x1c1c + -0x14c * 0x1a + 0x3dd4, null], _0x428c87 = _0x428c87[0x1a3 + 0x1 * -0x1e23 + 0x1c80]; } } else _0x3e87c6 = _0x281ec0[-0x1 * -0x953 + 0x159 * 0x1 + -0xaaa], _0x281ec0[0x26ad * -0x1 + 0x2 * -0xe4b + 0x4343] = 0x100d + -0x19cc + 0x5 * 0x1f3, _0x4b7ccd[_0x261736(0x36e)](_0x281ec0); } } } } else { if (_0x5cd5b9 < 0x195 + -0x3b * -0x9d + -0x3 * 0xc7f) { if (_0x5cd5b9 < 0xd * -0x1 + 0x229 + -0x1d8) { if (-0x949 + 0x13f8 + -0xa6d === _0x5cd5b9) { for (_0x281ec0 = _0x27fcf3[_0x12cd72--], _0x5c0f33 = null; _0xd55c82 = _0x4b7ccd[_0x261736(0x267)]();) if (0x1e97 + 0x31 * -0x5e + -0xc97 === _0xd55c82[0x150 + 0x6 * 0x645 + -0x26ee] || 0xb29 * -0x3 + -0x333 * 0x3 + 0x2b17 === _0xd55c82[0x1 * -0x22c2 + -0x187b + 0x3b3d]) { _0x5c0f33 = _0xd55c82; break; } if (_0x5c0f33) _0x42ef1a = [-0x11 * 0x14d + 0x1492 * 0x1 + 0x18c, _0x281ec0], _0x3e87c6 = _0x5c0f33[0xc28 * -0x3 + -0x8 * 0x4e1 + 0x4b82], _0x5c0f33[0x51e * 0x1 + 0x755 * -0x5 + 0x1f8b] = 0x1875 * 0x1 + 0x146 + -0x19bb, _0x4b7ccd[_0x261736(0x36e)](_0x5c0f33); else { if (!_0x428c87) return _0x281ec0; _0x3e87c6 = _0x428c87[0x1 * 0x15be + -0x2639 + 0x107c], _0x560641 = _0x428c87[-0x1 * 0xe1e + 0x2042 + 0x2 * -0x911], _0x4444cf = _0x428c87[0x25fd + 0x1cfe + -0x42f8], _0x4b7ccd = _0x428c87[0x3 * 0xa76 + 0x17ce + -0x372c], _0x27fcf3[++_0x12cd72] = _0x281ec0, _0x42ef1a = [-0x7 * 0x4a6 + -0x12d3 + -0x3 * -0x111f, null], _0x428c87 = _0x428c87[-0x29 * 0x1e + 0x9d * -0x2f + 0x21a1]; } } else _0x12cd72 -= _0x1fc1ed = _0x26f45f[_0x3e87c6++], _0x5c0f33 = _0x27fcf3['slice'](_0x12cd72 + (0xf4f * -0x1 + 0x1af9 * 0x1 + -0xba9), _0x12cd72 + _0x1fc1ed + (0x230 + 0x1e17 * -0x1 + 0x6fa * 0x4)), _0x281ec0 = _0x27fcf3[_0x12cd72--], _0xd55c82 = _0x27fcf3[_0x12cd72--], _0x281ec0['_u'] === _0x970e7 ? (_0x281ec0 = _0x281ec0['_v'], _0x428c87 = [_0x428c87, _0x3e87c6, _0x560641, _0x4444cf, _0x4b7ccd], _0x3e87c6 = _0x281ec0[0x190c + 0x3 * 0x89b + 0x32dd * -0x1], null == _0xd55c82 && (_0xd55c82 = (function () { return this; }())), _0x560641 = _0xd55c82, (_0x4444cf = [_0x5c0f33]['concat'](_0x5c0f33))[_0x261736(0x259)] = Math[_0x261736(0x20c)](_0x281ec0[-0x1 * 0x170b + -0x936 + 0x1 * 0x2042], _0x1fc1ed) + (0x3 * -0x4fa + 0x21ed + -0x12fe), _0x4444cf['p'] = _0x281ec0[-0x16 * -0x1af + 0x35f * 0x1 + 0x2867 * -0x1], _0x4b7ccd = []) : _0x27fcf3[++_0x12cd72] = _0x281ec0[_0x261736(0x207)](_0xd55c82, _0x5c0f33); } else { if (0x60a + -0xf05 * -0x1 + 0x14cb * -0x1 === _0x5cd5b9) { for (_0x1fc1ed = _0x26f45f[_0x3e87c6++], _0xd55c82 = [void (-0x1b5d + 0x22a1 + 0x26c * -0x3)], _0x37b70b = _0x1fc1ed; _0x37b70b > 0x1 * 0xd7e + -0x25f1 + 0x1873; --_0x37b70b) _0xd55c82[_0x37b70b] = _0x27fcf3[_0x12cd72--]; _0x5c0f33 = _0x27fcf3[_0x12cd72--], _0x281ec0 = Function['bind'][_0x261736(0x207)](_0x5c0f33, _0xd55c82), _0x27fcf3[++_0x12cd72] = new _0x281ec0(); } else _0x3e87c6 += (_0x1fc1ed = (_0x1fc1ed = (_0x26f45f[_0x3e87c6] << 0x1f7 + 0x1 * -0x7d0 + 0x7 * 0xd7) + _0x26f45f[_0x3e87c6 + (0x264a + 0xc3c + -0x3285)]) << 0x4 * 0x3ee + 0x45 * 0x51 + -0x15 * 0x1c9 >> 0x5 * 0x199 + -0x24f * -0x2 + -0xc8b) + (-0x1f3 * 0x6 + 0x24f7 + -0x1943); } } else _0x5cd5b9 < -0x45d * -0x2 + -0x1e51 + 0x15e0 ? 0x125b + 0x198a + -0x2b9e === _0x5cd5b9 ? (_0x1fc1ed = (_0x1fc1ed = (_0x26f45f[_0x3e87c6] << -0x269 * -0xe + 0x1 * -0x225 + -0x1f91) + _0x26f45f[_0x3e87c6 + (-0x23bd * -0x1 + 0x1ce2 + 0x12 * -0x397)]) << 0xc61 + -0x133b + 0x6ea >> 0x10dc + -0x30a * 0x1 + -0xdc2, _0x3e87c6 += -0xc5 * -0x9 + -0x252f + 0x1e44, (_0x281ec0 = _0x27fcf3[_0x12cd72--]) || (_0x3e87c6 += _0x1fc1ed)) : (_0x1fc1ed = (_0x1fc1ed = (_0x26f45f[_0x3e87c6] << 0x55 * 0x14 + 0x1 * 0x1e18 + -0x3 * 0xc3c) + _0x26f45f[_0x3e87c6 + (-0x22a3 + -0x5bf * -0x2 + 0x1726)]) << -0x98e * 0x4 + 0x8a1 * 0x1 + 0x1da7 >> -0x72 * -0x26 + 0x2b * -0x51 + -0x341, _0x3e87c6 += 0x47 * 0x1d + 0x137 * -0x1d + 0x1b32, _0x281ec0 = _0x27fcf3[_0x12cd72--], _0x27fcf3[_0x12cd72] === _0x281ec0 && (--_0x12cd72, _0x3e87c6 += _0x1fc1ed)) : -0xb * 0x33d + 0x23 * -0xf2 + 0x44fe === _0x5cd5b9 ? --_0x12cd72 : (_0x281ec0 = _0x27fcf3[_0x12cd72], _0x27fcf3[++_0x12cd72] = _0x281ec0); } } } } catch (_0x111ea4) { for (_0x42ef1a = [-0x392 * -0x4 + 0x1a0 + -0xfe8, null]; (_0x1fc1ed = _0x4b7ccd[_0x261736(0x267)]()) && !_0x1fc1ed[0x126a + -0x1 * 0x227c + 0x1012 * 0x1];) ; if (!_0x1fc1ed) { _0x489bfb: for (; _0x428c87;) { for (_0x281ec0 = _0x428c87[0x10da * -0x1 + -0x539 + 0x1617 * 0x1]; _0x1fc1ed = _0x281ec0[_0x261736(0x267)]();) if (_0x1fc1ed[0xaf4 * -0x2 + -0x2 * 0x132d + -0x2 * -0x1e21]) break _0x489bfb; _0x428c87 = _0x428c87[0x1381 + 0x193e + -0x4f * 0x91]; } if (!_0x428c87) throw _0x111ea4; _0x3e87c6 = _0x428c87[0xb * -0x277 + 0x14ac + -0x226 * -0x3], _0x560641 = _0x428c87[0x153e + 0x1fd * -0xf + 0x897], _0x4444cf = _0x428c87[-0x1 * -0xa2e + 0xcd6 + 0x3 * -0x7ab], _0x4b7ccd = _0x428c87[-0x2 * 0x1145 + 0x2 * 0x293 + -0x3ad * -0x8], _0x428c87 = _0x428c87[-0x8e7 + 0x247f + -0x1b98]; } -0xe * 0x277 + -0x3db + -0x665 * -0x6 === (_0x281ec0 = _0x1fc1ed[-0xe65 + 0x11d1 + -0x36c]) ? (_0x3e87c6 = _0x1fc1ed[-0x7 * -0x1c + 0x2fe * 0xa + -0x1eae], _0x1fc1ed[0x3f5 * 0x1 + 0x9e4 + 0x1 * -0xdd9] = -0x1622 * -0x1 + -0x1 * -0x1736 + -0x2d58, _0x4b7ccd['push'](_0x1fc1ed), _0x27fcf3[++_0x12cd72] = _0x111ea4) : 0x1a52 + -0xfc4 + 0x2d * -0x3c === _0x281ec0 ? (_0x3e87c6 = _0x1fc1ed[-0x19 * 0xfe + 0x1f98 + -0x6c8], _0x1fc1ed[0x5 * -0x33f + 0x11d * -0x1 + 0x1158] = 0x3ac * 0x1 + 0x2 * -0x1300 + 0x152 * 0x1a, _0x4b7ccd['push'](_0x1fc1ed), _0x42ef1a = [0x8f * 0x1d + -0x1c4b + 0xc1b, _0x111ea4]) : (_0x3e87c6 = _0x1fc1ed[0x31f * -0x5 + -0xae7 + -0x1 * -0x1a85], _0x1fc1ed[0x27 * -0xb3 + -0x186 * 0xa + 0x2a81] = 0xf9 + 0x164a + -0x1741, _0x4b7ccd[_0x261736(0x36e)](_0x1fc1ed), _0x27fcf3[++_0x12cd72] = _0x111ea4); } }(_0x537efa, [], -0x1e + 0x2631 + -0x2613, _0x3f9548, _0x39b0b8); } !function (_0xe67213, _0x19937e) { _0x19937e(window.byted_acrawler = {}); // var _0x40472c = w_0x25f3; // 'object' == typeof exports && _0x40472c(0x384) != typeof module ? _0x19937e(exports) : _0x40472c(0x1ee) == typeof define && define['amd'] ? define([_0x40472c(0x1b8)], _0x19937e) : _0x19937e((_0xe67213 = _0x40472c(0x384) != typeof globalThis ? globalThis : _0xe67213 || self)[_0x40472c(0x1f5)] = {}); }(this, function (_0x1d18f2) { 'use strict'; var _0x5612de = w_0x25f3; function _0x137ba2(_0x383b51) { var _0x382940 = w_0x25f3, _0x2e23ce, _0x2c2004; function _0x3a4f3f(_0x5a726e, _0x520718) { var _0x480686 = w_0x25f3; try { var _0x4a8345 = _0x383b51[_0x5a726e](_0x520718) , _0x8d9d0a = _0x4a8345[_0x480686(0x2e4)] , _0x32f534 = _0x8d9d0a instanceof _0x59d886; Promise[_0x480686(0x278)](_0x32f534 ? _0x8d9d0a['v'] : _0x8d9d0a)[_0x480686(0x1ed)](function (_0x5047be) { var _0x2a7bf3 = _0x480686; if (_0x32f534) { var _0x1c15d7 = 'return' === _0x5a726e ? _0x2a7bf3(0x2fd) : _0x2a7bf3(0x389); if (!_0x8d9d0a['k'] || _0x5047be['done']) return _0x3a4f3f(_0x1c15d7, _0x5047be); _0x5047be = _0x383b51[_0x1c15d7](_0x5047be)[_0x2a7bf3(0x2e4)]; } _0x396815(_0x4a8345[_0x2a7bf3(0x1e3)] ? _0x2a7bf3(0x2fd) : _0x2a7bf3(0x2f1), _0x5047be); }, function (_0x55c002) { var _0x11b83e = _0x480686; _0x3a4f3f(_0x11b83e(0x250), _0x55c002); }); } catch (_0x5e07d1) { _0x396815(_0x480686(0x250), _0x5e07d1); } } function _0x396815(_0x189a93, _0x2988d2) { var _0x557617 = w_0x25f3; switch (_0x189a93) { case _0x557617(0x2fd): _0x2e23ce[_0x557617(0x278)]({ 'value': _0x2988d2, 'done': !(-0x252f + 0x1b60 + 0x9cf) }); break; case 'throw': _0x2e23ce[_0x557617(0x39b)](_0x2988d2); break; default: _0x2e23ce[_0x557617(0x278)]({ 'value': _0x2988d2, 'done': !(-0x18ec + -0x12dc + 0x2bc9) }); } (_0x2e23ce = _0x2e23ce['next']) ? _0x3a4f3f(_0x2e23ce[_0x557617(0x392)], _0x2e23ce[_0x557617(0x327)]) : _0x2c2004 = null; } this['_invoke'] = function (_0xd1d2b8, _0x168b0a) { return new Promise(function (_0x1a939a, _0x9b7633) { var _0x1f7808 = w_0x25f3 , _0x270c76 = { 'key': _0xd1d2b8, 'arg': _0x168b0a, 'resolve': _0x1a939a, 'reject': _0x9b7633, 'next': null }; _0x2c2004 ? _0x2c2004 = _0x2c2004[_0x1f7808(0x389)] = _0x270c76 : (_0x2e23ce = _0x2c2004 = _0x270c76, _0x3a4f3f(_0xd1d2b8, _0x168b0a)); } ); } , _0x382940(0x1ee) != typeof _0x383b51[_0x382940(0x2fd)] && (this[_0x382940(0x2fd)] = void (-0xe73 + -0xe * 0x24b + 0x2e8d)); } function _0x59d886(_0x53e3a8, _0x594492) { this['v'] = _0x53e3a8, this['k'] = _0x594492; } function _0x1d9867(_0x38463f, _0x559d1b, _0x1b2c54, _0x595501) { return { 'getMetadata': function (_0x397823) { var _0x2d4b6b = w_0x25f3; _0x1fca4f(_0x595501, _0x2d4b6b(0x349)), _0x525dc3(_0x397823); var _0x18296f = _0x38463f[_0x397823]; if (void (-0x15d1 + 0x4d8 + -0xb * -0x18b) !== _0x18296f) { if (-0x1fa5 * -0x1 + 0x6b8 + -0x265c === _0x559d1b) { var _0x192f00 = _0x18296f[_0x2d4b6b(0x1d4)]; if (void (-0x1 * 0x46d + -0x74 * 0x6 + 0x1f * 0x3b) !== _0x192f00) return _0x192f00[_0x1b2c54]; } else { if (0x409 * 0x8 + 0x53 * -0x3e + -0xc2c === _0x559d1b) { var _0x6d3fe1 = _0x18296f[_0x2d4b6b(0x261)]; if (void (0x1c7c + 0x8b0 + -0x7a * 0x4e) !== _0x6d3fe1) return _0x6d3fe1[_0x2d4b6b(0x32b)](_0x1b2c54); } else { if (Object[_0x2d4b6b(0x3b8)][_0x2d4b6b(0x334)](_0x18296f, _0x2d4b6b(0x2ac))) return _0x18296f[_0x2d4b6b(0x2ac)]; } } } }, 'setMetadata': function (_0x3ee519, _0x4e53ba) { var _0x22e2d1 = w_0x25f3; _0x1fca4f(_0x595501, _0x22e2d1(0x395)), _0x525dc3(_0x3ee519); var _0x39b490 = _0x38463f[_0x3ee519]; if (void (0x181 * -0x1 + 0x102 * 0x9 + -0x791) === _0x39b490 && (_0x39b490 = _0x38463f[_0x3ee519] = {}), -0x97e + -0x39 * -0x22 + 0x11 * 0x1d === _0x559d1b) { var _0x38c721 = _0x39b490[_0x22e2d1(0x1d4)]; void (-0x2f9 * -0x7 + 0x8fe + -0x1dcd * 0x1) === _0x38c721 && (_0x38c721 = _0x39b490[_0x22e2d1(0x1d4)] = {}), _0x38c721[_0x1b2c54] = _0x4e53ba; } else { if (-0x4dc + 0xbb0 + -0x6d2 === _0x559d1b) { var _0x4ca087 = _0x39b490['priv']; void (0x29 * 0xb9 + -0x24a + -0x1b57) === _0x4ca087 && (_0x4ca087 = _0x39b490[_0x22e2d1(0x261)] = new Map()), _0x4ca087[_0x22e2d1(0x1e4)](_0x1b2c54, _0x4e53ba); } else _0x39b490[_0x22e2d1(0x2ac)] = _0x4e53ba; } } }; } function _0x43bce4(_0x282431, _0x336297) { var _0xd4a703 = w_0x25f3 , _0x2b5c4b = _0x282431[Symbol['metadata'] || Symbol[_0xd4a703(0x31e)]('Symbol.metadata')] , _0x191a7e = Object[_0xd4a703(0x388)](_0x336297); if (-0x22cd + -0x378 + -0x65 * -0x61 !== _0x191a7e[_0xd4a703(0x259)]) { for (var _0x434093 = 0x1d66 + -0x2 * 0xa03 + -0x960; _0x434093 < _0x191a7e[_0xd4a703(0x259)]; _0x434093++) { var _0x720e9a = _0x191a7e[_0x434093] , _0x14e145 = _0x336297[_0x720e9a] , _0x2965bc = _0x2b5c4b ? _0x2b5c4b[_0x720e9a] : null , _0x530b43 = _0x14e145[_0xd4a703(0x1d4)] , _0x36f2c2 = _0x2965bc ? _0x2965bc[_0xd4a703(0x1d4)] : null; _0x530b43 && _0x36f2c2 && Object['setPrototypeOf'](_0x530b43, _0x36f2c2); var _0x1732ba = _0x14e145[_0xd4a703(0x261)]; if (_0x1732ba) { var _0x550c9f = Array[_0xd4a703(0x29c)](_0x1732ba['values']()) , _0x5cb426 = _0x2965bc ? _0x2965bc[_0xd4a703(0x261)] : null; _0x5cb426 && (_0x550c9f = _0x550c9f[_0xd4a703(0x2b8)](_0x5cb426)), _0x14e145[_0xd4a703(0x261)] = _0x550c9f; } _0x2965bc && Object[_0xd4a703(0x23b)](_0x14e145, _0x2965bc); } _0x2b5c4b && Object['setPrototypeOf'](_0x336297, _0x2b5c4b), _0x282431[Symbol[_0xd4a703(0x3a3)] || Symbol['for']('Symbol.metadata')] = _0x336297; } } function _0x26f5a8(_0x4a45da, _0x3cff50) { return function (_0x346098) { var _0x33211b = w_0x25f3; _0x1fca4f(_0x3cff50, 'addInitializer'), _0x3ccc16(_0x346098, _0x33211b(0x3ba)), _0x4a45da[_0x33211b(0x36e)](_0x346098); } ; } function _0x2bb58f(_0x256829, _0x48125d, _0x3ef27c, _0x2a2bd4, _0x3688dc, _0x348e43, _0x559d92, _0x5a7ba4, _0x9da1fe) { var _0x2a79c7 = w_0x25f3, _0x2e3577; switch (_0x348e43) { case 0xf31 + -0x3 * 0x2dd + -0x699: _0x2e3577 = _0x2a79c7(0x1b9); break; case 0x269d + -0x1291 + -0x140a: _0x2e3577 = _0x2a79c7(0x25a); break; case 0x24f * -0x9 + 0x12b3 + 0x217: _0x2e3577 = _0x2a79c7(0x181); break; case 0xee9 + -0xe3b * 0x1 + -0xaa: _0x2e3577 = _0x2a79c7(0x35d); break; default: _0x2e3577 = _0x2a79c7(0x23c); } var _0x12a0c0, _0x433c00, _0x37f6a8 = { 'kind': _0x2e3577, 'name': _0x5a7ba4 ? '#' + _0x48125d : _0x48125d, 'isStatic': _0x559d92, 'isPrivate': _0x5a7ba4 }, _0x71731a = { 'v': !(-0x1b16 * -0x1 + 0x1 * 0x1fe1 + -0x2 * 0x1d7b) }; if (0x15e0 + -0xdfe + -0x7e2 !== _0x348e43 && (_0x37f6a8['addInitializer'] = _0x26f5a8(_0x3688dc, _0x71731a)), _0x5a7ba4) { _0x12a0c0 = -0x2 * 0xd69 + 0x2a1 * 0x1 + 0x1833, _0x433c00 = Symbol(_0x48125d); var _0x18857b = {}; 0x2b + 0x9ef * 0x1 + -0x1 * 0xa1a === _0x348e43 ? (_0x18857b[_0x2a79c7(0x32b)] = _0x3ef27c[_0x2a79c7(0x32b)], _0x18857b['set'] = _0x3ef27c['set']) : -0x2 * 0xd06 + 0x1816 + 0x1f8 === _0x348e43 ? _0x18857b[_0x2a79c7(0x32b)] = function () { var _0x14604b = _0x2a79c7; return _0x3ef27c[_0x14604b(0x2e4)]; } : (0x6bc + 0x1 * 0x2095 + 0x10 * -0x275 !== _0x348e43 && -0x1207 + 0x31f + 0x13 * 0xc9 !== _0x348e43 || (_0x18857b[_0x2a79c7(0x32b)] = function () { var _0x290422 = _0x2a79c7; return _0x3ef27c[_0x290422(0x32b)][_0x290422(0x334)](this); } ), 0x1296 + -0x1481 + 0x1ec !== _0x348e43 && -0x1892 + -0xfa7 + 0x283d * 0x1 !== _0x348e43 || (_0x18857b[_0x2a79c7(0x1e4)] = function (_0x4e18b8) { var _0x7e7883 = _0x2a79c7; _0x3ef27c['set'][_0x7e7883(0x334)](this, _0x4e18b8); } )), _0x37f6a8[_0x2a79c7(0x1d0)] = _0x18857b; } else _0x12a0c0 = 0xca0 * 0x1 + 0x83 + -0x52 * 0x29, _0x433c00 = _0x48125d; try { return _0x256829(_0x9da1fe, Object[_0x2a79c7(0x2a0)](_0x37f6a8, _0x1d9867(_0x2a2bd4, _0x12a0c0, _0x433c00, _0x71731a))); } finally { _0x71731a['v'] = !(0x6ad + -0x17a9 + 0x10fc); } } function _0x1fca4f(_0x322983, _0x544667) { var _0x43acb9 = w_0x25f3; if (_0x322983['v']) throw new Error(_0x43acb9(0x214) + _0x544667 + _0x43acb9(0x168)); } function _0x525dc3(_0x3ef577) { var _0x42d9e9 = w_0x25f3; if (_0x42d9e9(0x2ed) != typeof _0x3ef577) throw new TypeError(_0x42d9e9(0x2e7) + _0x3ef577); } function _0x3ccc16(_0x56cd04, _0x56ab29) { var _0x12ff08 = w_0x25f3; if ('function' != typeof _0x56cd04) throw new TypeError(_0x56ab29 + _0x12ff08(0x2f8)); } function _0x32dfd4(_0x43869e, _0x434307) { var _0x3fc8b9 = w_0x25f3 , _0xd7b002 = typeof _0x434307; if (-0x1116 + 0x183f + 0x2 * -0x394 === _0x43869e) { if (_0x3fc8b9(0x17d) !== _0xd7b002 || null === _0x434307) throw new TypeError(_0x3fc8b9(0x1fd)); void (-0x39e * -0x9 + -0x1 * 0x1241 + -0x7 * 0x20b) !== _0x434307[_0x3fc8b9(0x32b)] && _0x3ccc16(_0x434307[_0x3fc8b9(0x32b)], _0x3fc8b9(0x170)), void (0xeff * 0x1 + 0x2673 + 0x1 * -0x3572) !== _0x434307[_0x3fc8b9(0x1e4)] && _0x3ccc16(_0x434307[_0x3fc8b9(0x1e4)], _0x3fc8b9(0x231)), void (-0x1558 + -0x1b41 * 0x1 + 0x57 * 0x8f) !== _0x434307['init'] && _0x3ccc16(_0x434307['init'], _0x3fc8b9(0x22f)), void (-0x148f + 0xfa3 * 0x1 + 0x4ec) !== _0x434307[_0x3fc8b9(0x2d0)] && _0x3ccc16(_0x434307[_0x3fc8b9(0x2d0)], 'accessor.initializer'); } else { if (_0x3fc8b9(0x1ee) !== _0xd7b002) throw new TypeError((0xf1a + 0xce * 0x1f + -0x280c === _0x43869e ? _0x3fc8b9(0x23c) : 0x2428 + -0x6 * -0x635 + -0x495c === _0x43869e ? _0x3fc8b9(0x19c) : _0x3fc8b9(0x25a)) + _0x3fc8b9(0x2cd)); } } function _0x372120(_0x40b476) { var _0x3e830c = w_0x25f3, _0x49c5be; return null == (_0x49c5be = _0x40b476['init']) && (_0x49c5be = _0x40b476[_0x3e830c(0x2d0)]) && _0x3e830c(0x384) != typeof console && console[_0x3e830c(0x3b0)](_0x3e830c(0x30b)), _0x49c5be; } function _0x481bfe(_0x40006c, _0xdbd444, _0x2c21df, _0x2a4c98, _0x438b52, _0x13e34d, _0x5ae545, _0xa813f0, _0x4c4bfe) { var _0x2ee2fe = w_0x25f3, _0x2f4463, _0x470ce0, _0x5e0119, _0x2b261c, _0x52c40b, _0x4e02cf, _0x1fa0af = _0x2c21df[-0x987 + 0x27 * -0xdf + -0x40 * -0xae]; if (_0x5ae545 ? _0x2f4463 = -0xa89 * 0x1 + -0xd71 + 0x17fa === _0x438b52 || -0x272 * -0xd + -0x1c * 0xb2 + -0xc51 === _0x438b52 ? { 'get': _0x2c21df[0xefd + 0x232b + -0x3225], 'set': _0x2c21df[0x21b5 * -0x1 + -0x1015 + 0x31ce] } : 0x17ef + -0x1d3 + 0x1619 * -0x1 === _0x438b52 ? { 'get': _0x2c21df[-0x1e92 + 0xff * -0xe + 0x2c87] } : -0x2211 + 0x1568 + 0xcad === _0x438b52 ? { 'set': _0x2c21df[-0xcd2 + 0x126e + 0x1 * -0x599] } : { 'value': _0x2c21df[0x2293 + -0x1815 + -0xa7b * 0x1] } : 0x23f1 + -0x93b * -0x4 + 0x1 * -0x48dd !== _0x438b52 && (_0x2f4463 = Object[_0x2ee2fe(0x2a6)](_0xdbd444, _0x2a4c98)), -0x9a5 + 0x2cc * -0x5 + -0xb * -0x226 === _0x438b52 ? _0x5e0119 = { 'get': _0x2f4463['get'], 'set': _0x2f4463[_0x2ee2fe(0x1e4)] } : -0xb5c + 0x210d + -0x1ab * 0xd === _0x438b52 ? _0x5e0119 = _0x2f4463[_0x2ee2fe(0x2e4)] : -0x12e2 * -0x2 + 0x1 * 0x815 + -0x2dd6 === _0x438b52 ? _0x5e0119 = _0x2f4463['get'] : 0x6 * 0x182 + 0x15fd + -0x1f05 * 0x1 === _0x438b52 && (_0x5e0119 = _0x2f4463[_0x2ee2fe(0x1e4)]), _0x2ee2fe(0x1ee) == typeof _0x1fa0af) void (0x12b3 * -0x2 + 0x1229 * 0x1 + 0x133d) !== (_0x2b261c = _0x2bb58f(_0x1fa0af, _0x2a4c98, _0x2f4463, _0xa813f0, _0x4c4bfe, _0x438b52, _0x13e34d, _0x5ae545, _0x5e0119)) && (_0x32dfd4(_0x438b52, _0x2b261c), -0x655 + -0xb75 + 0x9 * 0x1fa === _0x438b52 ? _0x470ce0 = _0x2b261c : 0x2239 + -0x9 * -0x42b + -0x47bb === _0x438b52 ? (_0x470ce0 = _0x372120(_0x2b261c), _0x52c40b = _0x2b261c[_0x2ee2fe(0x32b)] || _0x5e0119[_0x2ee2fe(0x32b)], _0x4e02cf = _0x2b261c['set'] || _0x5e0119['set'], _0x5e0119 = { 'get': _0x52c40b, 'set': _0x4e02cf }) : _0x5e0119 = _0x2b261c); else for (var _0x5801c7 = _0x1fa0af['length'] - (0x2435 + 0x18d0 + 0x16 * -0x2c6); _0x5801c7 >= 0x3ab + 0x58e * -0x6 + 0x1da9; _0x5801c7--) { var _0x2c9ee5; void (0x11 * -0x22 + -0x29 * -0x45 + 0x8cb * -0x1) !== (_0x2b261c = _0x2bb58f(_0x1fa0af[_0x5801c7], _0x2a4c98, _0x2f4463, _0xa813f0, _0x4c4bfe, _0x438b52, _0x13e34d, _0x5ae545, _0x5e0119)) && (_0x32dfd4(_0x438b52, _0x2b261c), -0x220e + 0x3 * 0x4d5 + 0x138f === _0x438b52 ? _0x2c9ee5 = _0x2b261c : -0x2 * -0x1281 + 0x48f * 0x7 + -0x44ea === _0x438b52 ? (_0x2c9ee5 = _0x372120(_0x2b261c), _0x52c40b = _0x2b261c[_0x2ee2fe(0x32b)] || _0x5e0119[_0x2ee2fe(0x32b)], _0x4e02cf = _0x2b261c[_0x2ee2fe(0x1e4)] || _0x5e0119[_0x2ee2fe(0x1e4)], _0x5e0119 = { 'get': _0x52c40b, 'set': _0x4e02cf }) : _0x5e0119 = _0x2b261c, void (0x204c + -0x2618 + 0x7 * 0xd4) !== _0x2c9ee5 && (void (0x3b3 * 0x9 + -0x2709 + 0x69 * 0xe) === _0x470ce0 ? _0x470ce0 = _0x2c9ee5 : _0x2ee2fe(0x1ee) == typeof _0x470ce0 ? _0x470ce0 = [_0x470ce0, _0x2c9ee5] : _0x470ce0['push'](_0x2c9ee5))); } if (-0x236c + 0xbb8 + 0x17b4 === _0x438b52 || 0x1 * 0x13e5 + -0x73 * -0xd + -0x1 * 0x19bb === _0x438b52) { if (void (0x213e + 0xef + -0x222d) === _0x470ce0) _0x470ce0 = function (_0x2f3a47, _0x29873e) { return _0x29873e; } ; else { if (_0x2ee2fe(0x1ee) != typeof _0x470ce0) { var _0x41d3b0 = _0x470ce0; _0x470ce0 = function (_0x434b4b, _0x5f49e4) { var _0x463980 = _0x2ee2fe; for (var _0x99d6c0 = _0x5f49e4, _0x29ba97 = -0x120f * 0x1 + 0x1241 + -0x1 * 0x32; _0x29ba97 < _0x41d3b0['length']; _0x29ba97++) _0x99d6c0 = _0x41d3b0[_0x29ba97][_0x463980(0x334)](_0x434b4b, _0x99d6c0); return _0x99d6c0; } ; } else { var _0x58c0cd = _0x470ce0; _0x470ce0 = function (_0x15ad86, _0x1885c8) { return _0x58c0cd['call'](_0x15ad86, _0x1885c8); } ; } } _0x40006c[_0x2ee2fe(0x36e)](_0x470ce0); } 0x1 * -0x241 + 0xf4f + -0xd0e !== _0x438b52 && (0x1 * 0x10e7 + -0x47c + -0x2 * 0x635 === _0x438b52 ? (_0x2f4463[_0x2ee2fe(0x32b)] = _0x5e0119[_0x2ee2fe(0x32b)], _0x2f4463[_0x2ee2fe(0x1e4)] = _0x5e0119[_0x2ee2fe(0x1e4)]) : -0x53 * 0x6b + 0x16e * 0x8 + 0x1743 === _0x438b52 ? _0x2f4463[_0x2ee2fe(0x2e4)] = _0x5e0119 : 0x4 * -0x4cd + -0x1 * -0x211a + 0x1 * -0xde3 === _0x438b52 ? _0x2f4463['get'] = _0x5e0119 : -0x1d4b * 0x1 + 0x1853 + 0x4fc === _0x438b52 && (_0x2f4463[_0x2ee2fe(0x1e4)] = _0x5e0119), _0x5ae545 ? 0x5 * -0x28 + 0xcfd + -0xc34 * 0x1 === _0x438b52 ? (_0x40006c[_0x2ee2fe(0x36e)](function (_0x21a97b, _0x5eb78e) { var _0x40880e = _0x2ee2fe; return _0x5e0119[_0x40880e(0x32b)][_0x40880e(0x334)](_0x21a97b, _0x5eb78e); }), _0x40006c[_0x2ee2fe(0x36e)](function (_0x2fa7d1, _0x120ab2) { var _0x14b22a = _0x2ee2fe; return _0x5e0119[_0x14b22a(0x1e4)][_0x14b22a(0x334)](_0x2fa7d1, _0x120ab2); })) : -0x4 * 0x64c + -0x1 * 0xe54 + 0x13c3 * 0x2 === _0x438b52 ? _0x40006c['push'](_0x5e0119) : _0x40006c[_0x2ee2fe(0x36e)](function (_0xcb08af, _0x156de1) { var _0x327ef4 = _0x2ee2fe; return _0x5e0119[_0x327ef4(0x334)](_0xcb08af, _0x156de1); }) : Object[_0x2ee2fe(0x175)](_0xdbd444, _0x2a4c98, _0x2f4463)); } function _0xa6bc9e(_0x4ceb2c, _0x1b99e7, _0xa8afec, _0x19fdc6, _0x501635) { var _0x4c0532 = w_0x25f3; for (var _0xcd77fa, _0x29d8ee, _0x1865b0 = new Map(), _0x4b9277 = new Map(), _0xbb3086 = 0x8 * 0x5e + 0x1f79 + -0x17 * 0x17f; _0xbb3086 < _0x501635[_0x4c0532(0x259)]; _0xbb3086++) { var _0x58c865 = _0x501635[_0xbb3086]; if (Array['isArray'](_0x58c865)) { var _0x203b06, _0x4069ae, _0x479925, _0x2ccf6e = _0x58c865[-0x3f4 + -0x1cc4 + -0x1 * -0x20b9], _0x138f67 = _0x58c865[0x17b7 + 0x1 * 0x1525 + -0x2cda], _0xf00e58 = _0x58c865[_0x4c0532(0x259)] > -0x9f5 + -0xd * -0x103 + -0x5 * 0xa3, _0x356344 = _0x2ccf6e >= -0x14cc + -0xe02 + 0x6f7 * 0x5; if (_0x356344 ? (_0x203b06 = _0x1b99e7, _0x4069ae = _0x19fdc6, 0x7 * -0x3c7 + -0x225e + 0x3ccf != (_0x2ccf6e -= -0xdf * 0x17 + -0xb0c + 0x1f1a) && (_0x479925 = _0x29d8ee = _0x29d8ee || [])) : (_0x203b06 = _0x1b99e7[_0x4c0532(0x344)], _0x4069ae = _0xa8afec, -0xc0c + 0x1a8e + -0x2 * 0x741 !== _0x2ccf6e && (_0x479925 = _0xcd77fa = _0xcd77fa || [])), -0x135b + 0xba6 + -0x7b5 * -0x1 !== _0x2ccf6e && !_0xf00e58) { var _0x5ef9f5 = _0x356344 ? _0x4b9277 : _0x1865b0 , _0x4ff161 = _0x5ef9f5[_0x4c0532(0x32b)](_0x138f67) || 0x2 * 0x27b + 0x137 * 0x1d + -0x2831; if (!(-0xea4 + 0x2178 + 0x96a * -0x2) === _0x4ff161 || -0x1dce + 0x245d + -0x346 * 0x2 === _0x4ff161 && -0x6 * -0x38f + -0x4 * 0x179 + 0xf72 * -0x1 !== _0x2ccf6e || 0x1265 + -0x40 * 0x6a + -0x21 * -0x3f === _0x4ff161 && -0x14bf + -0x1fa6 + 0x3468 !== _0x2ccf6e) throw new Error(_0x4c0532(0x351) + _0x138f67); !_0x4ff161 && _0x2ccf6e > -0x895 * 0x1 + 0x9fa + -0x163 ? _0x5ef9f5[_0x4c0532(0x1e4)](_0x138f67, _0x2ccf6e) : _0x5ef9f5['set'](_0x138f67, !(-0x1a * 0x12 + 0xa1 * 0x1d + -0x1069 * 0x1)); } _0x481bfe(_0x4ceb2c, _0x203b06, _0x58c865, _0x138f67, _0x2ccf6e, _0x356344, _0xf00e58, _0x4069ae, _0x479925); } } _0x3657e2(_0x4ceb2c, _0xcd77fa), _0x3657e2(_0x4ceb2c, _0x29d8ee); } function _0x3657e2(_0x27f72c, _0x8206e1) { var _0x4690f1 = w_0x25f3; _0x8206e1 && _0x27f72c[_0x4690f1(0x36e)](function (_0x5a459a) { var _0x25d832 = _0x4690f1; for (var _0x3abe8d = -0x2d8 + -0x38c + 0x664; _0x3abe8d < _0x8206e1['length']; _0x3abe8d++) _0x8206e1[_0x3abe8d][_0x25d832(0x334)](_0x5a459a); return _0x5a459a; }); } function _0x5f3676(_0x5c13b5, _0x4779ee, _0x3479c8, _0x3cb840) { var _0x5df6f5 = w_0x25f3; if (_0x3cb840['length'] > -0x1744 + -0xfb * 0xd + 0x2403) { for (var _0x3c4c21 = [], _0x504436 = _0x4779ee, _0x1b5d8b = _0x4779ee['name'], _0x38689d = _0x3cb840[_0x5df6f5(0x259)] - (-0x1f5e + 0x1e2c * 0x1 + 0x133); _0x38689d >= 0x878 + -0x1269 * 0x2 + 0x1c5a; _0x38689d--) { var _0x2bbf37 = { 'v': !(0x1 * -0x17dc + -0x1f4 * -0x10 + -0x3d * 0x1f) }; try { var _0x175a61 = Object['assign']({ 'kind': _0x5df6f5(0x19c), 'name': _0x1b5d8b, 'addInitializer': _0x26f5a8(_0x3c4c21, _0x2bbf37) }, _0x1d9867(_0x3479c8, -0x1dfa + -0x1517 + 0x3311, _0x1b5d8b, _0x2bbf37)) , _0x23fff7 = _0x3cb840[_0x38689d](_0x504436, _0x175a61); } finally { _0x2bbf37['v'] = !(0x1 * -0x1e5d + -0xba7 * 0x2 + 0x35ab); } void (-0x2bf + -0x103d * -0x1 + -0xd7e) !== _0x23fff7 && (_0x32dfd4(0xcf3 + -0x9f8 + -0x2f1, _0x23fff7), _0x504436 = _0x23fff7); } _0x5c13b5[_0x5df6f5(0x36e)](_0x504436, function () { var _0x52b1c0 = _0x5df6f5; for (var _0x4f0e95 = 0x18 * 0x57 + 0x9f2 * 0x3 + -0x25fe; _0x4f0e95 < _0x3c4c21[_0x52b1c0(0x259)]; _0x4f0e95++) _0x3c4c21[_0x4f0e95][_0x52b1c0(0x334)](_0x504436); }); } } function _0x51be3f(_0x44772e, _0x5243ca, _0x398fa6) { var _0x234a54 = w_0x25f3 , _0x13ba59 = [] , _0x3f8aa9 = {} , _0x129506 = {}; return _0xa6bc9e(_0x13ba59, _0x44772e, _0x129506, _0x3f8aa9, _0x5243ca), _0x43bce4(_0x44772e[_0x234a54(0x344)], _0x129506), _0x5f3676(_0x13ba59, _0x44772e, _0x3f8aa9, _0x398fa6), _0x43bce4(_0x44772e, _0x3f8aa9), _0x13ba59; } function _0x281b3b() { function _0x556aac(_0xe6f04c, _0x2cf155) { return function (_0x4ecc37) { var _0x253daa = w_0x25f3; !function (_0x109a83, _0x5991fe) { var _0x11a2fc = w_0x25f3; if (_0x109a83['v']) throw new Error(_0x11a2fc(0x257)); }(_0x2cf155), _0x3020d2(_0x4ecc37, _0x253daa(0x3ba)), _0xe6f04c[_0x253daa(0x36e)](_0x4ecc37); } ; } function _0x193050(_0xc0995a, _0x3ee2bb, _0x30e0b6, _0xba208c, _0x262641, _0x4fa001, _0x1bb0d5, _0xa6e531) { var _0x208bac = w_0x25f3, _0x59dc77; switch (_0x262641) { case 0x1af6 + -0xd13 + -0xde2: _0x59dc77 = 'accessor'; break; case 0xfda + -0x760 + -0x878: _0x59dc77 = _0x208bac(0x25a); break; case 0x1a * 0xb9 + 0xc3f + -0x1f06: _0x59dc77 = 'getter'; break; case -0xb42 * 0x3 + -0x12d9 + 0x34a3: _0x59dc77 = 'setter'; break; default: _0x59dc77 = _0x208bac(0x23c); } var _0x1b169f, _0x3fad4b, _0x1050cb = { 'kind': _0x59dc77, 'name': _0x1bb0d5 ? '#' + _0x3ee2bb : _0x3ee2bb, 'static': _0x4fa001, 'private': _0x1bb0d5 }, _0x252772 = { 'v': !(0x5 * 0x4d5 + -0x1 * 0x1d6e + 0x546) }; -0x21ff + -0x127a + -0x3479 * -0x1 !== _0x262641 && (_0x1050cb['addInitializer'] = _0x556aac(_0xba208c, _0x252772)), -0x2c9 + -0x27 + 0x2f0 === _0x262641 ? _0x1bb0d5 ? (_0x1b169f = _0x30e0b6['get'], _0x3fad4b = _0x30e0b6[_0x208bac(0x1e4)]) : (_0x1b169f = function () { return this[_0x3ee2bb]; } , _0x3fad4b = function (_0x417555) { this[_0x3ee2bb] = _0x417555; } ) : 0x1 * -0x1d61 + -0x1 * 0x841 + -0xc8c * -0x3 === _0x262641 ? _0x1b169f = function () { var _0x195f9b = _0x208bac; return _0x30e0b6[_0x195f9b(0x2e4)]; } : (0x1 * -0x1e49 + 0x1 * 0x1eef + 0x37 * -0x3 !== _0x262641 && 0x1 * 0x204a + -0x137 * -0xb + -0x2da4 !== _0x262641 || (_0x1b169f = function () { var _0x3ad118 = _0x208bac; return _0x30e0b6[_0x3ad118(0x32b)][_0x3ad118(0x334)](this); } ), 0x729 + -0x7ae + 0x1 * 0x86 !== _0x262641 && -0xa * -0x161 + -0x512 + -0x2 * 0x45a !== _0x262641 || (_0x3fad4b = function (_0x37b522) { var _0x599416 = _0x208bac; _0x30e0b6[_0x599416(0x1e4)]['call'](this, _0x37b522); } )), _0x1050cb[_0x208bac(0x1d0)] = _0x1b169f && _0x3fad4b ? { 'get': _0x1b169f, 'set': _0x3fad4b } : _0x1b169f ? { 'get': _0x1b169f } : { 'set': _0x3fad4b }; try { return _0xc0995a(_0xa6e531, _0x1050cb); } finally { _0x252772['v'] = !(-0x1 * 0x1ecf + 0x1cfc + 0x1d3); } } function _0x3020d2(_0x5684cc, _0x512b77) { var _0x3b7e7a = w_0x25f3; if (_0x3b7e7a(0x1ee) != typeof _0x5684cc) throw new TypeError(_0x512b77 + '\x20must\x20be\x20a\x20function'); } function _0x13d7df(_0x22787a, _0x4a8dfe) { var _0x994eff = w_0x25f3 , _0x751eff = typeof _0x4a8dfe; if (-0x690 + 0x133f * -0x2 + 0x2d0f === _0x22787a) { if (_0x994eff(0x17d) !== _0x751eff || null === _0x4a8dfe) throw new TypeError(_0x994eff(0x1fd)); void (-0x2af + 0x838 + -0xd * 0x6d) !== _0x4a8dfe[_0x994eff(0x32b)] && _0x3020d2(_0x4a8dfe[_0x994eff(0x32b)], _0x994eff(0x170)), void (0x1b * -0x43 + -0x24c8 + 0x2bd9) !== _0x4a8dfe[_0x994eff(0x1e4)] && _0x3020d2(_0x4a8dfe[_0x994eff(0x1e4)], _0x994eff(0x231)), void (-0x2a4 + 0x35 * -0x1d + 0x8a5) !== _0x4a8dfe[_0x994eff(0x3b9)] && _0x3020d2(_0x4a8dfe[_0x994eff(0x3b9)], _0x994eff(0x22f)); } else { if ('function' !== _0x751eff) throw new TypeError((-0x800 + 0xd * 0x75 + 0x20f === _0x22787a ? _0x994eff(0x23c) : 0x13ad + 0x1db0 + -0x3153 === _0x22787a ? _0x994eff(0x19c) : _0x994eff(0x25a)) + '\x20decorators\x20must\x20return\x20a\x20function\x20or\x20void\x200'); } } function _0x3345ab(_0xc8cad8, _0x5d015f, _0x32f33f, _0x5f5169, _0x23451f, _0x2595f8, _0xb8bdee, _0x2c3fb2) { var _0x1d74f6 = w_0x25f3, _0x584e33, _0x103b18, _0x5d6523, _0x2d18a1, _0x2b9fd3, _0x11479e, _0x4ef3d6 = _0x32f33f[0x23c3 + -0x1a88 + 0x1 * -0x93b]; if (_0xb8bdee ? _0x584e33 = 0x7ed * 0x1 + 0xac9 + -0x12b6 === _0x23451f || 0x4 * 0x7f5 + 0x11ab + 0x712 * -0x7 === _0x23451f ? { 'get': _0x32f33f[0x265e * 0x1 + -0x23e1 + -0x1 * 0x27a], 'set': _0x32f33f[0x108e + -0x2551 + 0x1b * 0xc5] } : 0x19bc + 0x1c0d * 0x1 + 0x35c6 * -0x1 === _0x23451f ? { 'get': _0x32f33f[-0x7fd * 0x4 + 0x1b6c + 0x48b] } : 0x149a + 0x2522 + -0x39b8 === _0x23451f ? { 'set': _0x32f33f[-0x1 * 0x3ad + -0xb4e * 0x2 + -0x9 * -0x2ec] } : { 'value': _0x32f33f[0x21a6 + -0x7 + -0x219c] } : 0x12ab * 0x1 + -0x1f41 + 0xc96 !== _0x23451f && (_0x584e33 = Object[_0x1d74f6(0x2a6)](_0x5d015f, _0x5f5169)), 0x11 * -0xf7 + -0x4 * 0x7be + 0x2f60 === _0x23451f ? _0x5d6523 = { 'get': _0x584e33[_0x1d74f6(0x32b)], 'set': _0x584e33['set'] } : 0x1e3 + 0x2 * 0x11ef + -0x25bf === _0x23451f ? _0x5d6523 = _0x584e33[_0x1d74f6(0x2e4)] : -0x6b + 0x1ff8 + -0xfc5 * 0x2 === _0x23451f ? _0x5d6523 = _0x584e33['get'] : 0x2577 + -0x2272 + -0x301 === _0x23451f && (_0x5d6523 = _0x584e33[_0x1d74f6(0x1e4)]), 'function' == typeof _0x4ef3d6) void (0x1bdc * -0x1 + 0x2343 + -0x1 * 0x767) !== (_0x2d18a1 = _0x193050(_0x4ef3d6, _0x5f5169, _0x584e33, _0x2c3fb2, _0x23451f, _0x2595f8, _0xb8bdee, _0x5d6523)) && (_0x13d7df(_0x23451f, _0x2d18a1), -0x5 * -0x27e + 0x6a * 0x1e + -0x18e2 === _0x23451f ? _0x103b18 = _0x2d18a1 : 0x23f1 + -0x4 * 0x8bb + 0x14 * -0xd === _0x23451f ? (_0x103b18 = _0x2d18a1['init'], _0x2b9fd3 = _0x2d18a1[_0x1d74f6(0x32b)] || _0x5d6523[_0x1d74f6(0x32b)], _0x11479e = _0x2d18a1[_0x1d74f6(0x1e4)] || _0x5d6523[_0x1d74f6(0x1e4)], _0x5d6523 = { 'get': _0x2b9fd3, 'set': _0x11479e }) : _0x5d6523 = _0x2d18a1); else for (var _0x4328fb = _0x4ef3d6[_0x1d74f6(0x259)] - (0xe31 + -0xc5b + -0x1d5); _0x4328fb >= 0x87a + -0xf7c + 0x702; _0x4328fb--) { var _0x5c4cc8; void (0x83b + -0x8af + 0x74) !== (_0x2d18a1 = _0x193050(_0x4ef3d6[_0x4328fb], _0x5f5169, _0x584e33, _0x2c3fb2, _0x23451f, _0x2595f8, _0xb8bdee, _0x5d6523)) && (_0x13d7df(_0x23451f, _0x2d18a1), -0x4 * 0x511 + -0xc0a * -0x1 + 0x83a === _0x23451f ? _0x5c4cc8 = _0x2d18a1 : -0x22 * -0x4d + -0x135d * -0x2 + 0x1051 * -0x3 === _0x23451f ? (_0x5c4cc8 = _0x2d18a1[_0x1d74f6(0x3b9)], _0x2b9fd3 = _0x2d18a1[_0x1d74f6(0x32b)] || _0x5d6523[_0x1d74f6(0x32b)], _0x11479e = _0x2d18a1[_0x1d74f6(0x1e4)] || _0x5d6523[_0x1d74f6(0x1e4)], _0x5d6523 = { 'get': _0x2b9fd3, 'set': _0x11479e }) : _0x5d6523 = _0x2d18a1, void (0x23b9 * 0x1 + 0x2 * -0xccb + -0x5 * 0x207) !== _0x5c4cc8 && (void (-0x15ac + 0x376 + 0x7e * 0x25) === _0x103b18 ? _0x103b18 = _0x5c4cc8 : _0x1d74f6(0x1ee) == typeof _0x103b18 ? _0x103b18 = [_0x103b18, _0x5c4cc8] : _0x103b18['push'](_0x5c4cc8))); } if (-0x11a3 + 0xd * -0x2f2 + -0x67 * -0x8b === _0x23451f || 0xeab * -0x1 + 0x752 * -0x1 + -0x15fe * -0x1 === _0x23451f) { if (void (-0x4a8 * 0x1 + 0xfb0 + -0xb08) === _0x103b18) _0x103b18 = function (_0x1682da, _0x55e4de) { return _0x55e4de; } ; else { if (_0x1d74f6(0x1ee) != typeof _0x103b18) { var _0x278a7c = _0x103b18; _0x103b18 = function (_0x1c32a2, _0x442b39) { var _0x56cde3 = _0x1d74f6; for (var _0x200043 = _0x442b39, _0x52eaec = 0xc8a + 0x26f6 + 0x20 * -0x19c; _0x52eaec < _0x278a7c[_0x56cde3(0x259)]; _0x52eaec++) _0x200043 = _0x278a7c[_0x52eaec]['call'](_0x1c32a2, _0x200043); return _0x200043; } ; } else { var _0x120537 = _0x103b18; _0x103b18 = function (_0x5a0b99, _0x523966) { var _0x2de49d = _0x1d74f6; return _0x120537[_0x2de49d(0x334)](_0x5a0b99, _0x523966); } ; } } _0xc8cad8['push'](_0x103b18); } 0x3ac * -0x8 + 0x915 + 0x144b !== _0x23451f && (-0xcde + -0x1f3f + 0x2c1e === _0x23451f ? (_0x584e33[_0x1d74f6(0x32b)] = _0x5d6523['get'], _0x584e33[_0x1d74f6(0x1e4)] = _0x5d6523['set']) : -0x31 * -0xc1 + -0x1 * -0x2388 + -0x4877 === _0x23451f ? _0x584e33[_0x1d74f6(0x2e4)] = _0x5d6523 : -0x4 * 0x899 + 0x1944 + 0x923 === _0x23451f ? _0x584e33[_0x1d74f6(0x32b)] = _0x5d6523 : -0x64e * 0x5 + 0x152f + 0xa5b === _0x23451f && (_0x584e33[_0x1d74f6(0x1e4)] = _0x5d6523), _0xb8bdee ? 0x186b + 0x3 * 0x7e9 + -0x1ed * 0x19 === _0x23451f ? (_0xc8cad8[_0x1d74f6(0x36e)](function (_0x1c7663, _0x210e49) { var _0xb35933 = _0x1d74f6; return _0x5d6523['get'][_0xb35933(0x334)](_0x1c7663, _0x210e49); }), _0xc8cad8[_0x1d74f6(0x36e)](function (_0x9ce4a0, _0xa2a6a5) { var _0x548bf5 = _0x1d74f6; return _0x5d6523[_0x548bf5(0x1e4)][_0x548bf5(0x334)](_0x9ce4a0, _0xa2a6a5); })) : -0x2 * 0x91d + -0x226e + 0x282 * 0x15 === _0x23451f ? _0xc8cad8['push'](_0x5d6523) : _0xc8cad8[_0x1d74f6(0x36e)](function (_0x5b9e9b, _0x4ef49d) { return _0x5d6523['call'](_0x5b9e9b, _0x4ef49d); }) : Object[_0x1d74f6(0x175)](_0x5d015f, _0x5f5169, _0x584e33)); } function _0xbbd38b(_0x42478e, _0x1ef3db) { var _0x15d90a = w_0x25f3; _0x1ef3db && _0x42478e[_0x15d90a(0x36e)](function (_0xdfc40e) { var _0x10548c = _0x15d90a; for (var _0x3e6f2c = -0x349 * -0xa + 0xe * 0x5b + -0x25d4; _0x3e6f2c < _0x1ef3db[_0x10548c(0x259)]; _0x3e6f2c++) _0x1ef3db[_0x3e6f2c][_0x10548c(0x334)](_0xdfc40e); return _0xdfc40e; }); } return function (_0x2327f5, _0x28ca06, _0x53ed99) { var _0x253a06 = []; return function (_0x1816e5, _0x29e49e, _0x48cb46) { var _0x5efef1 = w_0x25f3; for (var _0x5857a9, _0x48222e, _0x2f7979 = new Map(), _0x2e733e = new Map(), _0x4b6d1a = 0x1 * -0xec3 + 0x1 * 0x6a2 + 0x821; _0x4b6d1a < _0x48cb46[_0x5efef1(0x259)]; _0x4b6d1a++) { var _0x58c6d3 = _0x48cb46[_0x4b6d1a]; if (Array['isArray'](_0x58c6d3)) { var _0x124b63, _0x4324aa, _0x8c2a39 = _0x58c6d3[-0x1d3a + -0x24ff + 0x423a], _0x8fabbd = _0x58c6d3[0x1b14 + -0x1 * -0x1f0c + -0x1d0f * 0x2], _0x249da3 = _0x58c6d3[_0x5efef1(0x259)] > 0x1 * 0xb42 + -0x1c1 * 0xe + -0x1 * -0xd4f, _0x11d04c = _0x8c2a39 >= -0x12b3 * 0x1 + 0x10e6 + 0x1d2; if (_0x11d04c ? (_0x124b63 = _0x29e49e, -0x15b * -0x5 + 0x373 + -0xa3a != (_0x8c2a39 -= -0x24b2 + -0x12b * 0x17 + 0x3f94) && (_0x4324aa = _0x48222e = _0x48222e || [])) : (_0x124b63 = _0x29e49e['prototype'], 0x11fa + -0x1859 + 0x65f !== _0x8c2a39 && (_0x4324aa = _0x5857a9 = _0x5857a9 || [])), -0x11df + -0x205d * 0x1 + 0x323c !== _0x8c2a39 && !_0x249da3) { var _0x3e0748 = _0x11d04c ? _0x2e733e : _0x2f7979 , _0x148749 = _0x3e0748[_0x5efef1(0x32b)](_0x8fabbd) || 0x6c0 + 0x8e3 + 0x1 * -0xfa3; if (!(-0x288 + -0x1b8f + 0x1e17) === _0x148749 || 0xcae + -0x84a + -0x461 === _0x148749 && 0xebc + -0x6cf + -0x7e9 !== _0x8c2a39 || 0x132d * 0x2 + 0x1 * 0x6ab + -0x1 * 0x2d01 === _0x148749 && 0x2144 + -0xe17 * -0x1 + -0x2f58 !== _0x8c2a39) throw new Error('Attempted\x20to\x20decorate\x20a\x20public\x20method/accessor\x20that\x20has\x20the\x20same\x20name\x20as\x20a\x20previously\x20decorated\x20public\x20method/accessor.\x20This\x20is\x20not\x20currently\x20supported\x20by\x20the\x20decorators\x20plugin.\x20Property\x20name\x20was:\x20' + _0x8fabbd); !_0x148749 && _0x8c2a39 > -0x2ea * -0xc + 0x1 * 0x24cb + 0x1 * -0x47c1 ? _0x3e0748[_0x5efef1(0x1e4)](_0x8fabbd, _0x8c2a39) : _0x3e0748[_0x5efef1(0x1e4)](_0x8fabbd, !(0xb9e + -0xc * -0x9d + -0x12fa)); } _0x3345ab(_0x1816e5, _0x124b63, _0x58c6d3, _0x8fabbd, _0x8c2a39, _0x11d04c, _0x249da3, _0x4324aa); } } _0xbbd38b(_0x1816e5, _0x5857a9), _0xbbd38b(_0x1816e5, _0x48222e); }(_0x253a06, _0x2327f5, _0x28ca06), function (_0x528ea3, _0x4f695c, _0x1527df) { var _0x436c1d = w_0x25f3; if (_0x1527df[_0x436c1d(0x259)] > 0xe5 + 0x269c + -0x2781) { for (var _0x4e0e1b = [], _0x3eca99 = _0x4f695c, _0x5c69d2 = _0x4f695c[_0x436c1d(0x341)], _0x2abebb = _0x1527df[_0x436c1d(0x259)] - (0x1 * -0x1cbe + 0xa * -0x2ba + 0x3803); _0x2abebb >= 0x8c0 + -0x44d * 0x4 + 0x874; _0x2abebb--) { var _0x30122e = { 'v': !(0x26bb + 0x34c * 0x7 + 0x1 * -0x3dce) }; try { var _0x46e0b1 = _0x1527df[_0x2abebb](_0x3eca99, { 'kind': _0x436c1d(0x19c), 'name': _0x5c69d2, 'addInitializer': _0x556aac(_0x4e0e1b, _0x30122e) }); } finally { _0x30122e['v'] = !(0x114e + 0x1f1 * -0x1 + 0x1 * -0xf5d); } void (-0x1 * -0x1c41 + 0x38d * 0x1 + -0x1fce) !== _0x46e0b1 && (_0x13d7df(0x180 + 0x6 * -0x2f + -0x5c, _0x46e0b1), _0x3eca99 = _0x46e0b1); } _0x528ea3['push'](_0x3eca99, function () { var _0x1ef0f8 = _0x436c1d; for (var _0x337d50 = 0x1 * -0x217d + -0x1a35 + -0x2 * -0x1dd9; _0x337d50 < _0x4e0e1b[_0x1ef0f8(0x259)]; _0x337d50++) _0x4e0e1b[_0x337d50]['call'](_0x3eca99); }); } }(_0x253a06, _0x2327f5, _0x53ed99), _0x253a06; } ; } var _0x15b960, _0x500e9f; function _0x4ab133(_0x538caa, _0x1dca3a, _0x362c83) { return (_0x15b960 = _0x15b960 || _0x281b3b())(_0x538caa, _0x1dca3a, _0x362c83); } function _0x4591cd() { function _0x135cea(_0x43f619, _0x5dfa54) { return function (_0x33b776) { var _0x55feea = w_0x25f3; !function (_0x4d2467, _0x1aaab5) { var _0x188bf8 = w_0x25f3; if (_0x4d2467['v']) throw new Error(_0x188bf8(0x257)); }(_0x5dfa54), _0x23bc0c(_0x33b776, _0x55feea(0x3ba)), _0x43f619[_0x55feea(0x36e)](_0x33b776); } ; } function _0x39ceae(_0x5713eb, _0x3cd64b, _0x2a5e05, _0x428f9b, _0x1cd0f5, _0x386fff, _0x54211b, _0x40ee96) { var _0x32bd6c = w_0x25f3, _0x43d2aa; switch (_0x1cd0f5) { case -0x3 * -0x9f7 + 0x13bc + -0x31a0: _0x43d2aa = _0x32bd6c(0x1b9); break; case 0x14f5 + -0x1f56 + -0x1 * -0xa63: _0x43d2aa = _0x32bd6c(0x25a); break; case -0x1 * -0x346 + -0xe + 0x335 * -0x1: _0x43d2aa = _0x32bd6c(0x181); break; case -0x2 * -0x367 + 0x16b3 + -0x1 * 0x1d7d: _0x43d2aa = _0x32bd6c(0x35d); break; default: _0x43d2aa = _0x32bd6c(0x23c); } var _0x1f76cf, _0x2d3a63, _0x52dc9f = { 'kind': _0x43d2aa, 'name': _0x54211b ? '#' + _0x3cd64b : _0x3cd64b, 'static': _0x386fff, 'private': _0x54211b }, _0x33b885 = { 'v': !(0x1 * 0xea1 + 0x22b7 + 0x3157 * -0x1) }; -0x948 + -0x2454 + 0x1c * 0x1a1 !== _0x1cd0f5 && (_0x52dc9f[_0x32bd6c(0x225)] = _0x135cea(_0x428f9b, _0x33b885)), 0xb * 0xb1 + 0x1417 + -0x1bb2 === _0x1cd0f5 ? _0x54211b ? (_0x1f76cf = _0x2a5e05[_0x32bd6c(0x32b)], _0x2d3a63 = _0x2a5e05['set']) : (_0x1f76cf = function () { return this[_0x3cd64b]; } , _0x2d3a63 = function (_0x209e9f) { this[_0x3cd64b] = _0x209e9f; } ) : 0x240f + 0xc11 * 0x3 + -0x22 * 0x220 === _0x1cd0f5 ? _0x1f76cf = function () { var _0x446fc0 = _0x32bd6c; return _0x2a5e05[_0x446fc0(0x2e4)]; } : (-0x4 * -0x1f6 + 0x1b48 + -0x231f !== _0x1cd0f5 && 0x61 * 0x1a + -0x113 * -0x11 + -0x1c1a !== _0x1cd0f5 || (_0x1f76cf = function () { var _0x447b7b = _0x32bd6c; return _0x2a5e05['get'][_0x447b7b(0x334)](this); } ), 0x1e4e + 0x25b6 * -0x1 + 0x769 !== _0x1cd0f5 && 0x1397 + -0xb7e + -0x815 !== _0x1cd0f5 || (_0x2d3a63 = function (_0x4c071d) { _0x2a5e05['set']['call'](this, _0x4c071d); } )), _0x52dc9f[_0x32bd6c(0x1d0)] = _0x1f76cf && _0x2d3a63 ? { 'get': _0x1f76cf, 'set': _0x2d3a63 } : _0x1f76cf ? { 'get': _0x1f76cf } : { 'set': _0x2d3a63 }; try { return _0x5713eb(_0x40ee96, _0x52dc9f); } finally { _0x33b885['v'] = !(-0x8c9 + 0x2 * -0x1189 + 0x2bdb); } } function _0x23bc0c(_0x4aa0f2, _0x12640a) { var _0x1b925e = w_0x25f3; if (_0x1b925e(0x1ee) != typeof _0x4aa0f2) throw new TypeError(_0x12640a + _0x1b925e(0x2f8)); } function _0x2de5e4(_0x3de3bb, _0xa398ec) { var _0xf94308 = w_0x25f3 , _0x2c8661 = typeof _0xa398ec; if (-0x33 * 0x4c + 0x5 * 0x787 + -0x1 * 0x167e === _0x3de3bb) { if (_0xf94308(0x17d) !== _0x2c8661 || null === _0xa398ec) throw new TypeError(_0xf94308(0x1fd)); void (-0x1e03 + -0x1eb + 0xff7 * 0x2) !== _0xa398ec[_0xf94308(0x32b)] && _0x23bc0c(_0xa398ec[_0xf94308(0x32b)], 'accessor.get'), void (0x3ba + -0x24db + -0xb0b * -0x3) !== _0xa398ec[_0xf94308(0x1e4)] && _0x23bc0c(_0xa398ec[_0xf94308(0x1e4)], _0xf94308(0x231)), void (0x7f8 + -0x1b10 + -0x1318 * -0x1) !== _0xa398ec[_0xf94308(0x3b9)] && _0x23bc0c(_0xa398ec['init'], _0xf94308(0x22f)); } else { if (_0xf94308(0x1ee) !== _0x2c8661) throw new TypeError((-0xf65 + 0xbd0 + 0x395 === _0x3de3bb ? _0xf94308(0x23c) : 0x48e + -0x1 * 0x1ebe + 0x1a3a === _0x3de3bb ? _0xf94308(0x19c) : _0xf94308(0x25a)) + _0xf94308(0x2cd)); } } function _0x343a1e(_0xc4b124, _0x1026d8, _0x47c875, _0x150a83, _0x4cdf11, _0x3e4a0b, _0x1dd313, _0x2b027d) { var _0x1c87ab = w_0x25f3, _0x2f46a5, _0x5a2be0, _0x547138, _0x289168, _0x1e1872, _0xd2fd89, _0x17ee52 = _0x47c875[-0x2099 + -0x133 * 0x1 + 0x21cc]; if (_0x1dd313 ? _0x2f46a5 = 0xb0a + 0x2125 + -0x2c2f === _0x4cdf11 || 0x12ea + 0x5ba + 0x385 * -0x7 === _0x4cdf11 ? { 'get': _0x47c875[0x1cf3 + 0x7 * -0x57d + 0x97b], 'set': _0x47c875[0x6 * 0x210 + -0x2b3 * 0x3 + 0x1 * -0x443] } : 0x1f7b + -0x2 * 0x1253 + 0x52e * 0x1 === _0x4cdf11 ? { 'get': _0x47c875[0x1476 + 0x23 * -0xb5 + 0x44c] } : 0x2290 + -0x20e6 + -0x1a6 === _0x4cdf11 ? { 'set': _0x47c875[0x1465 * -0x1 + -0x1 * 0x7d9 + -0x1 * -0x1c41] } : { 'value': _0x47c875[-0x1550 + 0xb0f + 0xa44] } : 0x8bb + 0x37b + 0x1 * -0xc36 !== _0x4cdf11 && (_0x2f46a5 = Object[_0x1c87ab(0x2a6)](_0x1026d8, _0x150a83)), -0xf * -0x49 + -0x8c3 * -0x1 + 0xd09 * -0x1 === _0x4cdf11 ? _0x547138 = { 'get': _0x2f46a5[_0x1c87ab(0x32b)], 'set': _0x2f46a5['set'] } : -0x7c4 + 0x1da9 + -0x15e3 === _0x4cdf11 ? _0x547138 = _0x2f46a5[_0x1c87ab(0x2e4)] : 0x7e1 + -0x159a + 0xdbc === _0x4cdf11 ? _0x547138 = _0x2f46a5[_0x1c87ab(0x32b)] : 0x105 + 0x177d * 0x1 + 0xbe * -0x21 === _0x4cdf11 && (_0x547138 = _0x2f46a5['set']), _0x1c87ab(0x1ee) == typeof _0x17ee52) void (-0x12c9 + 0x3 * -0x2f + 0x1e * 0xa5) !== (_0x289168 = _0x39ceae(_0x17ee52, _0x150a83, _0x2f46a5, _0x2b027d, _0x4cdf11, _0x3e4a0b, _0x1dd313, _0x547138)) && (_0x2de5e4(_0x4cdf11, _0x289168), -0x1cc9 + -0x2 * 0xb5d + 0x1 * 0x3383 === _0x4cdf11 ? _0x5a2be0 = _0x289168 : -0x1 * 0x1517 + 0x25 * 0x44 + 0xb44 === _0x4cdf11 ? (_0x5a2be0 = _0x289168[_0x1c87ab(0x3b9)], _0x1e1872 = _0x289168[_0x1c87ab(0x32b)] || _0x547138['get'], _0xd2fd89 = _0x289168[_0x1c87ab(0x1e4)] || _0x547138[_0x1c87ab(0x1e4)], _0x547138 = { 'get': _0x1e1872, 'set': _0xd2fd89 }) : _0x547138 = _0x289168); else for (var _0x5a46d9 = _0x17ee52[_0x1c87ab(0x259)] - (-0x49 * 0x19 + 0x4a2 + 0x280); _0x5a46d9 >= -0x225b + 0x1562 + 0xcf9; _0x5a46d9--) { var _0x60a0d7; void (-0xffd + 0x7 * -0x293 + 0x2202) !== (_0x289168 = _0x39ceae(_0x17ee52[_0x5a46d9], _0x150a83, _0x2f46a5, _0x2b027d, _0x4cdf11, _0x3e4a0b, _0x1dd313, _0x547138)) && (_0x2de5e4(_0x4cdf11, _0x289168), 0x52 * -0x59 + 0x9 * -0x104 + 0x2 * 0x12d3 === _0x4cdf11 ? _0x60a0d7 = _0x289168 : -0x1689 + 0x1e96 + -0x80c === _0x4cdf11 ? (_0x60a0d7 = _0x289168['init'], _0x1e1872 = _0x289168[_0x1c87ab(0x32b)] || _0x547138['get'], _0xd2fd89 = _0x289168[_0x1c87ab(0x1e4)] || _0x547138[_0x1c87ab(0x1e4)], _0x547138 = { 'get': _0x1e1872, 'set': _0xd2fd89 }) : _0x547138 = _0x289168, void (-0x1a77 + -0x136f + -0xeb * -0x32) !== _0x60a0d7 && (void (-0x519 + -0x1 * -0x1245 + -0x2 * 0x696) === _0x5a2be0 ? _0x5a2be0 = _0x60a0d7 : _0x1c87ab(0x1ee) == typeof _0x5a2be0 ? _0x5a2be0 = [_0x5a2be0, _0x60a0d7] : _0x5a2be0[_0x1c87ab(0x36e)](_0x60a0d7))); } if (0x78c + 0x1d71 * -0x1 + 0x15e5 * 0x1 === _0x4cdf11 || -0x49 * 0x6 + 0x1e * 0xd2 + -0x1 * 0x16e5 === _0x4cdf11) { if (void (0x21db + 0x22d * 0xd + -0x3e24) === _0x5a2be0) _0x5a2be0 = function (_0x5345b0, _0x1a659b) { return _0x1a659b; } ; else { if ('function' != typeof _0x5a2be0) { var _0x336daa = _0x5a2be0; _0x5a2be0 = function (_0x30e05d, _0xf3b4df) { var _0x4d9e7f = _0x1c87ab; for (var _0x362c52 = _0xf3b4df, _0x22ed8a = 0x1 * 0x1f3c + -0x610 + -0x192c; _0x22ed8a < _0x336daa[_0x4d9e7f(0x259)]; _0x22ed8a++) _0x362c52 = _0x336daa[_0x22ed8a]['call'](_0x30e05d, _0x362c52); return _0x362c52; } ; } else { var _0x5aed3b = _0x5a2be0; _0x5a2be0 = function (_0x5dc978, _0x5db160) { return _0x5aed3b['call'](_0x5dc978, _0x5db160); } ; } } _0xc4b124[_0x1c87ab(0x36e)](_0x5a2be0); } 0x177a + -0xe7 * -0x1c + 0x22 * -0x16f !== _0x4cdf11 && (0x3af + 0x9 * -0x217 + 0x50b * 0x3 === _0x4cdf11 ? (_0x2f46a5[_0x1c87ab(0x32b)] = _0x547138[_0x1c87ab(0x32b)], _0x2f46a5[_0x1c87ab(0x1e4)] = _0x547138[_0x1c87ab(0x1e4)]) : 0x18a3 * -0x1 + 0x263c + -0xd97 === _0x4cdf11 ? _0x2f46a5[_0x1c87ab(0x2e4)] = _0x547138 : -0x4 * -0x31b + -0x837 * 0x4 + -0x5 * -0x417 === _0x4cdf11 ? _0x2f46a5[_0x1c87ab(0x32b)] = _0x547138 : -0x1a2e + 0x2 * 0x131d + -0xc08 === _0x4cdf11 && (_0x2f46a5[_0x1c87ab(0x1e4)] = _0x547138), _0x1dd313 ? -0x1 * -0x1757 + 0x1ada + -0x3230 === _0x4cdf11 ? (_0xc4b124[_0x1c87ab(0x36e)](function (_0xb886c1, _0x3a6bf5) { var _0x288d67 = _0x1c87ab; return _0x547138[_0x288d67(0x32b)][_0x288d67(0x334)](_0xb886c1, _0x3a6bf5); }), _0xc4b124[_0x1c87ab(0x36e)](function (_0x337452, _0x50608d) { var _0x44b896 = _0x1c87ab; return _0x547138[_0x44b896(0x1e4)][_0x44b896(0x334)](_0x337452, _0x50608d); })) : -0x2 * 0x93d + 0x297 + -0x139 * -0xd === _0x4cdf11 ? _0xc4b124['push'](_0x547138) : _0xc4b124[_0x1c87ab(0x36e)](function (_0x1e24c7, _0x120c64) { var _0x3e3294 = _0x1c87ab; return _0x547138[_0x3e3294(0x334)](_0x1e24c7, _0x120c64); }) : Object[_0x1c87ab(0x175)](_0x1026d8, _0x150a83, _0x2f46a5)); } function _0x4c3a0d(_0x48207f, _0x257e35) { var _0x1249e7 = w_0x25f3; for (var _0x47fbbd, _0x402774, _0x333e3a = [], _0xe5fb1e = new Map(), _0x15f194 = new Map(), _0x41a1c1 = -0x1 * 0x575 + -0x13c0 + -0x1b * -0xef; _0x41a1c1 < _0x257e35[_0x1249e7(0x259)]; _0x41a1c1++) { var _0x63ddf3 = _0x257e35[_0x41a1c1]; if (Array[_0x1249e7(0x2af)](_0x63ddf3)) { var _0x446366, _0x186385, _0x19186f = _0x63ddf3[-0x5bc * 0x4 + 0xf6a * -0x1 + -0x3 * -0xcc9], _0x4911c0 = _0x63ddf3[0x1 * 0x46 + -0xb73 + 0xb2f], _0x39f6a1 = _0x63ddf3['length'] > -0x4 * -0x101 + -0xe6c + 0xa6b, _0x27b8e1 = _0x19186f >= 0xb76 + 0x73 * -0x34 + 0x71 * 0x1b; if (_0x27b8e1 ? (_0x446366 = _0x48207f, 0x26c + -0x157d + -0x65b * -0x3 != (_0x19186f -= 0x2324 + 0x27 * 0xbe + -0x4011) && (_0x186385 = _0x402774 = _0x402774 || [])) : (_0x446366 = _0x48207f[_0x1249e7(0x344)], -0x7 * 0x297 + 0x1b26 + -0x905 !== _0x19186f && (_0x186385 = _0x47fbbd = _0x47fbbd || [])), 0x13d6 * -0x1 + 0x1 * 0xbb7 + 0x81f !== _0x19186f && !_0x39f6a1) { var _0x3f295f = _0x27b8e1 ? _0x15f194 : _0xe5fb1e , _0x566c7b = _0x3f295f[_0x1249e7(0x32b)](_0x4911c0) || -0x188 * -0xb + 0x3e * 0x49 + -0x2286; if (!(0x903 * -0x1 + -0x1f * -0x8d + 0x1 * -0x810) === _0x566c7b || -0x43 * 0x5 + -0x1c6c + 0x2f * 0xa2 === _0x566c7b && -0x737 + -0x1ac2 + 0x21fd !== _0x19186f || 0xdd2 + -0x202b + 0x125d === _0x566c7b && -0x1 * 0x118d + -0x2b8 * -0x3 + 0x8 * 0x12d !== _0x19186f) throw new Error('Attempted\x20to\x20decorate\x20a\x20public\x20method/accessor\x20that\x20has\x20the\x20same\x20name\x20as\x20a\x20previously\x20decorated\x20public\x20method/accessor.\x20This\x20is\x20not\x20currently\x20supported\x20by\x20the\x20decorators\x20plugin.\x20Property\x20name\x20was:\x20' + _0x4911c0); !_0x566c7b && _0x19186f > 0xf * -0x26f + 0x1 * 0x79d + 0x1ce6 * 0x1 ? _0x3f295f[_0x1249e7(0x1e4)](_0x4911c0, _0x19186f) : _0x3f295f['set'](_0x4911c0, !(0x4a * 0x71 + -0xad * 0x2 + 0x6 * -0x538)); } _0x343a1e(_0x333e3a, _0x446366, _0x63ddf3, _0x4911c0, _0x19186f, _0x27b8e1, _0x39f6a1, _0x186385); } } return _0x45d367(_0x333e3a, _0x47fbbd), _0x45d367(_0x333e3a, _0x402774), _0x333e3a; } function _0x45d367(_0x59ffc8, _0x5ae59b) { var _0x5b965e = w_0x25f3; _0x5ae59b && _0x59ffc8[_0x5b965e(0x36e)](function (_0x3ed28e) { var _0x5985fb = _0x5b965e; for (var _0x5bcc94 = 0x156a + 0x18e0 + -0x2e4a; _0x5bcc94 < _0x5ae59b['length']; _0x5bcc94++) _0x5ae59b[_0x5bcc94][_0x5985fb(0x334)](_0x3ed28e); return _0x3ed28e; }); } return function (_0x33b4ae, _0x339d11, _0x5ebc36) { return { 'e': _0x4c3a0d(_0x33b4ae, _0x339d11), get 'c'() { return function (_0x92474e, _0x343c16) { var _0x3cc4d2 = w_0x25f3; if (_0x343c16['length'] > -0x271 + -0x2690 + 0x2901) { for (var _0x18f66c = [], _0x1dd6eb = _0x92474e, _0x1d8833 = _0x92474e[_0x3cc4d2(0x341)], _0x163025 = _0x343c16[_0x3cc4d2(0x259)] - (0x1 * -0x20ed + 0x1f94 + 0x2 * 0xad); _0x163025 >= 0xfba + 0x22 * 0xb6 + 0x27e6 * -0x1; _0x163025--) { var _0x49ea6f = { 'v': !(-0x524 + 0x1a34 + -0x150f) }; try { var _0x181a9d = _0x343c16[_0x163025](_0x1dd6eb, { 'kind': _0x3cc4d2(0x19c), 'name': _0x1d8833, 'addInitializer': _0x135cea(_0x18f66c, _0x49ea6f) }); } finally { _0x49ea6f['v'] = !(-0x23e + 0x1 * -0x7fb + 0xa39); } void (-0x1 * -0x253d + -0x23d + -0x80 * 0x46) !== _0x181a9d && (_0x2de5e4(0x4c5 + 0xcaf + -0x2e7 * 0x6, _0x181a9d), _0x1dd6eb = _0x181a9d); } return [_0x1dd6eb, function () { var _0x543396 = _0x3cc4d2; for (var _0x2a7175 = -0xe71 * -0x1 + 0x24c0 + -0xa3d * 0x5; _0x2a7175 < _0x18f66c['length']; _0x2a7175++) _0x18f66c[_0x2a7175][_0x543396(0x334)](_0x1dd6eb); } ]; } }(_0x33b4ae, _0x5ebc36); } }; } ; } function _0x49af1f(_0x3a86ea, _0x270f72, _0x219a67) { return (_0x49af1f = _0x4591cd())(_0x3a86ea, _0x270f72, _0x219a67); } function _0x3e4ff5(_0x5b206b, _0xdfb3e7) { return function (_0x19bdbd) { var _0x1caf0f = w_0x25f3; _0x15eb90(_0xdfb3e7, _0x1caf0f(0x225)), _0x45d8b1(_0x19bdbd, _0x1caf0f(0x3ba)), _0x5b206b[_0x1caf0f(0x36e)](_0x19bdbd); } ; } function _0x19207d(_0x45ced6, _0xcee03f) { var _0x41489e = w_0x25f3; if (!_0x45ced6(_0xcee03f)) throw new TypeError(_0x41489e(0x255)); } function _0x151762(_0x535a19, _0x4e0a69, _0x270f96, _0x52f7e9, _0x23d388, _0x49094d, _0x49f620, _0x251987, _0x40c067) { var _0x3b49d7 = w_0x25f3, _0x5d54d8; switch (_0x23d388) { case -0x14b9 + 0x1dd5 + -0x91b: _0x5d54d8 = _0x3b49d7(0x1b9); break; case -0xe4a + 0x12be + -0x472: _0x5d54d8 = _0x3b49d7(0x25a); break; case -0x10f1 * -0x2 + 0x179 * 0x19 + -0x46b0: _0x5d54d8 = _0x3b49d7(0x181); break; case 0x3 * -0x5cf + 0x778 * 0x1 + 0x353 * 0x3: _0x5d54d8 = _0x3b49d7(0x35d); break; default: _0x5d54d8 = _0x3b49d7(0x23c); } var _0x246ff5, _0x4e02d1, _0x45ccfd = { 'kind': _0x5d54d8, 'name': _0x49f620 ? '#' + _0x4e0a69 : _0x4e0a69, 'static': _0x49094d, 'private': _0x49f620 }, _0x4733a4 = { 'v': !(-0xad5 + 0x1f * -0x59 + -0x1f7 * -0xb) }; if (0xcf6 + 0x1e15 + -0x3 * 0xe59 !== _0x23d388 && (_0x45ccfd[_0x3b49d7(0x225)] = _0x3e4ff5(_0x52f7e9, _0x4733a4)), _0x49f620 || -0x112e + -0x23ab + 0x34d9 !== _0x23d388 && 0xa00 + -0x234a + 0x194c !== _0x23d388) { if (-0x327 * -0x2 + -0xb00 + 0x4b4 === _0x23d388) _0x246ff5 = function (_0x36bc44) { var _0x2d48fd = _0x3b49d7; return _0x19207d(_0x40c067, _0x36bc44), _0x270f96[_0x2d48fd(0x2e4)]; } ; else { var _0x339c00 = -0x1746 + -0x1bb + 0xad * 0x25 === _0x23d388 || -0x859 + 0x4 * -0xe2 + -0x27 * -0x4e === _0x23d388; (_0x339c00 || 0x5 * -0x3bf + -0x1d90 + 0x304e === _0x23d388) && (_0x246ff5 = _0x49f620 ? function (_0x26da64) { var _0x21bb3a = _0x3b49d7; return _0x19207d(_0x40c067, _0x26da64), _0x270f96['get'][_0x21bb3a(0x334)](_0x26da64); } : function (_0x2ac873) { var _0xa2fd4f = _0x3b49d7; return _0x270f96[_0xa2fd4f(0x32b)][_0xa2fd4f(0x334)](_0x2ac873); } ), (_0x339c00 || -0x14c4 * 0x1 + -0x231e + -0xb2e * -0x5 === _0x23d388) && (_0x4e02d1 = _0x49f620 ? function (_0x1e61ca, _0x1c77b0) { var _0x5f2230 = _0x3b49d7; _0x19207d(_0x40c067, _0x1e61ca), _0x270f96[_0x5f2230(0x1e4)][_0x5f2230(0x334)](_0x1e61ca, _0x1c77b0); } : function (_0x4d5a08, _0x3ac6c6) { var _0x1baec7 = _0x3b49d7; _0x270f96[_0x1baec7(0x1e4)][_0x1baec7(0x334)](_0x4d5a08, _0x3ac6c6); } ); } } else _0x246ff5 = function (_0x4ff2b5) { return _0x4ff2b5[_0x4e0a69]; } , -0x20ec + 0x12ee * 0x2 + -0x4f0 === _0x23d388 && (_0x4e02d1 = function (_0x2f8383, _0x4a22e5) { _0x2f8383[_0x4e0a69] = _0x4a22e5; } ); var _0x557c63 = _0x49f620 ? _0x40c067[_0x3b49d7(0x301)]() : function (_0x2f4445) { return _0x4e0a69 in _0x2f4445; } ; _0x45ccfd[_0x3b49d7(0x1d0)] = _0x246ff5 && _0x4e02d1 ? { 'get': _0x246ff5, 'set': _0x4e02d1, 'has': _0x557c63 } : _0x246ff5 ? { 'get': _0x246ff5, 'has': _0x557c63 } : { 'set': _0x4e02d1, 'has': _0x557c63 }; try { return _0x535a19(_0x251987, _0x45ccfd); } finally { _0x4733a4['v'] = !(0xea8 + -0xe9f * 0x1 + 0x1 * -0x9); } } function _0x15eb90(_0x202051, _0x1c2373) { var _0x51521b = w_0x25f3; if (_0x202051['v']) throw new Error('attempted\x20to\x20call\x20' + _0x1c2373 + _0x51521b(0x168)); } function _0x45d8b1(_0x2ad532, _0x423791) { var _0x14562d = w_0x25f3; if (_0x14562d(0x1ee) != typeof _0x2ad532) throw new TypeError(_0x423791 + _0x14562d(0x2f8)); } function _0xed525b(_0x522a86, _0x4177e1) { var _0x442324 = w_0x25f3 , _0x399204 = typeof _0x4177e1; if (-0x361 + 0x3 * 0x2c4 + 0x4ea * -0x1 === _0x522a86) { if (_0x442324(0x17d) !== _0x399204 || null === _0x4177e1) throw new TypeError(_0x442324(0x1fd)); void (-0x4a0 + 0x2e8 + 0x1b8) !== _0x4177e1[_0x442324(0x32b)] && _0x45d8b1(_0x4177e1[_0x442324(0x32b)], _0x442324(0x170)), void (0xc4 * -0x1d + 0x651 + 0xfe3) !== _0x4177e1['set'] && _0x45d8b1(_0x4177e1[_0x442324(0x1e4)], _0x442324(0x231)), void (-0x168c + 0x16b5 * 0x1 + 0x29 * -0x1) !== _0x4177e1['init'] && _0x45d8b1(_0x4177e1[_0x442324(0x3b9)], _0x442324(0x22f)); } else { if (_0x442324(0x1ee) !== _0x399204) throw new TypeError((0x20d7 + 0x7 * -0x362 + 0x7 * -0x14f === _0x522a86 ? _0x442324(0x23c) : 0x1 * -0x1257 + -0x2d9 + -0x26 * -0x8f === _0x522a86 ? 'class' : 'method') + '\x20decorators\x20must\x20return\x20a\x20function\x20or\x20void\x200'); } } function _0x244c39(_0x17683) { return function () { return _0x17683(this); } ; } function _0x48532c(_0x5503e0) { return function (_0x23d6a1) { _0x5503e0(this, _0x23d6a1); } ; } function _0x23e6b(_0x5cf198, _0x5646db, _0x26f9f9, _0x37f54b, _0x47565f, _0x2aa948, _0xccabc1, _0x5c3a48, _0x9ffbdc) { var _0x3e2adf = w_0x25f3, _0x592aeb, _0x365a6e, _0x4e8d10, _0x589008, _0x17d95a, _0x11c9ab, _0x9a7bbd = _0x26f9f9[0xa46 + 0xb3c + -0x1582]; if (_0xccabc1 ? _0x592aeb = 0x24 * 0x47 + 0xf85 + -0x1981 === _0x47565f || 0x14ca + -0x1099 + 0x218 * -0x2 === _0x47565f ? { 'get': _0x244c39(_0x26f9f9[0x7 * -0x2f0 + 0x227b + -0xde8]), 'set': _0x48532c(_0x26f9f9[-0xad * -0x19 + -0xc3c + 0x29 * -0x1d]) } : 0x1b99 * -0x1 + 0x367 + -0x1 * -0x1835 === _0x47565f ? { 'get': _0x26f9f9[0xb1 * 0x22 + 0x3 * 0xcef + -0x24 * 0x1bb] } : 0x1 * -0x10f + -0x1 * -0x2023 + -0x1f10 === _0x47565f ? { 'set': _0x26f9f9[0x36 * -0xa6 + -0x1cb2 + 0x3fb9] } : { 'value': _0x26f9f9[-0x7ba * 0x1 + 0x1850 + -0x1093] } : -0x2 * 0x713 + 0x26fe + 0x27c * -0xa !== _0x47565f && (_0x592aeb = Object[_0x3e2adf(0x2a6)](_0x5646db, _0x37f54b)), -0x1982 + 0x1947 + 0x14 * 0x3 === _0x47565f ? _0x4e8d10 = { 'get': _0x592aeb[_0x3e2adf(0x32b)], 'set': _0x592aeb[_0x3e2adf(0x1e4)] } : 0xe9b + -0x2a3 + 0x5fb * -0x2 === _0x47565f ? _0x4e8d10 = _0x592aeb['value'] : -0x13 * -0x5b + 0x17cb + -0x1 * 0x1e89 === _0x47565f ? _0x4e8d10 = _0x592aeb[_0x3e2adf(0x32b)] : 0x7d7 * -0x1 + 0x5 * 0x81 + 0x556 === _0x47565f && (_0x4e8d10 = _0x592aeb['set']), 'function' == typeof _0x9a7bbd) void (0x1 * 0x1ee3 + 0xd + -0x1ef0) !== (_0x589008 = _0x151762(_0x9a7bbd, _0x37f54b, _0x592aeb, _0x5c3a48, _0x47565f, _0x2aa948, _0xccabc1, _0x4e8d10, _0x9ffbdc)) && (_0xed525b(_0x47565f, _0x589008), 0xc91 * -0x1 + -0x893 * -0x1 + 0x3fe === _0x47565f ? _0x365a6e = _0x589008 : -0x1 * 0x21dd + -0x109 * -0x24 + -0x366 === _0x47565f ? (_0x365a6e = _0x589008[_0x3e2adf(0x3b9)], _0x17d95a = _0x589008[_0x3e2adf(0x32b)] || _0x4e8d10[_0x3e2adf(0x32b)], _0x11c9ab = _0x589008['set'] || _0x4e8d10[_0x3e2adf(0x1e4)], _0x4e8d10 = { 'get': _0x17d95a, 'set': _0x11c9ab }) : _0x4e8d10 = _0x589008); else for (var _0x4f1458 = _0x9a7bbd['length'] - (0x204c + 0x277 * 0x7 + -0xe * 0x38a); _0x4f1458 >= -0x23c2 + 0x91 + 0x2331; _0x4f1458--) { var _0x189ac8; void (-0x18f8 * -0x1 + -0x143a + -0x1 * 0x4be) !== (_0x589008 = _0x151762(_0x9a7bbd[_0x4f1458], _0x37f54b, _0x592aeb, _0x5c3a48, _0x47565f, _0x2aa948, _0xccabc1, _0x4e8d10, _0x9ffbdc)) && (_0xed525b(_0x47565f, _0x589008), -0x22cb + -0x2043 * -0x1 + 0x288 === _0x47565f ? _0x189ac8 = _0x589008 : 0x1bd * -0x3 + 0x283 * 0x8 + 0x2 * -0x770 === _0x47565f ? (_0x189ac8 = _0x589008[_0x3e2adf(0x3b9)], _0x17d95a = _0x589008[_0x3e2adf(0x32b)] || _0x4e8d10[_0x3e2adf(0x32b)], _0x11c9ab = _0x589008[_0x3e2adf(0x1e4)] || _0x4e8d10['set'], _0x4e8d10 = { 'get': _0x17d95a, 'set': _0x11c9ab }) : _0x4e8d10 = _0x589008, void (-0xaeb + 0x1 * 0x1bb3 + 0x4 * -0x432) !== _0x189ac8 && (void (-0x2359 + -0x3 * -0x323 + 0x19f0) === _0x365a6e ? _0x365a6e = _0x189ac8 : _0x3e2adf(0x1ee) == typeof _0x365a6e ? _0x365a6e = [_0x365a6e, _0x189ac8] : _0x365a6e[_0x3e2adf(0x36e)](_0x189ac8))); } if (0x21ee + -0x129b + 0xf53 * -0x1 === _0x47565f || -0x44d * -0x7 + -0xa27 + -0x13f3 * 0x1 === _0x47565f) { if (void (0x10 * -0x218 + -0x707 * 0x1 + 0x2887) === _0x365a6e) _0x365a6e = function (_0x56b70b, _0x5e4d55) { return _0x5e4d55; } ; else { if (_0x3e2adf(0x1ee) != typeof _0x365a6e) { var _0x3101a1 = _0x365a6e; _0x365a6e = function (_0x449bbf, _0x5196d8) { var _0x360050 = _0x3e2adf; for (var _0x143a4f = _0x5196d8, _0x36e4b1 = -0x1ef8 + -0x3 * -0xd01 + -0x80b * 0x1; _0x36e4b1 < _0x3101a1[_0x360050(0x259)]; _0x36e4b1++) _0x143a4f = _0x3101a1[_0x36e4b1][_0x360050(0x334)](_0x449bbf, _0x143a4f); return _0x143a4f; } ; } else { var _0x1e2902 = _0x365a6e; _0x365a6e = function (_0xe6998d, _0xec7c91) { var _0x5bb1b6 = _0x3e2adf; return _0x1e2902[_0x5bb1b6(0x334)](_0xe6998d, _0xec7c91); } ; } } _0x5cf198['push'](_0x365a6e); } 0x82e + 0x14c5 + -0x1cf3 !== _0x47565f && (-0x1 * -0x1d72 + 0x15b0 + 0x3 * -0x110b === _0x47565f ? (_0x592aeb[_0x3e2adf(0x32b)] = _0x4e8d10[_0x3e2adf(0x32b)], _0x592aeb['set'] = _0x4e8d10['set']) : 0x1de7 + -0x16 * -0x10a + -0x34c1 === _0x47565f ? _0x592aeb[_0x3e2adf(0x2e4)] = _0x4e8d10 : -0x2508 + -0x2665 + 0x4b70 === _0x47565f ? _0x592aeb[_0x3e2adf(0x32b)] = _0x4e8d10 : 0x11 * -0x9f + -0x6a5 + 0x1138 === _0x47565f && (_0x592aeb[_0x3e2adf(0x1e4)] = _0x4e8d10), _0xccabc1 ? 0x8f6 * 0x1 + -0xae9 + 0xa * 0x32 === _0x47565f ? (_0x5cf198['push'](function (_0x31015b, _0x979f27) { var _0x1938a1 = _0x3e2adf; return _0x4e8d10['get'][_0x1938a1(0x334)](_0x31015b, _0x979f27); }), _0x5cf198[_0x3e2adf(0x36e)](function (_0x506584, _0x44c428) { var _0x29e7e5 = _0x3e2adf; return _0x4e8d10['set'][_0x29e7e5(0x334)](_0x506584, _0x44c428); })) : 0x27 * -0xd0 + 0x4 * -0x12d + 0x2466 === _0x47565f ? _0x5cf198[_0x3e2adf(0x36e)](_0x4e8d10) : _0x5cf198[_0x3e2adf(0x36e)](function (_0x3345b4, _0x845662) { var _0x1b7734 = _0x3e2adf; return _0x4e8d10[_0x1b7734(0x334)](_0x3345b4, _0x845662); }) : Object['defineProperty'](_0x5646db, _0x37f54b, _0x592aeb)); } function _0x754898(_0x2bd2c5, _0x2c3dcd, _0x119780) { var _0x42b3ee = w_0x25f3; for (var _0x328f7d, _0x12addf, _0x29a079, _0x3330d8 = [], _0x484293 = new Map(), _0x4a2ea8 = new Map(), _0x478e5d = 0x1 * -0x3e1 + -0x22b8 + 0x2699; _0x478e5d < _0x2c3dcd[_0x42b3ee(0x259)]; _0x478e5d++) { var _0x5c7927 = _0x2c3dcd[_0x478e5d]; if (Array[_0x42b3ee(0x2af)](_0x5c7927)) { var _0x25ea34, _0x5b9b12, _0x35b641 = _0x5c7927[-0x33b * 0x1 + 0xebe + 0x3 * -0x3d6], _0x49a803 = _0x5c7927[-0x78a + -0x29 * 0x60 + 0xc * 0x1e9], _0x410da0 = _0x5c7927[_0x42b3ee(0x259)] > 0x1291 + -0x1e03 + 0xb75, _0x3c9168 = _0x35b641 >= -0x13e1 * 0x1 + 0x793 + -0x5 * -0x277, _0x41ada5 = _0x119780; if (_0x3c9168 ? (_0x25ea34 = _0x2bd2c5, -0x7e3 + -0x1087 * 0x2 + 0x28f1 != (_0x35b641 -= 0x867 + 0xa1b + 0x127d * -0x1) && (_0x5b9b12 = _0x12addf = _0x12addf || []), _0x410da0 && !_0x29a079 && (_0x29a079 = function (_0x106ee5) { return _0x37e1e2(_0x106ee5) === _0x2bd2c5; } ), _0x41ada5 = _0x29a079) : (_0x25ea34 = _0x2bd2c5['prototype'], 0x13a3 * 0x1 + -0x2589 + 0x11e6 * 0x1 !== _0x35b641 && (_0x5b9b12 = _0x328f7d = _0x328f7d || [])), 0xfad + 0x3 * -0xceb + 0x1714 !== _0x35b641 && !_0x410da0) { var _0x4430b1 = _0x3c9168 ? _0x4a2ea8 : _0x484293 , _0x3a9a47 = _0x4430b1[_0x42b3ee(0x32b)](_0x49a803) || 0x139e + -0xa * 0x13d + -0x73c; if (!(-0x1 * 0x5df + 0x94b + -0x36c) === _0x3a9a47 || 0x3 * 0x38 + -0x25b * 0x7 + -0x8 * -0x1fb === _0x3a9a47 && 0x54 + 0x152e + -0x2a * 0x83 !== _0x35b641 || 0x2 * -0x1cb + 0x1e71 + -0x1ad7 * 0x1 === _0x3a9a47 && 0x6e9 + 0xa71 + 0xc1 * -0x17 !== _0x35b641) throw new Error('Attempted\x20to\x20decorate\x20a\x20public\x20method/accessor\x20that\x20has\x20the\x20same\x20name\x20as\x20a\x20previously\x20decorated\x20public\x20method/accessor.\x20This\x20is\x20not\x20currently\x20supported\x20by\x20the\x20decorators\x20plugin.\x20Property\x20name\x20was:\x20' + _0x49a803); !_0x3a9a47 && _0x35b641 > -0x73 * -0x45 + 0x798 + -0x2695 ? _0x4430b1['set'](_0x49a803, _0x35b641) : _0x4430b1['set'](_0x49a803, !(0x1 * 0xb7c + -0x15ce * 0x1 + 0xa52)); } _0x23e6b(_0x3330d8, _0x25ea34, _0x5c7927, _0x49a803, _0x35b641, _0x3c9168, _0x410da0, _0x5b9b12, _0x41ada5); } } return _0xdfc9aa(_0x3330d8, _0x328f7d), _0xdfc9aa(_0x3330d8, _0x12addf), _0x3330d8; } function _0xdfc9aa(_0x18680e, _0x994303) { var _0x3df611 = w_0x25f3; _0x994303 && _0x18680e[_0x3df611(0x36e)](function (_0x3720d6) { var _0xc905ee = _0x3df611; for (var _0x400047 = 0x17dc + -0x3df * 0x6 + -0xa2; _0x400047 < _0x994303['length']; _0x400047++) _0x994303[_0x400047][_0xc905ee(0x334)](_0x3720d6); return _0x3720d6; }); } function _0x2258b9(_0x31d7ec, _0x22dcdb) { var _0x41474b = w_0x25f3; if (_0x22dcdb[_0x41474b(0x259)] > 0x26a0 + -0x145f + -0x1241) { for (var _0x3c54e9 = [], _0x8cb428 = _0x31d7ec, _0x2344cd = _0x31d7ec['name'], _0x16f8af = _0x22dcdb[_0x41474b(0x259)] - (0x1321 + 0x1388 + 0x1354 * -0x2); _0x16f8af >= 0x1a79 + 0x6f * 0x2a + -0x2caf; _0x16f8af--) { var _0x36735e = { 'v': !(0x4 * -0x601 + 0x116d * 0x1 + -0x698 * -0x1) }; try { var _0x147e7c = _0x22dcdb[_0x16f8af](_0x8cb428, { 'kind': _0x41474b(0x19c), 'name': _0x2344cd, 'addInitializer': _0x3e4ff5(_0x3c54e9, _0x36735e) }); } finally { _0x36735e['v'] = !(0x1e88 + -0x1973 + -0x515 * 0x1); } void (-0xedb + -0x5 * 0x2bf + 0x1c96) !== _0x147e7c && (_0xed525b(0x10b8 + -0xb * 0x2e1 + -0x3 * -0x4ff, _0x147e7c), _0x8cb428 = _0x147e7c); } return [_0x8cb428, function () { var _0x28afa7 = _0x41474b; for (var _0x4967c5 = -0x1 * 0x23b5 + -0x2 * -0x4c7 + 0x203 * 0xd; _0x4967c5 < _0x3c54e9[_0x28afa7(0x259)]; _0x4967c5++) _0x3c54e9[_0x4967c5][_0x28afa7(0x334)](_0x8cb428); } ]; } } function _0x499d65(_0x2be690, _0x428d43, _0x381e26, _0x409a13) { return { 'e': _0x754898(_0x2be690, _0x428d43, _0x409a13), get 'c'() { return _0x2258b9(_0x2be690, _0x381e26); } }; } function _0x63f01f(_0x5fd149) { var _0x2e1243 = w_0x25f3 , _0x111c4c = {} , _0x2aa094 = !(-0x3d * -0x53 + -0x2086 * -0x1 + -0x344c); function _0x3ad3fe(_0x4a2256, _0x3bcff1) { return _0x2aa094 = !(0x160 * 0x3 + -0x4b5 * 0x1 + 0x1 * 0x95), { 'done': !(0x1cd3 + 0x1 * -0xa11 + -0x12c1 * 0x1), 'value': new _0x59d886(_0x3bcff1 = new Promise(function (_0x353966) { _0x353966(_0x5fd149[_0x4a2256](_0x3bcff1)); } ), 0x2b * 0x44 + -0x8b3 * 0x2 + 0x1 * 0x5fb) }; } return _0x111c4c[_0x2e1243(0x384) != typeof Symbol && Symbol['iterator'] || _0x2e1243(0x1dd)] = function () { return this; } , _0x111c4c[_0x2e1243(0x389)] = function (_0x45912d) { return _0x2aa094 ? (_0x2aa094 = !(0x1569 + 0x7 * 0x314 + -0x2af4), _0x45912d) : _0x3ad3fe('next', _0x45912d); } , _0x2e1243(0x1ee) == typeof _0x5fd149[_0x2e1243(0x250)] && (_0x111c4c[_0x2e1243(0x250)] = function (_0xd71e81) { var _0x37886e = _0x2e1243; if (_0x2aa094) throw _0x2aa094 = !(-0x648 + -0x1eca + -0x2513 * -0x1), _0xd71e81; return _0x3ad3fe(_0x37886e(0x250), _0xd71e81); } ), _0x2e1243(0x1ee) == typeof _0x5fd149[_0x2e1243(0x2fd)] && (_0x111c4c[_0x2e1243(0x2fd)] = function (_0xd4b711) { return _0x2aa094 ? (_0x2aa094 = !(-0x8e1 * -0x4 + -0x2194 + 0x3 * -0xa5), _0xd4b711) : _0x3ad3fe('return', _0xd4b711); } ), _0x111c4c; } function _0x278b9f(_0x12b945) { var _0x55cd1f = w_0x25f3, _0x449dfe, _0x31b182, _0x218f32, _0x2ecb8a = -0x1d9 * -0x5 + -0x5d4 + -0x367; for ('undefined' != typeof Symbol && (_0x31b182 = Symbol[_0x55cd1f(0x17c)], _0x218f32 = Symbol[_0x55cd1f(0x3b3)]); _0x2ecb8a--;) { if (_0x31b182 && null != (_0x449dfe = _0x12b945[_0x31b182])) return _0x449dfe[_0x55cd1f(0x334)](_0x12b945); if (_0x218f32 && null != (_0x449dfe = _0x12b945[_0x218f32])) return new _0x3d6fc9(_0x449dfe[_0x55cd1f(0x334)](_0x12b945)); _0x31b182 = _0x55cd1f(0x230), _0x218f32 = _0x55cd1f(0x1dd); } throw new TypeError(_0x55cd1f(0x2a9)); } function _0x3d6fc9(_0x4d37b6) { var _0x36b1b0 = w_0x25f3; function _0x6c368b(_0x231d6c) { var _0x3a3e78 = w_0x25f3; if (Object(_0x231d6c) !== _0x231d6c) return Promise['reject'](new TypeError(_0x231d6c + _0x3a3e78(0x3a6))); var _0x4cdd04 = _0x231d6c[_0x3a3e78(0x1e3)]; return Promise['resolve'](_0x231d6c[_0x3a3e78(0x2e4)])['then'](function (_0x468cc1) { return { 'value': _0x468cc1, 'done': _0x4cdd04 }; }); } return (_0x3d6fc9 = function (_0x5a83bf) { var _0x2ffee8 = w_0x25f3; this['s'] = _0x5a83bf, this['n'] = _0x5a83bf[_0x2ffee8(0x389)]; } )[_0x36b1b0(0x344)] = { 's': null, 'n': null, 'next': function () { var _0x1eac7c = _0x36b1b0; return _0x6c368b(this['n'][_0x1eac7c(0x207)](this['s'], arguments)); }, 'return': function (_0x379438) { var _0x45dc23 = _0x36b1b0 , _0x35ef86 = this['s'][_0x45dc23(0x2fd)]; return void (0x23cd + 0x6f3 * 0x1 + -0x48 * 0x98) === _0x35ef86 ? Promise[_0x45dc23(0x278)]({ 'value': _0x379438, 'done': !(-0x220 + -0x16b0 + 0x8 * 0x31a) }) : _0x6c368b(_0x35ef86[_0x45dc23(0x207)](this['s'], arguments)); }, 'throw': function (_0x2ca86a) { var _0x5812c4 = _0x36b1b0 , _0x449ee5 = this['s'][_0x5812c4(0x2fd)]; return void (0xe3b + 0x1 * -0x128e + 0x453) === _0x449ee5 ? Promise[_0x5812c4(0x39b)](_0x2ca86a) : _0x6c368b(_0x449ee5['apply'](this['s'], arguments)); } }, new _0x3d6fc9(_0x4d37b6); } function _0x81c36b(_0x305287) { return new _0x59d886(_0x305287, -0x5e + 0xf84 + -0xf26 * 0x1); } function _0x37e1e2(_0x3127ce) { var _0x43b88e = w_0x25f3; if (Object(_0x3127ce) !== _0x3127ce) throw TypeError('right-hand\x20side\x20of\x20\x27in\x27\x20should\x20be\x20an\x20object,\x20got\x20' + (null !== _0x3127ce ? typeof _0x3127ce : _0x43b88e(0x308))); return _0x3127ce; } function _0x564673(_0x18bd7c, _0x51fb0f, _0x2103c5, _0x5f4619) { var _0x41c64 = w_0x25f3 , _0xab8c16 = { 'configurable': !(-0x15c4 + 0x25f7 * -0x1 + 0x3bbb), 'enumerable': !(0x2581 + -0x121c + -0x1365) }; return _0xab8c16[_0x18bd7c] = _0x5f4619, Object[_0x41c64(0x175)](_0x51fb0f, _0x2103c5, _0xab8c16); } function _0x19d66a(_0x14c297, _0x128d38) { var _0x48eab4 = w_0x25f3 , _0x25f943 = null == _0x14c297 ? null : _0x48eab4(0x384) != typeof Symbol && _0x14c297[Symbol[_0x48eab4(0x3b3)]] || _0x14c297[_0x48eab4(0x1dd)]; if (null != _0x25f943) { var _0x285958, _0x5bcc9e, _0x2d4c39, _0x193c70, _0x146015 = [], _0x16efb7 = !(-0x7e * 0x36 + 0xcff * 0x1 + 0xd95), _0x5c9556 = !(-0x1d71 * -0x1 + 0x434 * -0x9 + 0x864); try { if (_0x2d4c39 = (_0x25f943 = _0x25f943[_0x48eab4(0x334)](_0x14c297))['next'], -0xe3 * -0xb + -0x1f3 * 0xe + -0x43 * -0x43 === _0x128d38) { if (Object(_0x25f943) !== _0x25f943) return; _0x16efb7 = !(0x17cb + 0x18a9 + -0x4f * 0x9d); } else { for (; !(_0x16efb7 = (_0x285958 = _0x2d4c39['call'](_0x25f943))[_0x48eab4(0x1e3)]) && (_0x146015[_0x48eab4(0x36e)](_0x285958[_0x48eab4(0x2e4)]), _0x146015[_0x48eab4(0x259)] !== _0x128d38); _0x16efb7 = !(0x1 * -0x22d3 + 0x1f2e + 0x3 * 0x137)) ; } } catch (_0x22e14d) { _0x5c9556 = !(-0x3 * -0x69e + -0xd79 + -0x47 * 0x17), _0x5bcc9e = _0x22e14d; } finally { try { if (!_0x16efb7 && null != _0x25f943[_0x48eab4(0x2fd)] && (_0x193c70 = _0x25f943['return'](), Object(_0x193c70) !== _0x193c70)) return; } finally { if (_0x5c9556) throw _0x5bcc9e; } } return _0x146015; } } function _0x376fd5(_0x233550, _0x1b337f) { var _0x545ac4 = w_0x25f3 , _0x524438 = _0x233550 && (_0x545ac4(0x384) != typeof Symbol && _0x233550[Symbol[_0x545ac4(0x3b3)]] || _0x233550['@@iterator']); if (null != _0x524438) { var _0x259ce6, _0x4523ee = []; for (_0x524438 = _0x524438['call'](_0x233550); _0x233550[_0x545ac4(0x259)] < _0x1b337f && !(_0x259ce6 = _0x524438['next']())[_0x545ac4(0x1e3)];) _0x4523ee[_0x545ac4(0x36e)](_0x259ce6['value']); return _0x4523ee; } } function _0x2e4b86(_0x760974, _0x438efa, _0x37a20b, _0x52b279) { var _0x2644f8 = w_0x25f3; _0x500e9f || (_0x500e9f = _0x2644f8(0x1ee) == typeof Symbol && Symbol[_0x2644f8(0x31e)] && Symbol[_0x2644f8(0x31e)](_0x2644f8(0x279)) || 0x1353f + -0x1157 + -0x3921); var _0x513b54 = _0x760974 && _0x760974[_0x2644f8(0x371)] , _0x5469e9 = arguments[_0x2644f8(0x259)] - (-0xcb * -0xb + -0xa * 0x137 + -0x28 * -0x16); if (_0x438efa || 0x109 * -0x1c + 0xdc1 + 0xf3b === _0x5469e9 || (_0x438efa = { 'children': void (-0x2 * -0x1037 + 0x32f + -0x239d) }), -0xf69 + 0xedb + -0x8f * -0x1 === _0x5469e9) _0x438efa[_0x2644f8(0x16f)] = _0x52b279; else { if (_0x5469e9 > -0x18e2 + -0x25af + 0x3e92) { for (var _0x5c6f82 = new Array(_0x5469e9), _0x63cb90 = -0x1 * -0xebd + -0x1a37 + 0x2 * 0x5bd; _0x63cb90 < _0x5469e9; _0x63cb90++) _0x5c6f82[_0x63cb90] = arguments[_0x63cb90 + (0x15f5 + -0x245f + -0x4cf * -0x3)]; _0x438efa[_0x2644f8(0x16f)] = _0x5c6f82; } } if (_0x438efa && _0x513b54) { for (var _0xbcee2c in _0x513b54) void (-0x1091 * -0x1 + -0x3 * 0x34d + -0x6aa) === _0x438efa[_0xbcee2c] && (_0x438efa[_0xbcee2c] = _0x513b54[_0xbcee2c]); } else _0x438efa || (_0x438efa = _0x513b54 || {}); return { '$$typeof': _0x500e9f, 'type': _0x760974, 'key': void (-0x7 * 0x1cd + -0x1 * -0xb38 + 0x163) === _0x37a20b ? null : '' + _0x37a20b, 'ref': null, 'props': _0x438efa, '_owner': null }; } function _0x3a3eb5(_0x4619be, _0x3fc822) { var _0x44978d = w_0x25f3 , _0x504f59 = Object[_0x44978d(0x17f)](_0x4619be); if (Object[_0x44978d(0x388)]) { var _0x4ea700 = Object['getOwnPropertySymbols'](_0x4619be); _0x3fc822 && (_0x4ea700 = _0x4ea700[_0x44978d(0x164)](function (_0x2a0c03) { var _0x162328 = _0x44978d; return Object[_0x162328(0x2a6)](_0x4619be, _0x2a0c03)[_0x162328(0x23a)]; })), _0x504f59['push'][_0x44978d(0x207)](_0x504f59, _0x4ea700); } return _0x504f59; } function _0x4ee2dd(_0xf371f0) { var _0x145ce6 = w_0x25f3; for (var _0x5e36a1 = -0x5 * 0x1ad + 0x11e6 * -0x1 + 0x1a48; _0x5e36a1 < arguments[_0x145ce6(0x259)]; _0x5e36a1++) { var _0xdb889d = null != arguments[_0x5e36a1] ? arguments[_0x5e36a1] : {}; _0x5e36a1 % (0x1174 + 0xeb3 + -0x2025) ? _0x3a3eb5(Object(_0xdb889d), !(-0x263c * 0x1 + 0x1 * -0xf7f + 0x7ad * 0x7))[_0x145ce6(0x254)](function (_0x328928) { _0x4a7824(_0xf371f0, _0x328928, _0xdb889d[_0x328928]); }) : Object[_0x145ce6(0x1ec)] ? Object[_0x145ce6(0x36c)](_0xf371f0, Object[_0x145ce6(0x1ec)](_0xdb889d)) : _0x3a3eb5(Object(_0xdb889d))[_0x145ce6(0x254)](function (_0x593f5f) { var _0x53f055 = _0x145ce6; Object[_0x53f055(0x175)](_0xf371f0, _0x593f5f, Object[_0x53f055(0x2a6)](_0xdb889d, _0x593f5f)); }); } return _0xf371f0; } function _0x385d19() { var _0x44a0fc = w_0x25f3; _0x385d19 = function () { return _0x507b23; } ; var _0x507b23 = {} , _0x39fa01 = Object[_0x44a0fc(0x344)] , _0x43238c = _0x39fa01[_0x44a0fc(0x3b8)] , _0x4cf7d8 = Object['defineProperty'] || function (_0x2541c5, _0x22c938, _0x405bab) { var _0x32f2ae = _0x44a0fc; _0x2541c5[_0x22c938] = _0x405bab[_0x32f2ae(0x2e4)]; } , _0x1c97cc = _0x44a0fc(0x1ee) == typeof Symbol ? Symbol : {} , _0x5acd1a = _0x1c97cc[_0x44a0fc(0x3b3)] || _0x44a0fc(0x1dd) , _0x52f292 = _0x1c97cc['asyncIterator'] || _0x44a0fc(0x230) , _0x447ab4 = _0x1c97cc[_0x44a0fc(0x1c0)] || _0x44a0fc(0x1e0); function _0x3baf2c(_0x30f952, _0x3ef614, _0x2d2b61) { var _0x598819 = _0x44a0fc; return Object[_0x598819(0x175)](_0x30f952, _0x3ef614, { 'value': _0x2d2b61, 'enumerable': !(0x1b1a * -0x1 + 0x1 * 0x2696 + -0x31 * 0x3c), 'configurable': !(-0x1a13 + 0x5 * -0x6d + 0xa * 0x2d2), 'writable': !(-0x1cd + 0x214e + 0x1f81 * -0x1) }), _0x30f952[_0x3ef614]; } try { _0x3baf2c({}, ''); } catch (_0x275159) { _0x3baf2c = function (_0x37fa7b, _0x2671f0, _0x45b82e) { return _0x37fa7b[_0x2671f0] = _0x45b82e; } ; } function _0x3e46c6(_0x50c0cc, _0x5f3c83, _0x148917, _0x2e5141) { var _0x1a25db = _0x44a0fc , _0x1b0fa0 = _0x5f3c83 && _0x5f3c83['prototype'] instanceof _0x50f36d ? _0x5f3c83 : _0x50f36d , _0x4f06ab = Object[_0x1a25db(0x3b7)](_0x1b0fa0[_0x1a25db(0x344)]) , _0x3e4999 = new _0x1b5db9(_0x2e5141 || []); return _0x4cf7d8(_0x4f06ab, _0x1a25db(0x1b1), { 'value': _0x121728(_0x50c0cc, _0x148917, _0x3e4999) }), _0x4f06ab; } function _0x575dfd(_0x48fd20, _0x6545ef, _0x242936) { var _0x580d50 = _0x44a0fc; try { return { 'type': _0x580d50(0x2f1), 'arg': _0x48fd20[_0x580d50(0x334)](_0x6545ef, _0x242936) }; } catch (_0x5aebac) { return { 'type': 'throw', 'arg': _0x5aebac }; } } _0x507b23['wrap'] = _0x3e46c6; var _0x511b05 = {}; function _0x50f36d() { } function _0x2e67e9() { } function _0x3a5ac7() { } var _0x4c3008 = {}; _0x3baf2c(_0x4c3008, _0x5acd1a, function () { return this; }); var _0x118945 = Object[_0x44a0fc(0x22c)] , _0x5d1d04 = _0x118945 && _0x118945(_0x118945(_0x2edbf4([]))); _0x5d1d04 && _0x5d1d04 !== _0x39fa01 && _0x43238c[_0x44a0fc(0x334)](_0x5d1d04, _0x5acd1a) && (_0x4c3008 = _0x5d1d04); var _0x42763b = _0x3a5ac7[_0x44a0fc(0x344)] = _0x50f36d['prototype'] = Object[_0x44a0fc(0x3b7)](_0x4c3008); function _0x4e156b(_0x314ec0) { var _0x1229ff = _0x44a0fc; [_0x1229ff(0x389), _0x1229ff(0x250), _0x1229ff(0x2fd)][_0x1229ff(0x254)](function (_0x1cac9a) { _0x3baf2c(_0x314ec0, _0x1cac9a, function (_0x509669) { var _0x3b5587 = w_0x25f3; return this[_0x3b5587(0x1b1)](_0x1cac9a, _0x509669); }); }); } function _0x23eb73(_0x5cb4f0, _0xfdda3) { var _0x2f70bc; _0x4cf7d8(this, '_invoke', { 'value': function (_0x47f883, _0x34dd78) { var _0x5c5cca = w_0x25f3; function _0x65e836() { return new _0xfdda3(function (_0x494e99, _0x39f97a) { !function _0x19e170(_0xd94afd, _0x7f5a3, _0x1fc978, _0x392c17) { var _0x5108db = w_0x25f3 , _0x123c2b = _0x575dfd(_0x5cb4f0[_0xd94afd], _0x5cb4f0, _0x7f5a3); if (_0x5108db(0x250) !== _0x123c2b[_0x5108db(0x1ab)]) { var _0x57ff30 = _0x123c2b[_0x5108db(0x327)] , _0x519f3c = _0x57ff30[_0x5108db(0x2e4)]; return _0x519f3c && 'object' == typeof _0x519f3c && _0x43238c[_0x5108db(0x334)](_0x519f3c, _0x5108db(0x2b1)) ? _0xfdda3[_0x5108db(0x278)](_0x519f3c[_0x5108db(0x2b1)])[_0x5108db(0x1ed)](function (_0x513ad4) { var _0x372e8b = _0x5108db; _0x19e170(_0x372e8b(0x389), _0x513ad4, _0x1fc978, _0x392c17); }, function (_0x3ee973) { _0x19e170('throw', _0x3ee973, _0x1fc978, _0x392c17); }) : _0xfdda3[_0x5108db(0x278)](_0x519f3c)[_0x5108db(0x1ed)](function (_0xb8049c) { var _0x3f5791 = _0x5108db; _0x57ff30[_0x3f5791(0x2e4)] = _0xb8049c, _0x1fc978(_0x57ff30); }, function (_0x163a1d) { var _0x43e785 = _0x5108db; return _0x19e170(_0x43e785(0x250), _0x163a1d, _0x1fc978, _0x392c17); }); } _0x392c17(_0x123c2b[_0x5108db(0x327)]); }(_0x47f883, _0x34dd78, _0x494e99, _0x39f97a); } ); } return _0x2f70bc = _0x2f70bc ? _0x2f70bc[_0x5c5cca(0x1ed)](_0x65e836, _0x65e836) : _0x65e836(); } }); } function _0x121728(_0x39e85b, _0x26363f, _0x3838fc) { var _0x4a9454 = 'suspendedStart'; return function (_0x33b907, _0x5c348b) { var _0x14af93 = w_0x25f3; if (_0x14af93(0x2ef) === _0x4a9454) throw new Error(_0x14af93(0x35a)); if ('completed' === _0x4a9454) { if ('throw' === _0x33b907) throw _0x5c348b; return _0x159200(); } for (_0x3838fc[_0x14af93(0x25a)] = _0x33b907, _0x3838fc[_0x14af93(0x327)] = _0x5c348b; ;) { var _0x43652a = _0x3838fc[_0x14af93(0x1bc)]; if (_0x43652a) { var _0xc12f6f = _0x5489f3(_0x43652a, _0x3838fc); if (_0xc12f6f) { if (_0xc12f6f === _0x511b05) continue; return _0xc12f6f; } } if (_0x14af93(0x389) === _0x3838fc[_0x14af93(0x25a)]) _0x3838fc[_0x14af93(0x3b2)] = _0x3838fc[_0x14af93(0x2a2)] = _0x3838fc[_0x14af93(0x327)]; else { if ('throw' === _0x3838fc[_0x14af93(0x25a)]) { if ('suspendedStart' === _0x4a9454) throw _0x4a9454 = _0x14af93(0x16a), _0x3838fc['arg']; _0x3838fc[_0x14af93(0x2cb)](_0x3838fc[_0x14af93(0x327)]); } else _0x14af93(0x2fd) === _0x3838fc[_0x14af93(0x25a)] && _0x3838fc['abrupt'](_0x14af93(0x2fd), _0x3838fc['arg']); } _0x4a9454 = _0x14af93(0x2ef); var _0x1a99b5 = _0x575dfd(_0x39e85b, _0x26363f, _0x3838fc); if ('normal' === _0x1a99b5['type']) { if (_0x4a9454 = _0x3838fc[_0x14af93(0x1e3)] ? _0x14af93(0x16a) : 'suspendedYield', _0x1a99b5[_0x14af93(0x327)] === _0x511b05) continue; return { 'value': _0x1a99b5[_0x14af93(0x327)], 'done': _0x3838fc[_0x14af93(0x1e3)] }; } _0x14af93(0x250) === _0x1a99b5[_0x14af93(0x1ab)] && (_0x4a9454 = _0x14af93(0x16a), _0x3838fc[_0x14af93(0x25a)] = _0x14af93(0x250), _0x3838fc[_0x14af93(0x327)] = _0x1a99b5[_0x14af93(0x327)]); } } ; } function _0x5489f3(_0x3c1e2d, _0x59877f) { var _0x299ed9 = _0x44a0fc , _0x1a45fc = _0x59877f['method'] , _0x116edb = _0x3c1e2d[_0x299ed9(0x3b3)][_0x1a45fc]; if (void (-0x15fe + 0xe93 + 0x76b) === _0x116edb) return _0x59877f[_0x299ed9(0x1bc)] = null, _0x299ed9(0x250) === _0x1a45fc && _0x3c1e2d[_0x299ed9(0x3b3)][_0x299ed9(0x2fd)] && (_0x59877f['method'] = 'return', _0x59877f[_0x299ed9(0x327)] = void (-0x78 * -0x25 + -0x2272 + 0x111a * 0x1), _0x5489f3(_0x3c1e2d, _0x59877f), _0x299ed9(0x250) === _0x59877f[_0x299ed9(0x25a)]) || 'return' !== _0x1a45fc && (_0x59877f[_0x299ed9(0x25a)] = _0x299ed9(0x250), _0x59877f[_0x299ed9(0x327)] = new TypeError('The\x20iterator\x20does\x20not\x20provide\x20a\x20\x27' + _0x1a45fc + _0x299ed9(0x32f))), _0x511b05; var _0x5be46a = _0x575dfd(_0x116edb, _0x3c1e2d[_0x299ed9(0x3b3)], _0x59877f[_0x299ed9(0x327)]); if (_0x299ed9(0x250) === _0x5be46a[_0x299ed9(0x1ab)]) return _0x59877f[_0x299ed9(0x25a)] = _0x299ed9(0x250), _0x59877f['arg'] = _0x5be46a[_0x299ed9(0x327)], _0x59877f[_0x299ed9(0x1bc)] = null, _0x511b05; var _0x44f533 = _0x5be46a[_0x299ed9(0x327)]; return _0x44f533 ? _0x44f533['done'] ? (_0x59877f[_0x3c1e2d[_0x299ed9(0x16e)]] = _0x44f533[_0x299ed9(0x2e4)], _0x59877f['next'] = _0x3c1e2d[_0x299ed9(0x281)], _0x299ed9(0x2fd) !== _0x59877f[_0x299ed9(0x25a)] && (_0x59877f[_0x299ed9(0x25a)] = _0x299ed9(0x389), _0x59877f['arg'] = void (0x1a2 * -0x17 + -0x1910 + 0x5 * 0xc86)), _0x59877f[_0x299ed9(0x1bc)] = null, _0x511b05) : _0x44f533 : (_0x59877f[_0x299ed9(0x25a)] = _0x299ed9(0x250), _0x59877f['arg'] = new TypeError(_0x299ed9(0x251)), _0x59877f[_0x299ed9(0x1bc)] = null, _0x511b05); } function _0x14015c(_0x5bad37) { var _0x42a847 = _0x44a0fc , _0x401cb9 = { 'tryLoc': _0x5bad37[-0x953 * 0x3 + 0xd3c + 0xebd] }; -0x85b + 0x1181 + -0x925 in _0x5bad37 && (_0x401cb9['catchLoc'] = _0x5bad37[0x12b4 + -0x1d08 + 0xa55]), 0x4dc + 0x1b6f + 0xac3 * -0x3 in _0x5bad37 && (_0x401cb9[_0x42a847(0x220)] = _0x5bad37[-0xd0c * -0x2 + 0x914 + -0xe * 0x283], _0x401cb9[_0x42a847(0x2fb)] = _0x5bad37[0x7 * -0x216 + 0x702 + 0x79b]), this['tryEntries'][_0x42a847(0x36e)](_0x401cb9); } function _0x2f82b3(_0x43fd78) { var _0x38de29 = _0x44a0fc , _0x2f1d6c = _0x43fd78[_0x38de29(0x387)] || {}; _0x2f1d6c['type'] = _0x38de29(0x2f1), delete _0x2f1d6c[_0x38de29(0x327)], _0x43fd78['completion'] = _0x2f1d6c; } function _0x1b5db9(_0x53b843) { var _0x2569b6 = _0x44a0fc; this[_0x2569b6(0x337)] = [{ 'tryLoc': _0x2569b6(0x1c3) }], _0x53b843[_0x2569b6(0x254)](_0x14015c, this), this[_0x2569b6(0x2aa)](!(0x1bba + 0x132e + -0x4c * 0x9e)); } function _0x2edbf4(_0xe32b16) { var _0x3a0ecf = _0x44a0fc; if (_0xe32b16) { var _0x3f503a = _0xe32b16[_0x5acd1a]; if (_0x3f503a) return _0x3f503a['call'](_0xe32b16); if (_0x3a0ecf(0x1ee) == typeof _0xe32b16[_0x3a0ecf(0x389)]) return _0xe32b16; if (!isNaN(_0xe32b16['length'])) { var _0x1ff1cf = -(-0x254c + 0xc7 * 0x11 + -0x2 * -0xc0b) , _0x4f1000 = function _0x136bfc() { var _0x10b933 = _0x3a0ecf; for (; ++_0x1ff1cf < _0xe32b16[_0x10b933(0x259)];) if (_0x43238c[_0x10b933(0x334)](_0xe32b16, _0x1ff1cf)) return _0x136bfc[_0x10b933(0x2e4)] = _0xe32b16[_0x1ff1cf], _0x136bfc[_0x10b933(0x1e3)] = !(-0x1f79 + -0x22d2 + 0x424c), _0x136bfc; return _0x136bfc['value'] = void (-0x639 + -0x115f + 0xa * 0x25c), _0x136bfc[_0x10b933(0x1e3)] = !(0x14ad + -0x1 * 0xa1b + -0xa92), _0x136bfc; }; return _0x4f1000[_0x3a0ecf(0x389)] = _0x4f1000; } } return { 'next': _0x159200 }; } function _0x159200() { return { 'value': void (-0x540 + -0x12 * -0x60 + -0x8 * 0x30), 'done': !(-0x526 + -0x6d4 * 0x2 + -0x2 * -0x967) }; } return _0x2e67e9[_0x44a0fc(0x344)] = _0x3a5ac7, _0x4cf7d8(_0x42763b, _0x44a0fc(0x2ac), { 'value': _0x3a5ac7, 'configurable': !(-0x2414 + 0x1307 + 0x110d) }), _0x4cf7d8(_0x3a5ac7, _0x44a0fc(0x2ac), { 'value': _0x2e67e9, 'configurable': !(0x117d + -0x12aa + 0x12d) }), _0x2e67e9[_0x44a0fc(0x18c)] = _0x3baf2c(_0x3a5ac7, _0x447ab4, _0x44a0fc(0x311)), _0x507b23[_0x44a0fc(0x386)] = function (_0x54fdc6) { var _0x2c4038 = _0x44a0fc , _0x29797b = _0x2c4038(0x1ee) == typeof _0x54fdc6 && _0x54fdc6[_0x2c4038(0x2ac)]; return !!_0x29797b && (_0x29797b === _0x2e67e9 || _0x2c4038(0x311) === (_0x29797b['displayName'] || _0x29797b['name'])); } , _0x507b23[_0x44a0fc(0x3a5)] = function (_0x41ff3c) { var _0x286ce7 = _0x44a0fc; return Object[_0x286ce7(0x23b)] ? Object[_0x286ce7(0x23b)](_0x41ff3c, _0x3a5ac7) : (_0x41ff3c[_0x286ce7(0x2ea)] = _0x3a5ac7, _0x3baf2c(_0x41ff3c, _0x447ab4, 'GeneratorFunction')), _0x41ff3c[_0x286ce7(0x344)] = Object['create'](_0x42763b), _0x41ff3c; } , _0x507b23[_0x44a0fc(0x2d1)] = function (_0x4d3173) { return { '__await': _0x4d3173 }; } , _0x4e156b(_0x23eb73[_0x44a0fc(0x344)]), _0x3baf2c(_0x23eb73[_0x44a0fc(0x344)], _0x52f292, function () { return this; }), _0x507b23[_0x44a0fc(0x273)] = _0x23eb73, _0x507b23[_0x44a0fc(0x1e7)] = function (_0x1403cf, _0xb864ce, _0x191461, _0x3aafb7, _0x4bc5ca) { var _0x225b19 = _0x44a0fc; void (-0x1 * 0x1145 + -0x239 + -0x3e6 * -0x5) === _0x4bc5ca && (_0x4bc5ca = Promise); var _0x1e23c5 = new _0x23eb73(_0x3e46c6(_0x1403cf, _0xb864ce, _0x191461, _0x3aafb7), _0x4bc5ca); return _0x507b23[_0x225b19(0x386)](_0xb864ce) ? _0x1e23c5 : _0x1e23c5[_0x225b19(0x389)]()[_0x225b19(0x1ed)](function (_0x3bf8a0) { var _0x1af03c = _0x225b19; return _0x3bf8a0[_0x1af03c(0x1e3)] ? _0x3bf8a0[_0x1af03c(0x2e4)] : _0x1e23c5[_0x1af03c(0x389)](); }); } , _0x4e156b(_0x42763b), _0x3baf2c(_0x42763b, _0x447ab4, _0x44a0fc(0x1b5)), _0x3baf2c(_0x42763b, _0x5acd1a, function () { return this; }), _0x3baf2c(_0x42763b, _0x44a0fc(0x3ae), function () { var _0x26f06c = _0x44a0fc; return _0x26f06c(0x176); }), _0x507b23['keys'] = function (_0x10e233) { var _0x515044 = _0x44a0fc , _0x184dfc = Object(_0x10e233) , _0x1ee5ea = []; for (var _0x103175 in _0x184dfc) _0x1ee5ea[_0x515044(0x36e)](_0x103175); return _0x1ee5ea[_0x515044(0x2f3)](), function _0x5c4b0b() { var _0x4f9a2b = _0x515044; for (; _0x1ee5ea[_0x4f9a2b(0x259)];) { var _0x180e64 = _0x1ee5ea[_0x4f9a2b(0x267)](); if (_0x180e64 in _0x184dfc) return _0x5c4b0b[_0x4f9a2b(0x2e4)] = _0x180e64, _0x5c4b0b[_0x4f9a2b(0x1e3)] = !(0x411 * -0x2 + -0x20 * 0x3c + 0x1 * 0xfa3), _0x5c4b0b; } return _0x5c4b0b['done'] = !(-0x4d2 * -0x5 + -0x3fd + -0x13 * 0x10f), _0x5c4b0b; } ; } , _0x507b23[_0x44a0fc(0x38b)] = _0x2edbf4, _0x1b5db9['prototype'] = { 'constructor': _0x1b5db9, 'reset': function (_0x3ebc58) { var _0x52c025 = _0x44a0fc; if (this[_0x52c025(0x32c)] = -0x16 * 0x181 + 0x1e6a + 0x2ac, this[_0x52c025(0x389)] = 0x1 * 0xa3f + -0x1 * 0x1fb7 + 0x1578, this[_0x52c025(0x3b2)] = this[_0x52c025(0x2a2)] = void (0x18ca + -0x52c * 0x2 + -0xe72), this[_0x52c025(0x1e3)] = !(-0x775 + -0xfe1 + -0x1757 * -0x1), this[_0x52c025(0x1bc)] = null, this[_0x52c025(0x25a)] = _0x52c025(0x389), this[_0x52c025(0x327)] = void (-0x1a0a + -0x263c + -0x2 * -0x2023), this[_0x52c025(0x337)][_0x52c025(0x254)](_0x2f82b3), !_0x3ebc58) { for (var _0x271134 in this) 't' === _0x271134['charAt'](0x3 * 0x707 + 0x2132 + 0x7 * -0x7c1) && _0x43238c[_0x52c025(0x334)](this, _0x271134) && !isNaN(+_0x271134['slice'](0x2629 * 0x1 + -0x7ec + -0x1e3c)) && (this[_0x271134] = void (0x14a3 + 0x211 + -0x16b4)); } }, 'stop': function () { var _0x5d1331 = _0x44a0fc; this[_0x5d1331(0x1e3)] = !(-0x1157 + -0x1d00 * -0x1 + -0xba9); var _0x360ca1 = this[_0x5d1331(0x337)][0x181c + 0x2252 + 0x137a * -0x3][_0x5d1331(0x387)]; if (_0x5d1331(0x250) === _0x360ca1[_0x5d1331(0x1ab)]) throw _0x360ca1['arg']; return this[_0x5d1331(0x29a)]; }, 'dispatchException': function (_0x5ccdbe) { var _0x2739ee = _0x44a0fc; if (this[_0x2739ee(0x1e3)]) throw _0x5ccdbe; var _0x2375b1 = this; function _0x5bc3f7(_0x2f8813, _0x2926a6) { var _0x19cf66 = _0x2739ee; return _0x459dde[_0x19cf66(0x1ab)] = _0x19cf66(0x250), _0x459dde[_0x19cf66(0x327)] = _0x5ccdbe, _0x2375b1[_0x19cf66(0x389)] = _0x2f8813, _0x2926a6 && (_0x2375b1[_0x19cf66(0x25a)] = _0x19cf66(0x389), _0x2375b1[_0x19cf66(0x327)] = void (-0x77f + -0x1c03 * -0x1 + -0xd * 0x194)), !!_0x2926a6; } for (var _0x218123 = this[_0x2739ee(0x337)][_0x2739ee(0x259)] - (-0x8ed + -0x9e5 + 0x12d3); _0x218123 >= -0x3 * -0x4ef + -0x161b * -0x1 + -0x24e8; --_0x218123) { var _0x5e4d00 = this[_0x2739ee(0x337)][_0x218123] , _0x459dde = _0x5e4d00[_0x2739ee(0x387)]; if (_0x2739ee(0x1c3) === _0x5e4d00['tryLoc']) return _0x5bc3f7(_0x2739ee(0x1db)); if (_0x5e4d00[_0x2739ee(0x29e)] <= this[_0x2739ee(0x32c)]) { var _0x55fd7f = _0x43238c['call'](_0x5e4d00, 'catchLoc') , _0x47cf6c = _0x43238c[_0x2739ee(0x334)](_0x5e4d00, _0x2739ee(0x220)); if (_0x55fd7f && _0x47cf6c) { if (this[_0x2739ee(0x32c)] < _0x5e4d00[_0x2739ee(0x2c4)]) return _0x5bc3f7(_0x5e4d00['catchLoc'], !(-0x25 * 0xe9 + -0x2368 + 0x4515)); if (this['prev'] < _0x5e4d00[_0x2739ee(0x220)]) return _0x5bc3f7(_0x5e4d00[_0x2739ee(0x220)]); } else { if (_0x55fd7f) { if (this['prev'] < _0x5e4d00[_0x2739ee(0x2c4)]) return _0x5bc3f7(_0x5e4d00[_0x2739ee(0x2c4)], !(0xf11 + -0x1032 + -0x121 * -0x1)); } else { if (!_0x47cf6c) throw new Error(_0x2739ee(0x33e)); if (this[_0x2739ee(0x32c)] < _0x5e4d00['finallyLoc']) return _0x5bc3f7(_0x5e4d00['finallyLoc']); } } } } }, 'abrupt': function (_0x452b06, _0x46591d) { var _0x189350 = _0x44a0fc; for (var _0x24d9ee = this[_0x189350(0x337)][_0x189350(0x259)] - (-0x1 * -0x434 + 0xb2d * -0x1 + 0x6fa); _0x24d9ee >= 0x144c + -0x1 * 0x80f + -0xc3d; --_0x24d9ee) { var _0x3e29ca = this[_0x189350(0x337)][_0x24d9ee]; if (_0x3e29ca[_0x189350(0x29e)] <= this[_0x189350(0x32c)] && _0x43238c[_0x189350(0x334)](_0x3e29ca, _0x189350(0x220)) && this[_0x189350(0x32c)] < _0x3e29ca[_0x189350(0x220)]) { var _0x35a2dc = _0x3e29ca; break; } } _0x35a2dc && (_0x189350(0x31f) === _0x452b06 || _0x189350(0x2d2) === _0x452b06) && _0x35a2dc[_0x189350(0x29e)] <= _0x46591d && _0x46591d <= _0x35a2dc[_0x189350(0x220)] && (_0x35a2dc = null); var _0x1837c0 = _0x35a2dc ? _0x35a2dc[_0x189350(0x387)] : {}; return _0x1837c0['type'] = _0x452b06, _0x1837c0[_0x189350(0x327)] = _0x46591d, _0x35a2dc ? (this[_0x189350(0x25a)] = _0x189350(0x389), this[_0x189350(0x389)] = _0x35a2dc[_0x189350(0x220)], _0x511b05) : this[_0x189350(0x162)](_0x1837c0); }, 'complete': function (_0x4daef1, _0x152d13) { var _0x2e0c88 = _0x44a0fc; if (_0x2e0c88(0x250) === _0x4daef1[_0x2e0c88(0x1ab)]) throw _0x4daef1[_0x2e0c88(0x327)]; return _0x2e0c88(0x31f) === _0x4daef1[_0x2e0c88(0x1ab)] || _0x2e0c88(0x2d2) === _0x4daef1[_0x2e0c88(0x1ab)] ? this[_0x2e0c88(0x389)] = _0x4daef1[_0x2e0c88(0x327)] : _0x2e0c88(0x2fd) === _0x4daef1['type'] ? (this[_0x2e0c88(0x29a)] = this[_0x2e0c88(0x327)] = _0x4daef1[_0x2e0c88(0x327)], this[_0x2e0c88(0x25a)] = _0x2e0c88(0x2fd), this[_0x2e0c88(0x389)] = 'end') : _0x2e0c88(0x2f1) === _0x4daef1[_0x2e0c88(0x1ab)] && _0x152d13 && (this[_0x2e0c88(0x389)] = _0x152d13), _0x511b05; }, 'finish': function (_0x33d396) { var _0x38fc51 = _0x44a0fc; for (var _0x5abbd8 = this[_0x38fc51(0x337)][_0x38fc51(0x259)] - (0x1b6c + 0x10f1 + -0x2c5c); _0x5abbd8 >= -0x106c + -0x26aa + 0x3716; --_0x5abbd8) { var _0x5eb5c4 = this[_0x38fc51(0x337)][_0x5abbd8]; if (_0x5eb5c4[_0x38fc51(0x220)] === _0x33d396) return this['complete'](_0x5eb5c4[_0x38fc51(0x387)], _0x5eb5c4[_0x38fc51(0x2fb)]), _0x2f82b3(_0x5eb5c4), _0x511b05; } }, 'catch': function (_0x235ccf) { var _0x4e3345 = _0x44a0fc; for (var _0x1db840 = this['tryEntries'][_0x4e3345(0x259)] - (-0x3a * -0x4f + -0x1 * -0x12d6 + -0x24bb * 0x1); _0x1db840 >= -0x168 + 0x305 + -0x19d; --_0x1db840) { var _0x545b66 = this['tryEntries'][_0x1db840]; if (_0x545b66[_0x4e3345(0x29e)] === _0x235ccf) { var _0x2d5b63 = _0x545b66[_0x4e3345(0x387)]; if (_0x4e3345(0x250) === _0x2d5b63[_0x4e3345(0x1ab)]) { var _0x13e035 = _0x2d5b63[_0x4e3345(0x327)]; _0x2f82b3(_0x545b66); } return _0x13e035; } } throw new Error(_0x4e3345(0x23f)); }, 'delegateYield': function (_0x395df9, _0x50d973, _0x48faf1) { var _0x39919e = _0x44a0fc; return this[_0x39919e(0x1bc)] = { 'iterator': _0x2edbf4(_0x395df9), 'resultName': _0x50d973, 'nextLoc': _0x48faf1 }, 'next' === this['method'] && (this['arg'] = void (0x1958 + -0xaba + -0xe9e)), _0x511b05; } }, _0x507b23; } function _0x1db123(_0x149c27) { var _0x39d672 = w_0x25f3; return (_0x1db123 = _0x39d672(0x1ee) == typeof Symbol && 'symbol' == typeof Symbol['iterator'] ? function (_0x4a8f2c) { return typeof _0x4a8f2c; } : function (_0x40eb64) { var _0x4f8eee = _0x39d672; return _0x40eb64 && _0x4f8eee(0x1ee) == typeof Symbol && _0x40eb64[_0x4f8eee(0x2ac)] === Symbol && _0x40eb64 !== Symbol['prototype'] ? _0x4f8eee(0x2ed) : typeof _0x40eb64; } )(_0x149c27); } function _0x34d29a() { var _0x4bbdba = w_0x25f3; _0x34d29a = function (_0x1859fe, _0x26adf2) { return new _0x3b92d4(_0x1859fe, void (-0x1f * 0x133 + 0x3 * 0x4f5 + 0x23b * 0xa), _0x26adf2); } ; var _0x5aed6e = RegExp[_0x4bbdba(0x344)] , _0x58ba91 = new WeakMap(); function _0x3b92d4(_0x2f61fd, _0x513c44, _0xaf22fc) { var _0xd932c6 = _0x4bbdba , _0xd60509 = new RegExp(_0x2f61fd, _0x513c44); return _0x58ba91[_0xd932c6(0x1e4)](_0xd60509, _0xaf22fc || _0x58ba91[_0xd932c6(0x32b)](_0x2f61fd)), _0x2e2f47(_0xd60509, _0x3b92d4[_0xd932c6(0x344)]); } function _0x427d42(_0x489eeb, _0x1699d8) { var _0x1004fb = _0x4bbdba , _0x2ae826 = _0x58ba91[_0x1004fb(0x32b)](_0x1699d8); return Object[_0x1004fb(0x17f)](_0x2ae826)[_0x1004fb(0x376)](function (_0x100f10, _0x5bfdf5) { var _0x3ad421 = _0x1004fb , _0x541568 = _0x2ae826[_0x5bfdf5]; if (_0x3ad421(0x28b) == typeof _0x541568) _0x100f10[_0x5bfdf5] = _0x489eeb[_0x541568]; else { for (var _0x3bc728 = 0x1 * 0x3ce + 0x3 * -0x435 + 0x8d1; void (0x2695 + 0x1ee8 + -0x457d) === _0x489eeb[_0x541568[_0x3bc728]] && _0x3bc728 + (0x3fb * -0x2 + 0x2631 + -0x2 * 0xf1d) < _0x541568['length'];) _0x3bc728++; _0x100f10[_0x5bfdf5] = _0x489eeb[_0x541568[_0x3bc728]]; } return _0x100f10; }, Object[_0x1004fb(0x3b7)](null)); } return _0x5dda7d(_0x3b92d4, RegExp), _0x3b92d4['prototype']['exec'] = function (_0x1bc796) { var _0x2a6afd = _0x4bbdba , _0x457628 = _0x5aed6e[_0x2a6afd(0x209)]['call'](this, _0x1bc796); if (_0x457628) { _0x457628['groups'] = _0x427d42(_0x457628, this); var _0x546542 = _0x457628['indices']; _0x546542 && (_0x546542['groups'] = _0x427d42(_0x546542, this)); } return _0x457628; } , _0x3b92d4[_0x4bbdba(0x344)][Symbol[_0x4bbdba(0x377)]] = function (_0x10e2ef, _0x453a26) { var _0x226516 = _0x4bbdba; if (_0x226516(0x33c) == typeof _0x453a26) { var _0x42705a = _0x58ba91[_0x226516(0x32b)](this); return _0x5aed6e[Symbol[_0x226516(0x377)]]['call'](this, _0x10e2ef, _0x453a26[_0x226516(0x377)](/\$<([^>]+)>/g, function (_0x4536fa, _0x53b1c5) { var _0x5db08d = _0x226516 , _0x1b4f5c = _0x42705a[_0x53b1c5]; return '$' + (Array[_0x5db08d(0x2af)](_0x1b4f5c) ? _0x1b4f5c[_0x5db08d(0x24f)]('$') : _0x1b4f5c); })); } if (_0x226516(0x1ee) == typeof _0x453a26) { var _0x51880e = this; return _0x5aed6e[Symbol[_0x226516(0x377)]][_0x226516(0x334)](this, _0x10e2ef, function () { var _0x3ce548 = _0x226516 , _0x352e21 = arguments; return _0x3ce548(0x17d) != typeof _0x352e21[_0x352e21[_0x3ce548(0x259)] - (-0xf9 * -0x7 + 0xd4 + -0x2 * 0x3d1)] && (_0x352e21 = []['slice'][_0x3ce548(0x334)](_0x352e21))[_0x3ce548(0x36e)](_0x427d42(_0x352e21, _0x51880e)), _0x453a26[_0x3ce548(0x207)](this, _0x352e21); }); } return _0x5aed6e[Symbol[_0x226516(0x377)]]['call'](this, _0x10e2ef, _0x453a26); } , _0x34d29a[_0x4bbdba(0x207)](this, arguments); } function _0x2772ab(_0x17b93c) { var _0x2e8af4 = w_0x25f3; this[_0x2e8af4(0x180)] = _0x17b93c; } function _0x125e01(_0x221f1a) { return function () { var _0x2db496 = w_0x25f3; return new _0x137ba2(_0x221f1a[_0x2db496(0x207)](this, arguments)); } ; } function _0x8a0370(_0x1ac7e2, _0x440b38, _0x4611ac, _0x1f79f3, _0x26eb95, _0x4af1d4, _0x2b24bc) { var _0x3a480e = w_0x25f3; try { var _0x16d0ce = _0x1ac7e2[_0x4af1d4](_0x2b24bc) , _0x2e8eaf = _0x16d0ce[_0x3a480e(0x2e4)]; } catch (_0xc88fae) { return void _0x4611ac(_0xc88fae); } _0x16d0ce[_0x3a480e(0x1e3)] ? _0x440b38(_0x2e8eaf) : Promise[_0x3a480e(0x278)](_0x2e8eaf)[_0x3a480e(0x1ed)](_0x1f79f3, _0x26eb95); } function _0x50daaa(_0x10de0d) { return function () { var _0x310206 = this , _0x17ad73 = arguments; return new Promise(function (_0x37120b, _0x728e37) { var _0x5a819e = _0x10de0d['apply'](_0x310206, _0x17ad73); function _0x12466a(_0x263e68) { var _0x123e7f = w_0x25f3; _0x8a0370(_0x5a819e, _0x37120b, _0x728e37, _0x12466a, _0x191f8b, _0x123e7f(0x389), _0x263e68); } function _0x191f8b(_0x255ae1) { var _0x5d15ce = w_0x25f3; _0x8a0370(_0x5a819e, _0x37120b, _0x728e37, _0x12466a, _0x191f8b, _0x5d15ce(0x250), _0x255ae1); } _0x12466a(void (0xbdf + -0x129c + -0x19 * -0x45)); } ); } ; } function _0x297d3d(_0xd86983, _0x45590a) { var _0x51c357 = w_0x25f3; if (!(_0xd86983 instanceof _0x45590a)) throw new TypeError(_0x51c357(0x2f7)); } function _0x96f358(_0x217e4a, _0x156410) { var _0x251198 = w_0x25f3; for (var _0x336960 = 0x1613 + -0x2f * 0x3b + 0xb3e * -0x1; _0x336960 < _0x156410[_0x251198(0x259)]; _0x336960++) { var _0x3d27f0 = _0x156410[_0x336960]; _0x3d27f0[_0x251198(0x23a)] = _0x3d27f0[_0x251198(0x23a)] || !(-0xbf7 + -0x166e + 0x2266), _0x3d27f0[_0x251198(0x2bc)] = !(-0xde + 0x1c94 + -0x1bb6), _0x251198(0x2e4) in _0x3d27f0 && (_0x3d27f0['writable'] = !(-0x21 * 0x12f + -0x2 * -0x17d + 0x2415)), Object[_0x251198(0x175)](_0x217e4a, _0x32e885(_0x3d27f0[_0x251198(0x392)]), _0x3d27f0); } } function _0x55cd8a(_0x174a54, _0x5e25eb, _0x465e50) { var _0x4d17e4 = w_0x25f3; return _0x5e25eb && _0x96f358(_0x174a54[_0x4d17e4(0x344)], _0x5e25eb), _0x465e50 && _0x96f358(_0x174a54, _0x465e50), Object[_0x4d17e4(0x175)](_0x174a54, _0x4d17e4(0x344), { 'writable': !(-0xc19 + 0x1aa + -0x2 * -0x538) }), _0x174a54; } function _0x58d8c2(_0x5634d7, _0x53515c) { var _0x5fc7bf = w_0x25f3; for (var _0x5e7563 in _0x53515c) { (_0x532356 = _0x53515c[_0x5e7563])[_0x5fc7bf(0x2bc)] = _0x532356[_0x5fc7bf(0x23a)] = !(0xb5 + 0x6 * 0x80 + -0x3b5), 'value' in _0x532356 && (_0x532356[_0x5fc7bf(0x2e3)] = !(0x19d * 0x11 + 0x126f + -0x2ddc)), Object[_0x5fc7bf(0x175)](_0x5634d7, _0x5e7563, _0x532356); } if (Object[_0x5fc7bf(0x388)]) for (var _0x45bcc1 = Object[_0x5fc7bf(0x388)](_0x53515c), _0x368698 = -0x11 * -0x1af + -0x1ec7 + 0x228; _0x368698 < _0x45bcc1[_0x5fc7bf(0x259)]; _0x368698++) { var _0x532356, _0x6be33e = _0x45bcc1[_0x368698]; (_0x532356 = _0x53515c[_0x6be33e])['configurable'] = _0x532356[_0x5fc7bf(0x23a)] = !(-0x833 + 0x9 * -0x76 + 0x1d * 0x6d), _0x5fc7bf(0x2e4) in _0x532356 && (_0x532356[_0x5fc7bf(0x2e3)] = !(0x18bf + -0x337 * 0x5 + -0x14 * 0x6f)), Object[_0x5fc7bf(0x175)](_0x5634d7, _0x6be33e, _0x532356); } return _0x5634d7; } function _0x40ff51(_0x254aaa, _0x287254) { var _0x77273f = w_0x25f3; for (var _0x587d0b = Object[_0x77273f(0x32a)](_0x287254), _0xa5f40d = -0x2174 + 0x25a * -0x2 + 0x42 * 0x94; _0xa5f40d < _0x587d0b[_0x77273f(0x259)]; _0xa5f40d++) { var _0x23b426 = _0x587d0b[_0xa5f40d] , _0x59ae3e = Object[_0x77273f(0x2a6)](_0x287254, _0x23b426); _0x59ae3e && _0x59ae3e[_0x77273f(0x2bc)] && void (-0x221b * 0x1 + 0x2326 * 0x1 + -0x10b) === _0x254aaa[_0x23b426] && Object['defineProperty'](_0x254aaa, _0x23b426, _0x59ae3e); } return _0x254aaa; } function _0x4a7824(_0x26784f, _0x47795a, _0x26a673) { var _0x1edba1 = w_0x25f3; return (_0x47795a = _0x32e885(_0x47795a)) in _0x26784f ? Object[_0x1edba1(0x175)](_0x26784f, _0x47795a, { 'value': _0x26a673, 'enumerable': !(0x24eb + 0x1b2 + -0x1 * 0x269d), 'configurable': !(-0x472 + -0xb7 * 0x12 + 0x1150), 'writable': !(-0x23f8 + 0x257d + -0x185 * 0x1) }) : _0x26784f[_0x47795a] = _0x26a673, _0x26784f; } function _0x36f54f() { var _0x472dbc = w_0x25f3; return (_0x36f54f = Object['assign'] ? Object[_0x472dbc(0x2a0)]['bind']() : function (_0x3e0b52) { var _0x1eddf8 = _0x472dbc; for (var _0x16d75e = -0x645 + -0x2115 * 0x1 + -0x19 * -0x193; _0x16d75e < arguments[_0x1eddf8(0x259)]; _0x16d75e++) { var _0x5a9173 = arguments[_0x16d75e]; for (var _0x145ff6 in _0x5a9173) Object[_0x1eddf8(0x344)][_0x1eddf8(0x3b8)][_0x1eddf8(0x334)](_0x5a9173, _0x145ff6) && (_0x3e0b52[_0x145ff6] = _0x5a9173[_0x145ff6]); } return _0x3e0b52; } )['apply'](this, arguments); } function _0x5f346f(_0x2ab8ef) { var _0x39ff04 = w_0x25f3; for (var _0x176101 = 0xc60 + -0x43 * -0x1f + 0x6 * -0x36a; _0x176101 < arguments[_0x39ff04(0x259)]; _0x176101++) { var _0x99688c = null != arguments[_0x176101] ? Object(arguments[_0x176101]) : {} , _0xbf3314 = Object[_0x39ff04(0x17f)](_0x99688c); _0x39ff04(0x1ee) == typeof Object[_0x39ff04(0x388)] && _0xbf3314[_0x39ff04(0x36e)][_0x39ff04(0x207)](_0xbf3314, Object[_0x39ff04(0x388)](_0x99688c)[_0x39ff04(0x164)](function (_0x2adc39) { var _0x23e48e = _0x39ff04; return Object[_0x23e48e(0x2a6)](_0x99688c, _0x2adc39)[_0x23e48e(0x23a)]; })), _0xbf3314['forEach'](function (_0x2e44e4) { _0x4a7824(_0x2ab8ef, _0x2e44e4, _0x99688c[_0x2e44e4]); }); } return _0x2ab8ef; } function _0x5dda7d(_0x46427b, _0x4b68de) { var _0x5c7f41 = w_0x25f3; if ('function' != typeof _0x4b68de && null !== _0x4b68de) throw new TypeError(_0x5c7f41(0x236)); _0x46427b[_0x5c7f41(0x344)] = Object['create'](_0x4b68de && _0x4b68de[_0x5c7f41(0x344)], { 'constructor': { 'value': _0x46427b, 'writable': !(-0x59 * -0x6d + -0x3a5 + -0x2240), 'configurable': !(0xf4d * -0x2 + -0x182e + 0x1b64 * 0x2) } }), Object[_0x5c7f41(0x175)](_0x46427b, _0x5c7f41(0x344), { 'writable': !(-0x9d0 * -0x1 + 0x17a1 + -0x217 * 0x10) }), _0x4b68de && _0x2e2f47(_0x46427b, _0x4b68de); } function _0x3879ac(_0x40738f, _0x3ddb1f) { var _0x34aaee = w_0x25f3; _0x40738f[_0x34aaee(0x344)] = Object['create'](_0x3ddb1f[_0x34aaee(0x344)]), _0x40738f[_0x34aaee(0x344)][_0x34aaee(0x2ac)] = _0x40738f, _0x2e2f47(_0x40738f, _0x3ddb1f); } function _0x22af63(_0x26c4e4) { var _0x3c9cd2 = w_0x25f3; return (_0x22af63 = Object[_0x3c9cd2(0x23b)] ? Object['getPrototypeOf'][_0x3c9cd2(0x301)]() : function (_0x212968) { var _0x3a939f = _0x3c9cd2; return _0x212968['__proto__'] || Object[_0x3a939f(0x22c)](_0x212968); } )(_0x26c4e4); } function _0x2e2f47(_0x24c757, _0x1d9d80) { var _0x5e1251 = w_0x25f3; return (_0x2e2f47 = Object['setPrototypeOf'] ? Object[_0x5e1251(0x23b)][_0x5e1251(0x301)]() : function (_0x1f13b4, _0x24fdc8) { var _0x16bc1b = _0x5e1251; return _0x1f13b4[_0x16bc1b(0x2ea)] = _0x24fdc8, _0x1f13b4; } )(_0x24c757, _0x1d9d80); } function _0x3390dc() { var _0x31b3f3 = w_0x25f3; if (_0x31b3f3(0x384) == typeof Reflect || !Reflect[_0x31b3f3(0x320)]) return !(-0x1158 + -0x789 + 0x18e2); if (Reflect[_0x31b3f3(0x320)]['sham']) return !(0xec5 + 0x8 * -0x92 + -0xa34 * 0x1); if (_0x31b3f3(0x1ee) == typeof Proxy) return !(-0xe01 + 0xa7b + 0x386); try { return Boolean[_0x31b3f3(0x344)][_0x31b3f3(0x303)][_0x31b3f3(0x334)](Reflect[_0x31b3f3(0x320)](Boolean, [], function () { })), !(-0xe0c + -0x2415 + 0x3221 * 0x1); } catch (_0x20aa1e) { return !(0xd * -0xa4 + -0xc1f + 0x1474); } } function _0x920a3d(_0x35e7ab, _0x5cbd00, _0x53ced5) { var _0x4ba719 = w_0x25f3; return (_0x920a3d = _0x3390dc() ? Reflect['construct'][_0x4ba719(0x301)]() : function (_0x40c7ae, _0xd26edf, _0x5ecdaf) { var _0x3b4c0f = _0x4ba719 , _0x55cb1f = [null]; _0x55cb1f['push'][_0x3b4c0f(0x207)](_0x55cb1f, _0xd26edf); var _0x47f6fb = new (Function[_0x3b4c0f(0x301)]['apply'](_0x40c7ae, _0x55cb1f))(); return _0x5ecdaf && _0x2e2f47(_0x47f6fb, _0x5ecdaf[_0x3b4c0f(0x344)]), _0x47f6fb; } )['apply'](null, arguments); } function _0x5cb0d7(_0x6e2f2f) { var _0x26e451 = w_0x25f3; return -(-0x65 * 0x35 + -0x2087 + -0x3571 * -0x1) !== Function[_0x26e451(0x3ae)]['call'](_0x6e2f2f)[_0x26e451(0x2c5)](_0x26e451(0x161)); } function _0x16f283(_0x11646e) { var _0x9334eb = w_0x25f3 , _0x551d10 = _0x9334eb(0x1ee) == typeof Map ? new Map() : void (-0xe * -0x5 + 0xd * -0x151 + 0x3 * 0x59d); return (_0x16f283 = function (_0xffb48b) { var _0x30c56b = _0x9334eb; if (null === _0xffb48b || !_0x5cb0d7(_0xffb48b)) return _0xffb48b; if (_0x30c56b(0x1ee) != typeof _0xffb48b) throw new TypeError(_0x30c56b(0x236)); if (void (0x12 * 0xf8 + -0x41b + 0xd55 * -0x1) !== _0x551d10) { if (_0x551d10[_0x30c56b(0x2fc)](_0xffb48b)) return _0x551d10[_0x30c56b(0x32b)](_0xffb48b); _0x551d10[_0x30c56b(0x1e4)](_0xffb48b, _0xfee014); } function _0xfee014() { var _0x10fec4 = _0x30c56b; return _0x920a3d(_0xffb48b, arguments, _0x22af63(this)[_0x10fec4(0x2ac)]); } return _0xfee014['prototype'] = Object[_0x30c56b(0x3b7)](_0xffb48b[_0x30c56b(0x344)], { 'constructor': { 'value': _0xfee014, 'enumerable': !(0x829 + 0x1 * -0x1271 + 0xa49), 'writable': !(0x724 + 0x15f9 + 0x1d * -0x101), 'configurable': !(0x3e4 + 0x685 * 0x4 + 0x1df8 * -0x1) } }), _0x2e2f47(_0xfee014, _0xffb48b); } )(_0x11646e); } function _0x8f6e33(_0xca4201, _0x572889) { var _0x44fa42 = w_0x25f3; return null != _0x572889 && _0x44fa42(0x384) != typeof Symbol && _0x572889[Symbol[_0x44fa42(0x178)]] ? !!_0x572889[Symbol[_0x44fa42(0x178)]](_0xca4201) : _0xca4201 instanceof _0x572889; } function _0x2ea366(_0x4da731) { var _0x2c9369 = w_0x25f3; return _0x4da731 && _0x4da731[_0x2c9369(0x3bd)] ? _0x4da731 : { 'default': _0x4da731 }; } function _0x1629e6(_0xb8ec58) { var _0xd6f6f1 = w_0x25f3; if (_0xd6f6f1(0x1ee) != typeof WeakMap) return null; var _0x2d8f39 = new WeakMap() , _0x2398fe = new WeakMap(); return (_0x1629e6 = function (_0x164b35) { return _0x164b35 ? _0x2398fe : _0x2d8f39; } )(_0xb8ec58); } function _0x2c9158(_0x2f5c3e, _0x55c9ff) { var _0x3d4018 = w_0x25f3; if (!_0x55c9ff && _0x2f5c3e && _0x2f5c3e['__esModule']) return _0x2f5c3e; if (null === _0x2f5c3e || _0x3d4018(0x17d) != typeof _0x2f5c3e && 'function' != typeof _0x2f5c3e) return { 'default': _0x2f5c3e }; var _0xcafe97 = _0x1629e6(_0x55c9ff); if (_0xcafe97 && _0xcafe97[_0x3d4018(0x2fc)](_0x2f5c3e)) return _0xcafe97[_0x3d4018(0x32b)](_0x2f5c3e); var _0x41e330 = {} , _0x115c7f = Object[_0x3d4018(0x175)] && Object[_0x3d4018(0x2a6)]; for (var _0x84a510 in _0x2f5c3e) if (_0x3d4018(0x1d3) !== _0x84a510 && Object[_0x3d4018(0x344)]['hasOwnProperty'][_0x3d4018(0x334)](_0x2f5c3e, _0x84a510)) { var _0x422bcd = _0x115c7f ? Object[_0x3d4018(0x2a6)](_0x2f5c3e, _0x84a510) : null; _0x422bcd && (_0x422bcd[_0x3d4018(0x32b)] || _0x422bcd[_0x3d4018(0x1e4)]) ? Object['defineProperty'](_0x41e330, _0x84a510, _0x422bcd) : _0x41e330[_0x84a510] = _0x2f5c3e[_0x84a510]; } return _0x41e330['default'] = _0x2f5c3e, _0xcafe97 && _0xcafe97[_0x3d4018(0x1e4)](_0x2f5c3e, _0x41e330), _0x41e330; } function _0x374736(_0x2d130b, _0x324593) { var _0x4ccff2 = w_0x25f3; if (_0x2d130b !== _0x324593) throw new TypeError(_0x4ccff2(0x16c)); } function _0x206199(_0x607e62) { var _0x5bbb21 = w_0x25f3; if (null == _0x607e62) throw new TypeError(_0x5bbb21(0x1cc) + _0x607e62); } function _0x2bfa3e(_0x161463, _0x2ff4d5) { var _0x59d7ee = w_0x25f3; if (null == _0x161463) return {}; var _0x31fbf6, _0x50d0b6, _0x5ca1c5 = {}, _0x5b71fd = Object['keys'](_0x161463); for (_0x50d0b6 = -0x763 + -0x15a + 0x1 * 0x8bd; _0x50d0b6 < _0x5b71fd[_0x59d7ee(0x259)]; _0x50d0b6++) _0x31fbf6 = _0x5b71fd[_0x50d0b6], _0x2ff4d5[_0x59d7ee(0x2c5)](_0x31fbf6) >= 0x8a7 + -0x21ed + 0x1946 || (_0x5ca1c5[_0x31fbf6] = _0x161463[_0x31fbf6]); return _0x5ca1c5; } function _0x2a28e9(_0x2cdf2e, _0x74ac11) { var _0x942c51 = w_0x25f3; if (null == _0x2cdf2e) return {}; var _0x5ef46e, _0x5acf70, _0x282df4 = _0x2bfa3e(_0x2cdf2e, _0x74ac11); if (Object['getOwnPropertySymbols']) { var _0x33d708 = Object[_0x942c51(0x388)](_0x2cdf2e); for (_0x5acf70 = -0xd30 + 0x4a1 * 0x1 + 0x7 * 0x139; _0x5acf70 < _0x33d708[_0x942c51(0x259)]; _0x5acf70++) _0x5ef46e = _0x33d708[_0x5acf70], _0x74ac11[_0x942c51(0x2c5)](_0x5ef46e) >= -0x1f5d * 0x1 + 0x40b + 0x1b52 || Object[_0x942c51(0x344)][_0x942c51(0x1da)][_0x942c51(0x334)](_0x2cdf2e, _0x5ef46e) && (_0x282df4[_0x5ef46e] = _0x2cdf2e[_0x5ef46e]); } return _0x282df4; } function _0x58f550(_0x94e938) { var _0x41d076 = w_0x25f3; if (void (-0x6a * 0x1 + -0xf6 + 0x160) === _0x94e938) throw new ReferenceError(_0x41d076(0x1bb)); return _0x94e938; } function _0xf3b17(_0x7b2cf6, _0x347c9f) { var _0x41f350 = w_0x25f3; if (_0x347c9f && (_0x41f350(0x17d) == typeof _0x347c9f || _0x41f350(0x1ee) == typeof _0x347c9f)) return _0x347c9f; if (void (0x160d + -0x24fd + -0x8 * -0x1de) !== _0x347c9f) throw new TypeError('Derived\x20constructors\x20may\x20only\x20return\x20object\x20or\x20undefined'); return _0x58f550(_0x7b2cf6); } function _0x17b234(_0x33316f) { var _0x3601f0 = _0x3390dc(); return function () { var _0x4733d6 = w_0x25f3, _0x2848c1, _0x2d0386 = _0x22af63(_0x33316f); if (_0x3601f0) { var _0x252bb3 = _0x22af63(this)[_0x4733d6(0x2ac)]; _0x2848c1 = Reflect[_0x4733d6(0x320)](_0x2d0386, arguments, _0x252bb3); } else _0x2848c1 = _0x2d0386[_0x4733d6(0x207)](this, arguments); return _0xf3b17(this, _0x2848c1); } ; } function _0x2b3a8b(_0x409fbb, _0x508825) { for (; !Object['prototype']['hasOwnProperty']['call'](_0x409fbb, _0x508825) && null !== (_0x409fbb = _0x22af63(_0x409fbb));) ; return _0x409fbb; } function _0x33bfa4() { var _0x1bbf02 = w_0x25f3; return (_0x33bfa4 = _0x1bbf02(0x384) != typeof Reflect && Reflect[_0x1bbf02(0x32b)] ? Reflect[_0x1bbf02(0x32b)][_0x1bbf02(0x301)]() : function (_0x45f38c, _0x4a526c, _0x482f60) { var _0xb2ad90 = _0x1bbf02 , _0x53f5d7 = _0x2b3a8b(_0x45f38c, _0x4a526c); if (_0x53f5d7) { var _0x24e05f = Object[_0xb2ad90(0x2a6)](_0x53f5d7, _0x4a526c); return _0x24e05f[_0xb2ad90(0x32b)] ? _0x24e05f[_0xb2ad90(0x32b)][_0xb2ad90(0x334)](arguments[_0xb2ad90(0x259)] < 0x7f * 0x25 + 0x23f3 + -0x364b ? _0x45f38c : _0x482f60) : _0x24e05f[_0xb2ad90(0x2e4)]; } } )['apply'](this, arguments); } function _0x5b010c(_0x878ee0, _0x27a5c4, _0x35cb40, _0x14e9ba) { var _0x1c5f60 = w_0x25f3; return (_0x5b010c = 'undefined' != typeof Reflect && Reflect[_0x1c5f60(0x1e4)] ? Reflect[_0x1c5f60(0x1e4)] : function (_0xc1d4d, _0x3a32f8, _0x25436d, _0x5ef6e3) { var _0x43a591 = _0x1c5f60, _0x23d91e, _0x5ac459 = _0x2b3a8b(_0xc1d4d, _0x3a32f8); if (_0x5ac459) { if ((_0x23d91e = Object[_0x43a591(0x2a6)](_0x5ac459, _0x3a32f8))[_0x43a591(0x1e4)]) return _0x23d91e['set'][_0x43a591(0x334)](_0x5ef6e3, _0x25436d), !(0x4d * -0x59 + -0x1 * -0x3d6 + 0x16ef); if (!_0x23d91e[_0x43a591(0x2e3)]) return !(-0x2691 + 0x9e * 0x25 + 0xfbc); } if (_0x23d91e = Object['getOwnPropertyDescriptor'](_0x5ef6e3, _0x3a32f8)) { if (!_0x23d91e[_0x43a591(0x2e3)]) return !(0x14d + -0x1 * -0x29b + -0x3e7); _0x23d91e[_0x43a591(0x2e4)] = _0x25436d, Object[_0x43a591(0x175)](_0x5ef6e3, _0x3a32f8, _0x23d91e); } else _0x4a7824(_0x5ef6e3, _0x3a32f8, _0x25436d); return !(0x295 + -0xa39 * -0x2 + -0x1707); } )(_0x878ee0, _0x27a5c4, _0x35cb40, _0x14e9ba); } function _0x2732a4(_0x24a602, _0x194547, _0x16a7b5, _0x8913b1, _0x531d0d) { var _0x16563f = w_0x25f3; if (!_0x5b010c(_0x24a602, _0x194547, _0x16a7b5, _0x8913b1 || _0x24a602) && _0x531d0d) throw new TypeError(_0x16563f(0x34e)); return _0x16a7b5; } function _0x281bf3(_0x4564b4, _0x1f4153) { var _0x10b792 = w_0x25f3; return _0x1f4153 || (_0x1f4153 = _0x4564b4[_0x10b792(0x2a5)](0x1017 + 0x1cb3 + 0x2 * -0x1665)), Object[_0x10b792(0x198)](Object[_0x10b792(0x36c)](_0x4564b4, { 'raw': { 'value': Object[_0x10b792(0x198)](_0x1f4153) } })); } function _0x2d5f0c(_0x18863e, _0x13e8d4) { var _0x47a3d1 = w_0x25f3; return _0x13e8d4 || (_0x13e8d4 = _0x18863e['slice'](0x8f1 * 0x1 + 0x1 * 0x1e1f + -0x7d * 0x50)), _0x18863e[_0x47a3d1(0x2ab)] = _0x13e8d4, _0x18863e; } function _0x246229(_0x5e71a0) { var _0x47c977 = w_0x25f3; throw new TypeError('\x22' + _0x5e71a0 + _0x47c977(0x338)); } function _0x5db7bf(_0x51f915) { throw new TypeError('\x22' + _0x51f915 + '\x22\x20is\x20write-only'); } function _0x2275f4(_0x42246e) { var _0x2f7351 = w_0x25f3; throw new ReferenceError(_0x2f7351(0x1c7) + _0x42246e + '\x22\x20cannot\x20be\x20referenced\x20in\x20computed\x20property\x20keys.'); } function _0x516c2b() { } function _0xab4ac9(_0xcde017) { throw new ReferenceError(_0xcde017 + '\x20is\x20not\x20defined\x20-\x20temporal\x20dead\x20zone'); } function _0x5b377b(_0x5f4fa5, _0x2f492b) { return _0x5f4fa5 === _0x516c2b ? _0xab4ac9(_0x2f492b) : _0x5f4fa5; } function _0x5096de(_0x3b53c6, _0x3225c3) { return _0x1ae312(_0x3b53c6) || _0x19d66a(_0x3b53c6, _0x3225c3) || _0x525331(_0x3b53c6, _0x3225c3) || _0x25b697(); } function _0x1dff6c(_0x3a7a63, _0x147620) { return _0x1ae312(_0x3a7a63) || _0x376fd5(_0x3a7a63, _0x147620) || _0x525331(_0x3a7a63, _0x147620) || _0x25b697(); } function _0x5c885(_0x3953c0) { return _0x1ae312(_0x3953c0) || _0x1853c6(_0x3953c0) || _0x525331(_0x3953c0) || _0x25b697(); } function _0x534083(_0x3396cf) { return _0x1ccd19(_0x3396cf) || _0x1853c6(_0x3396cf) || _0x525331(_0x3396cf) || _0x542337(); } function _0x1ccd19(_0x15e4f0) { var _0x384cbb = w_0x25f3; if (Array[_0x384cbb(0x2af)](_0x15e4f0)) return _0x537c83(_0x15e4f0); } function _0x1ae312(_0x4bfad0) { var _0x3bc5ad = w_0x25f3; if (Array[_0x3bc5ad(0x2af)](_0x4bfad0)) return _0x4bfad0; } function _0x4bbf4b(_0x33a831, _0x181a3c, _0x135270) { var _0x5bf329 = w_0x25f3; if (_0x181a3c && !Array[_0x5bf329(0x2af)](_0x181a3c) && _0x5bf329(0x28b) == typeof _0x181a3c[_0x5bf329(0x259)]) { var _0x43388b = _0x181a3c[_0x5bf329(0x259)]; return _0x537c83(_0x181a3c, void (0xb * -0x29 + 0x387 + -0x2 * 0xe2) !== _0x135270 && _0x135270 < _0x43388b ? _0x135270 : _0x43388b); } return _0x33a831(_0x181a3c, _0x135270); } function _0x1853c6(_0x3086ca) { var _0x3e6030 = w_0x25f3; if (_0x3e6030(0x384) != typeof Symbol && null != _0x3086ca[Symbol[_0x3e6030(0x3b3)]] || null != _0x3086ca[_0x3e6030(0x1dd)]) return Array['from'](_0x3086ca); } function _0x525331(_0x1ceb3f, _0x38a668) { var _0x2b5250 = w_0x25f3; if (_0x1ceb3f) { if (_0x2b5250(0x33c) == typeof _0x1ceb3f) return _0x537c83(_0x1ceb3f, _0x38a668); var _0x2821d9 = Object[_0x2b5250(0x344)][_0x2b5250(0x3ae)][_0x2b5250(0x334)](_0x1ceb3f)['slice'](0x2ae * -0x7 + 0x1 * 0x2181 + 0xeb7 * -0x1, -(-0x1d * -0xec + 0x2 * -0x32 + -0x1a57)); return _0x2b5250(0x2eb) === _0x2821d9 && _0x1ceb3f[_0x2b5250(0x2ac)] && (_0x2821d9 = _0x1ceb3f[_0x2b5250(0x2ac)]['name']), 'Map' === _0x2821d9 || _0x2b5250(0x160) === _0x2821d9 ? Array[_0x2b5250(0x29c)](_0x1ceb3f) : _0x2b5250(0x27f) === _0x2821d9 || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/[_0x2b5250(0x22b)](_0x2821d9) ? _0x537c83(_0x1ceb3f, _0x38a668) : void (-0x1 * 0xf34 + 0x12e6 + 0x1d9 * -0x2); } } function _0x537c83(_0x1cc46a, _0xca9940) { var _0x4c66e3 = w_0x25f3; (null == _0xca9940 || _0xca9940 > _0x1cc46a[_0x4c66e3(0x259)]) && (_0xca9940 = _0x1cc46a[_0x4c66e3(0x259)]); for (var _0x3859e4 = 0x1fe3 * 0x1 + -0x22be + 0x2db, _0x3a8ed3 = new Array(_0xca9940); _0x3859e4 < _0xca9940; _0x3859e4++) _0x3a8ed3[_0x3859e4] = _0x1cc46a[_0x3859e4]; return _0x3a8ed3; } function _0x542337() { var _0x1d894c = w_0x25f3; throw new TypeError(_0x1d894c(0x3a8)); } function _0x25b697() { throw new TypeError('Invalid\x20attempt\x20to\x20destructure\x20non-iterable\x20instance.\x0aIn\x20order\x20to\x20be\x20iterable,\x20non-array\x20objects\x20must\x20have\x20a\x20[Symbol.iterator]()\x20method.'); } function _0x350075(_0x189dc6, _0x3f6be9) { var _0x464771 = w_0x25f3 , _0x199147 = _0x464771(0x384) != typeof Symbol && _0x189dc6[Symbol[_0x464771(0x3b3)]] || _0x189dc6[_0x464771(0x1dd)]; if (!_0x199147) { if (Array['isArray'](_0x189dc6) || (_0x199147 = _0x525331(_0x189dc6)) || _0x3f6be9 && _0x189dc6 && _0x464771(0x28b) == typeof _0x189dc6[_0x464771(0x259)]) { _0x199147 && (_0x189dc6 = _0x199147); var _0x544a3f = 0x1fd2 + 0x1 * -0x1ad5 + 0x4fd * -0x1 , _0x5b20ae = function () { }; return { 's': _0x5b20ae, 'n': function () { var _0x4c93f0 = _0x464771; return _0x544a3f >= _0x189dc6[_0x4c93f0(0x259)] ? { 'done': !(-0x1aaa * 0x1 + -0x126e * 0x1 + 0x2d18) } : { 'done': !(0x7 * -0x205 + 0x1d44 + -0xf20), 'value': _0x189dc6[_0x544a3f++] }; }, 'e': function (_0x4d8393) { throw _0x4d8393; }, 'f': _0x5b20ae }; } throw new TypeError(_0x464771(0x2ae)); } var _0x23cdd3, _0x5bcfbb = !(0x1 * -0x810 + -0xb3 * 0x2f + 0x1 * 0x28ed), _0x4db4fd = !(0x221 + 0x62e * 0x5 + 0x3 * -0xb02); return { 's': function () { _0x199147 = _0x199147['call'](_0x189dc6); }, 'n': function () { var _0x30f507 = _0x464771 , _0x2382b6 = _0x199147['next'](); return _0x5bcfbb = _0x2382b6[_0x30f507(0x1e3)], _0x2382b6; }, 'e': function (_0x3b0f24) { _0x4db4fd = !(0x18e0 + 0x2 * 0x214 + -0x1d08), _0x23cdd3 = _0x3b0f24; }, 'f': function () { var _0x22fdad = _0x464771; try { _0x5bcfbb || null == _0x199147[_0x22fdad(0x2fd)] || _0x199147[_0x22fdad(0x2fd)](); } finally { if (_0x4db4fd) throw _0x23cdd3; } } }; } function _0x37af30(_0x37ea9e, _0x2388a0) { var _0x3701de = w_0x25f3 , _0x2ac940 = 'undefined' != typeof Symbol && _0x37ea9e[Symbol[_0x3701de(0x3b3)]] || _0x37ea9e['@@iterator']; if (_0x2ac940) return (_0x2ac940 = _0x2ac940['call'](_0x37ea9e))[_0x3701de(0x389)][_0x3701de(0x301)](_0x2ac940); if (Array[_0x3701de(0x2af)](_0x37ea9e) || (_0x2ac940 = _0x525331(_0x37ea9e)) || _0x2388a0 && _0x37ea9e && 'number' == typeof _0x37ea9e[_0x3701de(0x259)]) { _0x2ac940 && (_0x37ea9e = _0x2ac940); var _0x1dcac8 = 0x1d * -0x11a + -0x1f * 0xc7 + -0x380b * -0x1; return function () { var _0x306ed0 = _0x3701de; return _0x1dcac8 >= _0x37ea9e[_0x306ed0(0x259)] ? { 'done': !(-0x29d + 0x5fb * -0x1 + 0x898) } : { 'done': !(-0x6e * 0x4f + 0x2311 + -0x11e), 'value': _0x37ea9e[_0x1dcac8++] }; } ; } throw new TypeError(_0x3701de(0x2ae)); } function _0x5362e1(_0x4cd55f) { return function () { var _0x58617f = w_0x25f3 , _0x183c06 = _0x4cd55f['apply'](this, arguments); return _0x183c06[_0x58617f(0x389)](), _0x183c06; } ; } function _0x4ad3b0(_0x460146, _0x32cf07) { var _0x271683 = w_0x25f3; if (_0x271683(0x17d) != typeof _0x460146 || null === _0x460146) return _0x460146; var _0xa9fa9c = _0x460146[Symbol['toPrimitive']]; if (void (0x21b4 + 0x5 * 0x4c7 + 0x3997 * -0x1) !== _0xa9fa9c) { var _0x560849 = _0xa9fa9c['call'](_0x460146, _0x32cf07 || _0x271683(0x1d3)); if ('object' != typeof _0x560849) return _0x560849; throw new TypeError(_0x271683(0x358)); } return ('string' === _0x32cf07 ? String : Number)(_0x460146); } function _0x32e885(_0x225644) { var _0x1eb479 = w_0x25f3 , _0x45c5d4 = _0x4ad3b0(_0x225644, _0x1eb479(0x33c)); return _0x1eb479(0x2ed) == typeof _0x45c5d4 ? _0x45c5d4 : String(_0x45c5d4); } function _0xf47dc3(_0x1d62a3, _0xeb8470) { var _0x31107e = w_0x25f3; throw new Error(_0x31107e(0x1ff)); } function _0x9d8dd5(_0x238b59, _0x206a18, _0x4378c6, _0x532e73) { var _0x340b07 = w_0x25f3; _0x4378c6 && Object[_0x340b07(0x175)](_0x238b59, _0x206a18, { 'enumerable': _0x4378c6['enumerable'], 'configurable': _0x4378c6[_0x340b07(0x2bc)], 'writable': _0x4378c6[_0x340b07(0x2e3)], 'value': _0x4378c6[_0x340b07(0x2d0)] ? _0x4378c6['initializer'][_0x340b07(0x334)](_0x532e73) : void (-0x1ae6 + -0x1 * -0xf6b + 0xb7b * 0x1) }); } function _0x271739(_0xb876c5, _0x285720, _0x361de9, _0x52c079, _0x38a76d) { var _0x26b61e = w_0x25f3 , _0x25c883 = {}; return Object['keys'](_0x52c079)[_0x26b61e(0x254)](function (_0xd1a198) { _0x25c883[_0xd1a198] = _0x52c079[_0xd1a198]; }), _0x25c883[_0x26b61e(0x23a)] = !!_0x25c883[_0x26b61e(0x23a)], _0x25c883[_0x26b61e(0x2bc)] = !!_0x25c883[_0x26b61e(0x2bc)], (_0x26b61e(0x2e4) in _0x25c883 || _0x25c883['initializer']) && (_0x25c883[_0x26b61e(0x2e3)] = !(0x21e8 + -0x7cf * -0x5 + -0x48f3)), _0x25c883 = _0x361de9[_0x26b61e(0x2a5)]()[_0x26b61e(0x2f3)]()[_0x26b61e(0x376)](function (_0x490c4a, _0x3636f6) { return _0x3636f6(_0xb876c5, _0x285720, _0x490c4a) || _0x490c4a; }, _0x25c883), _0x38a76d && void (0x1050 + 0x1 * 0x121f + -0x226f) !== _0x25c883[_0x26b61e(0x2d0)] && (_0x25c883[_0x26b61e(0x2e4)] = _0x25c883[_0x26b61e(0x2d0)] ? _0x25c883[_0x26b61e(0x2d0)][_0x26b61e(0x334)](_0x38a76d) : void (0x1392 + -0xbe * 0x23 + -0x1 * -0x668), _0x25c883[_0x26b61e(0x2d0)] = void (-0x1 * -0x1b9a + 0x7f7 + -0x2391 * 0x1)), void (0x53 * 0xb + 0xa2 * -0xf + 0x5ed) === _0x25c883[_0x26b61e(0x2d0)] && (Object[_0x26b61e(0x175)](_0xb876c5, _0x285720, _0x25c883), _0x25c883 = null), _0x25c883; } _0x137ba2['prototype'][_0x5612de(0x1ee) == typeof Symbol && Symbol[_0x5612de(0x17c)] || _0x5612de(0x230)] = function () { return this; } , _0x137ba2[_0x5612de(0x344)]['next'] = function (_0x4262ae) { var _0x1543b4 = _0x5612de; return this[_0x1543b4(0x1b1)](_0x1543b4(0x389), _0x4262ae); } , _0x137ba2[_0x5612de(0x344)]['throw'] = function (_0x29bafe) { var _0x4c6950 = _0x5612de; return this[_0x4c6950(0x1b1)]('throw', _0x29bafe); } , _0x137ba2[_0x5612de(0x344)][_0x5612de(0x2fd)] = function (_0x49a53d) { var _0x58f790 = _0x5612de; return this[_0x58f790(0x1b1)](_0x58f790(0x2fd), _0x49a53d); } ; var _0x371360 = 0x11 * -0x20 + 0x88 * -0x1f + 0x1298, _0x4d6c78, _0x312e19, _0x2a32a1, _0x371ac2; function _0x2ca065(_0x4c2f2d) { var _0x4415ed = _0x5612de; return _0x4415ed(0x365) + _0x371360++ + '_' + _0x4c2f2d; } function _0x33f649(_0x59611c, _0xb8c4c1) { var _0x22f43f = _0x5612de; if (!Object['prototype'][_0x22f43f(0x3b8)][_0x22f43f(0x334)](_0x59611c, _0xb8c4c1)) throw new TypeError(_0x22f43f(0x271)); return _0x59611c; } function _0x4c53b8(_0x467264, _0x1c39b3) { var _0x12fb5e = _0x5612de; return _0x1de0ff(_0x467264, _0x3feef3(_0x467264, _0x1c39b3, _0x12fb5e(0x32b))); } function _0xf01d58(_0x248ad, _0xd514dd, _0x4ffcbe) { var _0x2a0763 = _0x5612de; return _0x30760d(_0x248ad, _0x3feef3(_0x248ad, _0xd514dd, _0x2a0763(0x1e4)), _0x4ffcbe), _0x4ffcbe; } function _0x3ff428(_0x59a12f, _0x2e6e68) { var _0x1a4f4c = _0x5612de; return _0x1af7ca(_0x59a12f, _0x3feef3(_0x59a12f, _0x2e6e68, _0x1a4f4c(0x1e4))); } function _0x3feef3(_0x76bca3, _0x49ed2b, _0xf40042) { var _0x26b65c = _0x5612de; if (!_0x49ed2b[_0x26b65c(0x2fc)](_0x76bca3)) throw new TypeError(_0x26b65c(0x2ad) + _0xf40042 + _0x26b65c(0x2bd)); return _0x49ed2b[_0x26b65c(0x32b)](_0x76bca3); } function _0x46f318(_0x2345ad, _0x29e732, _0x5d3874) { var _0x1785a7 = _0x5612de; return _0x3fbf28(_0x2345ad, _0x29e732), _0x234ff5(_0x5d3874, _0x1785a7(0x32b)), _0x1de0ff(_0x2345ad, _0x5d3874); } function _0x3d490a(_0x421876, _0x556ee1, _0x489ccf, _0x32c792) { return _0x3fbf28(_0x421876, _0x556ee1), _0x234ff5(_0x489ccf, 'set'), _0x30760d(_0x421876, _0x489ccf, _0x32c792), _0x32c792; } function _0x1f2159(_0x3c139e, _0xf1d93, _0xa6753b) { return _0x3fbf28(_0x3c139e, _0xf1d93), _0xa6753b; } function _0x2fcc7b() { var _0x52da1e = _0x5612de; throw new TypeError(_0x52da1e(0x1bd)); } function _0x1de0ff(_0x35f1cc, _0x2cd6a2) { var _0x331169 = _0x5612de; return _0x2cd6a2[_0x331169(0x32b)] ? _0x2cd6a2[_0x331169(0x32b)][_0x331169(0x334)](_0x35f1cc) : _0x2cd6a2[_0x331169(0x2e4)]; } function _0x30760d(_0x26c16c, _0x3bf250, _0x4bb532) { var _0x4b5758 = _0x5612de; if (_0x3bf250['set']) _0x3bf250['set'][_0x4b5758(0x334)](_0x26c16c, _0x4bb532); else { if (!_0x3bf250['writable']) throw new TypeError('attempted\x20to\x20set\x20read\x20only\x20private\x20field'); _0x3bf250['value'] = _0x4bb532; } } function _0x1af7ca(_0x38569e, _0x4d9da9) { var _0x8504a5 = _0x5612de; if (_0x4d9da9[_0x8504a5(0x1e4)]) return _0x8504a5(0x36f) in _0x4d9da9 || (_0x4d9da9['__destrObj'] = { set 'value'(_0xa3d23) { var _0x3d920b = _0x8504a5; _0x4d9da9[_0x3d920b(0x1e4)][_0x3d920b(0x334)](_0x38569e, _0xa3d23); } }), _0x4d9da9['__destrObj']; if (!_0x4d9da9[_0x8504a5(0x2e3)]) throw new TypeError('attempted\x20to\x20set\x20read\x20only\x20private\x20field'); return _0x4d9da9; } function _0x153ad5(_0x361a17, _0x4bf341, _0x38ef34) { var _0x36fea3 = _0x5612de; return _0x3fbf28(_0x361a17, _0x4bf341), _0x234ff5(_0x38ef34, _0x36fea3(0x1e4)), _0x1af7ca(_0x361a17, _0x38ef34); } function _0x3fbf28(_0x1609bf, _0x372dbd) { if (_0x1609bf !== _0x372dbd) throw new TypeError('Private\x20static\x20access\x20of\x20wrong\x20provenance'); } function _0x234ff5(_0x5dcf40, _0xa0e3be) { var _0x4d5828 = _0x5612de; if (void (0x9 * 0x3b9 + -0x7 * -0x2bb + -0x382 * 0xf) === _0x5dcf40) throw new TypeError(_0x4d5828(0x2ad) + _0xa0e3be + '\x20private\x20static\x20field\x20before\x20its\x20declaration'); } function _0x3cd893(_0x4d4ac2, _0x117b1a, _0x13ebad, _0x227002) { var _0x404d8d = _0x5612de , _0x21250e = _0xdaf8f6(); if (_0x227002) { for (var _0x1ad96b = -0x1 * -0x109d + 0x157 + -0x11f4; _0x1ad96b < _0x227002[_0x404d8d(0x259)]; _0x1ad96b++) _0x21250e = _0x227002[_0x1ad96b](_0x21250e); } var _0x176094 = _0x117b1a(function (_0x32bc32) { var _0x1f970f = _0x404d8d; _0x21250e[_0x1f970f(0x313)](_0x32bc32, _0x1fa626[_0x1f970f(0x265)]); }, _0x13ebad) , _0x1fa626 = _0x21250e[_0x404d8d(0x2df)](_0x872852(_0x176094['d'][_0x404d8d(0x1cb)](_0x210308)), _0x4d4ac2); return _0x21250e[_0x404d8d(0x217)](_0x176094['F'], _0x1fa626[_0x404d8d(0x265)]), _0x21250e[_0x404d8d(0x165)](_0x176094['F'], _0x1fa626[_0x404d8d(0x39c)]); } function _0xdaf8f6() { var _0x3fa41d = _0x5612de; _0xdaf8f6 = function () { return _0x513b0e; } ; var _0x513b0e = { 'elementsDefinitionOrder': [[_0x3fa41d(0x25a)], [_0x3fa41d(0x23c)]], 'initializeInstanceElements': function (_0x179aa6, _0x25d5cd) { var _0x557071 = _0x3fa41d; [_0x557071(0x25a), _0x557071(0x23c)][_0x557071(0x254)](function (_0x2476e8) { var _0x9b5c0b = _0x557071; _0x25d5cd[_0x9b5c0b(0x254)](function (_0x299a4a) { var _0x53da95 = _0x9b5c0b; _0x299a4a['kind'] === _0x2476e8 && _0x53da95(0x235) === _0x299a4a[_0x53da95(0x215)] && this[_0x53da95(0x2e1)](_0x179aa6, _0x299a4a); }, this); }, this); }, 'initializeClassElements': function (_0x300a4c, _0x38031f) { var _0x450502 = _0x3fa41d , _0x4e1581 = _0x300a4c[_0x450502(0x344)]; [_0x450502(0x25a), _0x450502(0x23c)]['forEach'](function (_0x2fde74) { _0x38031f['forEach'](function (_0xe5e082) { var _0x123341 = w_0x25f3 , _0x174ec4 = _0xe5e082[_0x123341(0x215)]; if (_0xe5e082[_0x123341(0x26f)] === _0x2fde74 && (_0x123341(0x396) === _0x174ec4 || _0x123341(0x344) === _0x174ec4)) { var _0x184ef0 = _0x123341(0x396) === _0x174ec4 ? _0x300a4c : _0x4e1581; this[_0x123341(0x2e1)](_0x184ef0, _0xe5e082); } }, this); }, this); }, 'defineClassElement': function (_0x2e2a2c, _0x5db810) { var _0x522104 = _0x3fa41d , _0x2d7321 = _0x5db810['descriptor']; if (_0x522104(0x23c) === _0x5db810[_0x522104(0x26f)]) { var _0x554ae6 = _0x5db810[_0x522104(0x2d0)]; _0x2d7321 = { 'enumerable': _0x2d7321[_0x522104(0x23a)], 'writable': _0x2d7321[_0x522104(0x2e3)], 'configurable': _0x2d7321[_0x522104(0x2bc)], 'value': void (-0x1 * 0x3bd + 0x1ef9 + -0x1b3c) === _0x554ae6 ? void (0x1 * 0x1afe + 0xcd0 + 0x7f6 * -0x5) : _0x554ae6[_0x522104(0x334)](_0x2e2a2c) }; } Object[_0x522104(0x175)](_0x2e2a2c, _0x5db810['key'], _0x2d7321); }, 'decorateClass': function (_0x4d5101, _0x1a7961) { var _0x129760 = _0x3fa41d , _0x1a31cc = [] , _0x3f7e89 = [] , _0xf8b039 = { 'static': [], 'prototype': [], 'own': [] }; if (_0x4d5101[_0x129760(0x254)](function (_0x5c0ba5) { var _0x27d627 = _0x129760; this[_0x27d627(0x33d)](_0x5c0ba5, _0xf8b039); }, this), _0x4d5101[_0x129760(0x254)](function (_0x239102) { var _0x151241 = _0x129760; if (!_0x51f3b5(_0x239102)) return _0x1a31cc[_0x151241(0x36e)](_0x239102); var _0x43ec40 = this[_0x151241(0x1ac)](_0x239102, _0xf8b039); _0x1a31cc[_0x151241(0x36e)](_0x43ec40[_0x151241(0x206)]), _0x1a31cc[_0x151241(0x36e)]['apply'](_0x1a31cc, _0x43ec40[_0x151241(0x3bc)]), _0x3f7e89[_0x151241(0x36e)]['apply'](_0x3f7e89, _0x43ec40[_0x151241(0x39c)]); }, this), !_0x1a7961) return { 'elements': _0x1a31cc, 'finishers': _0x3f7e89 }; var _0x857545 = this[_0x129760(0x34c)](_0x1a31cc, _0x1a7961); return _0x3f7e89['push'][_0x129760(0x207)](_0x3f7e89, _0x857545[_0x129760(0x39c)]), _0x857545[_0x129760(0x39c)] = _0x3f7e89, _0x857545; }, 'addElementPlacement': function (_0x9cedb8, _0x4c2171, _0xa6e6c8) { var _0x5d3aa1 = _0x3fa41d , _0x33e5e0 = _0x4c2171[_0x9cedb8[_0x5d3aa1(0x215)]]; if (!_0xa6e6c8 && -(-0x1a83 + -0x1 * -0x16ee + 0x99 * 0x6) !== _0x33e5e0['indexOf'](_0x9cedb8['key'])) throw new TypeError('Duplicated\x20element\x20(' + _0x9cedb8[_0x5d3aa1(0x392)] + ')'); _0x33e5e0[_0x5d3aa1(0x36e)](_0x9cedb8[_0x5d3aa1(0x392)]); }, 'decorateElement': function (_0x463258, _0x11d852) { var _0x495609 = _0x3fa41d; for (var _0x4c5aa5 = [], _0x46b095 = [], _0x106243 = _0x463258['decorators'], _0x195d25 = _0x106243[_0x495609(0x259)] - (-0x1d5c * -0x1 + 0x1aa9 + 0x956 * -0x6); _0x195d25 >= 0x18ee + 0x1 * 0x1c19 + 0xf * -0x389; _0x195d25--) { var _0x425891 = _0x11d852[_0x463258[_0x495609(0x215)]]; _0x425891[_0x495609(0x2ec)](_0x425891[_0x495609(0x2c5)](_0x463258[_0x495609(0x392)]), 0x1 * 0xca7 + -0xaf * -0xb + -0x142b); var _0x30722d = this[_0x495609(0x372)](_0x463258) , _0x15a14c = this[_0x495609(0x18a)]((0x61 * 0x2d + 0x1545 + -0x1 * 0x2652, _0x106243[_0x195d25])(_0x30722d) || _0x30722d); _0x463258 = _0x15a14c['element'], this['addElementPlacement'](_0x463258, _0x11d852), _0x15a14c[_0x495609(0x2f4)] && _0x46b095[_0x495609(0x36e)](_0x15a14c[_0x495609(0x2f4)]); var _0x98e9dc = _0x15a14c[_0x495609(0x3bc)]; if (_0x98e9dc) { for (var _0x56dd4d = 0xb * 0x12e + -0x1f17 + -0x121d * -0x1; _0x56dd4d < _0x98e9dc[_0x495609(0x259)]; _0x56dd4d++) this[_0x495609(0x33d)](_0x98e9dc[_0x56dd4d], _0x11d852); _0x4c5aa5[_0x495609(0x36e)]['apply'](_0x4c5aa5, _0x98e9dc); } } return { 'element': _0x463258, 'finishers': _0x46b095, 'extras': _0x4c5aa5 }; }, 'decorateConstructor': function (_0x2a4d32, _0x2666ea) { var _0x5e156c = _0x3fa41d; for (var _0xb2f010 = [], _0x17b2e4 = _0x2666ea[_0x5e156c(0x259)] - (0x21c0 + -0xc8e * 0x2 + -0x8a3 * 0x1); _0x17b2e4 >= -0x1c70 + -0x1 * -0x1256 + -0x1af * -0x6; _0x17b2e4--) { var _0x222759 = this[_0x5e156c(0x193)](_0x2a4d32) , _0xc41f78 = this[_0x5e156c(0x324)]((0x1f46 + 0x81d * 0x2 + -0x5f * 0x80, _0x2666ea[_0x17b2e4])(_0x222759) || _0x222759); if (void (0x1373 + 0x2b1 * -0x2 + -0x1 * 0xe11) !== _0xc41f78[_0x5e156c(0x2f4)] && _0xb2f010[_0x5e156c(0x36e)](_0xc41f78[_0x5e156c(0x2f4)]), void (0x31 * -0xb5 + -0x11 * 0x67 + 0x84c * 0x5) !== _0xc41f78['elements']) { _0x2a4d32 = _0xc41f78[_0x5e156c(0x265)]; for (var _0x38c2da = -0x1 * -0xc12 + 0x7 * -0x28d + 0x5c9; _0x38c2da < _0x2a4d32[_0x5e156c(0x259)] - (0x59f * 0x6 + -0x1 * -0x1c6d + 0x25 * -0x1ae); _0x38c2da++) for (var _0x2950ed = _0x38c2da + (0x1b91 * 0x1 + -0xbc0 + -0x8 * 0x1fa); _0x2950ed < _0x2a4d32['length']; _0x2950ed++) if (_0x2a4d32[_0x38c2da][_0x5e156c(0x392)] === _0x2a4d32[_0x2950ed][_0x5e156c(0x392)] && _0x2a4d32[_0x38c2da][_0x5e156c(0x215)] === _0x2a4d32[_0x2950ed][_0x5e156c(0x215)]) throw new TypeError(_0x5e156c(0x367) + _0x2a4d32[_0x38c2da][_0x5e156c(0x392)] + ')'); } } return { 'elements': _0x2a4d32, 'finishers': _0xb2f010 }; }, 'fromElementDescriptor': function (_0x4a43c8) { var _0x302152 = _0x3fa41d , _0x406d6b = { 'kind': _0x4a43c8[_0x302152(0x26f)], 'key': _0x4a43c8['key'], 'placement': _0x4a43c8['placement'], 'descriptor': _0x4a43c8[_0x302152(0x20f)] }; return Object[_0x302152(0x175)](_0x406d6b, Symbol[_0x302152(0x1c0)], { 'value': _0x302152(0x1e8), 'configurable': !(-0x1b3a + 0x21bb * 0x1 + -0x14d * 0x5) }), _0x302152(0x23c) === _0x4a43c8['kind'] && (_0x406d6b[_0x302152(0x2d0)] = _0x4a43c8['initializer']), _0x406d6b; }, 'toElementDescriptors': function (_0x14be66) { if (void (0x1f48 + -0x19fe + 0x2a5 * -0x2) !== _0x14be66) return _0x5c885(_0x14be66)['map'](function (_0x170c20) { var _0x5285ee = w_0x25f3 , _0xf0c7ce = this[_0x5285ee(0x25e)](_0x170c20); return this['disallowProperty'](_0x170c20, _0x5285ee(0x2f4), _0x5285ee(0x21f)), this[_0x5285ee(0x21a)](_0x170c20, _0x5285ee(0x3bc), _0x5285ee(0x21f)), _0xf0c7ce; }, this); }, 'toElementDescriptor': function (_0x4c8a60) { var _0x1fc4d7 = _0x3fa41d , _0x33cfb6 = String(_0x4c8a60[_0x1fc4d7(0x26f)]); if (_0x1fc4d7(0x25a) !== _0x33cfb6 && _0x1fc4d7(0x23c) !== _0x33cfb6) throw new TypeError('An\x20element\x20descriptor\x27s\x20.kind\x20property\x20must\x20be\x20either\x20\x22method\x22\x20or\x20\x22field\x22,\x20but\x20a\x20decorator\x20created\x20an\x20element\x20descriptor\x20with\x20.kind\x20\x22' + _0x33cfb6 + '\x22'); var _0xebb768 = _0x32e885(_0x4c8a60[_0x1fc4d7(0x392)]) , _0x52ab63 = String(_0x4c8a60[_0x1fc4d7(0x215)]); if (_0x1fc4d7(0x396) !== _0x52ab63 && _0x1fc4d7(0x344) !== _0x52ab63 && _0x1fc4d7(0x235) !== _0x52ab63) throw new TypeError(_0x1fc4d7(0x2b5) + _0x52ab63 + '\x22'); var _0x33ee46 = _0x4c8a60[_0x1fc4d7(0x20f)]; this[_0x1fc4d7(0x21a)](_0x4c8a60, 'elements', _0x1fc4d7(0x21f)); var _0x1ca161 = { 'kind': _0x33cfb6, 'key': _0xebb768, 'placement': _0x52ab63, 'descriptor': Object[_0x1fc4d7(0x2a0)]({}, _0x33ee46) }; return _0x1fc4d7(0x23c) !== _0x33cfb6 ? this[_0x1fc4d7(0x21a)](_0x4c8a60, _0x1fc4d7(0x2d0), _0x1fc4d7(0x28d)) : (this['disallowProperty'](_0x33ee46, _0x1fc4d7(0x32b), _0x1fc4d7(0x277)), this['disallowProperty'](_0x33ee46, 'set', _0x1fc4d7(0x277)), this['disallowProperty'](_0x33ee46, _0x1fc4d7(0x2e4), _0x1fc4d7(0x277)), _0x1ca161['initializer'] = _0x4c8a60[_0x1fc4d7(0x2d0)]), _0x1ca161; }, 'toElementFinisherExtras': function (_0x53b840) { var _0x1157c0 = _0x3fa41d; return { 'element': this[_0x1157c0(0x25e)](_0x53b840), 'finisher': _0x22df7b(_0x53b840, _0x1157c0(0x2f4)), 'extras': this[_0x1157c0(0x370)](_0x53b840['extras']) }; }, 'fromClassDescriptor': function (_0x30c308) { var _0x482b04 = _0x3fa41d , _0xf231c1 = { 'kind': _0x482b04(0x19c), 'elements': _0x30c308['map'](this[_0x482b04(0x372)], this) }; return Object[_0x482b04(0x175)](_0xf231c1, Symbol[_0x482b04(0x1c0)], { 'value': _0x482b04(0x1e8), 'configurable': !(0xf * -0xf1 + 0x1 * -0xcfe + -0x1b1d * -0x1) }), _0xf231c1; }, 'toClassDescriptor': function (_0x178ca0) { var _0x2520a1 = _0x3fa41d , _0x79f336 = String(_0x178ca0[_0x2520a1(0x26f)]); if ('class' !== _0x79f336) throw new TypeError('A\x20class\x20descriptor\x27s\x20.kind\x20property\x20must\x20be\x20\x22class\x22,\x20but\x20a\x20decorator\x20created\x20a\x20class\x20descriptor\x20with\x20.kind\x20\x22' + _0x79f336 + '\x22'); this['disallowProperty'](_0x178ca0, _0x2520a1(0x392), 'A\x20class\x20descriptor'), this[_0x2520a1(0x21a)](_0x178ca0, _0x2520a1(0x215), 'A\x20class\x20descriptor'), this[_0x2520a1(0x21a)](_0x178ca0, _0x2520a1(0x20f), _0x2520a1(0x1a1)), this['disallowProperty'](_0x178ca0, 'initializer', _0x2520a1(0x1a1)), this[_0x2520a1(0x21a)](_0x178ca0, _0x2520a1(0x3bc), 'A\x20class\x20descriptor'); var _0x154e32 = _0x22df7b(_0x178ca0, _0x2520a1(0x2f4)); return { 'elements': this['toElementDescriptors'](_0x178ca0['elements']), 'finisher': _0x154e32 }; }, 'runClassFinishers': function (_0x58401c, _0x3ff489) { var _0x4a8de1 = _0x3fa41d; for (var _0x5ec766 = 0x13b0 + -0xe05 + 0x1 * -0x5ab; _0x5ec766 < _0x3ff489[_0x4a8de1(0x259)]; _0x5ec766++) { var _0x176fd2 = (0x7d * -0x25 + 0x101 * 0x14 + 0x5 * -0x67, _0x3ff489[_0x5ec766])(_0x58401c); if (void (-0x1 * 0x200 + 0x1c4a + -0x1a4a) !== _0x176fd2) { if (_0x4a8de1(0x1ee) != typeof _0x176fd2) throw new TypeError('Finishers\x20must\x20return\x20a\x20constructor.'); _0x58401c = _0x176fd2; } } return _0x58401c; }, 'disallowProperty': function (_0x376492, _0x1998df, _0x46cda4) { var _0x5b24b6 = _0x3fa41d; if (void (0x2239 + -0xa67 + -0x17d2) !== _0x376492[_0x1998df]) throw new TypeError(_0x46cda4 + _0x5b24b6(0x232) + _0x1998df + _0x5b24b6(0x2a1)); } }; return _0x513b0e; } function _0x210308(_0x5bae0) { var _0x509268 = _0x5612de, _0x2061f6, _0x1daa96 = _0x32e885(_0x5bae0['key']); _0x509268(0x25a) === _0x5bae0[_0x509268(0x26f)] ? _0x2061f6 = { 'value': _0x5bae0[_0x509268(0x2e4)], 'writable': !(-0x286 + -0x3 * -0x83b + -0x162b), 'configurable': !(-0x1 * -0x13f9 + -0x2 * -0x1127 + -0x3647), 'enumerable': !(-0x210 * -0x1 + 0xf43 * -0x2 + 0x1c77) } : _0x509268(0x32b) === _0x5bae0[_0x509268(0x26f)] ? _0x2061f6 = { 'get': _0x5bae0['value'], 'configurable': !(-0x4 * -0x87 + 0x25b6 + -0x13e9 * 0x2), 'enumerable': !(0x4 * -0x105 + -0x8 * 0x3b + 0x5ed * 0x1) } : 'set' === _0x5bae0[_0x509268(0x26f)] ? _0x2061f6 = { 'set': _0x5bae0[_0x509268(0x2e4)], 'configurable': !(-0x1 * -0xd7c + 0x24e2 + -0x325e), 'enumerable': !(0x246b * -0x1 + -0xbdc + 0x67 * 0x78) } : _0x509268(0x23c) === _0x5bae0['kind'] && (_0x2061f6 = { 'configurable': !(-0x7 * 0x7c + 0x0 + -0x1 * -0x364), 'writable': !(0x10cc + 0x93a + -0x1a06), 'enumerable': !(-0x339 + -0xfd1 * -0x1 + -0xc98) }); var _0x12b911 = { 'kind': _0x509268(0x23c) === _0x5bae0[_0x509268(0x26f)] ? _0x509268(0x23c) : 'method', 'key': _0x1daa96, 'placement': _0x5bae0[_0x509268(0x396)] ? _0x509268(0x396) : 'field' === _0x5bae0[_0x509268(0x26f)] ? 'own' : _0x509268(0x344), 'descriptor': _0x2061f6 }; return _0x5bae0['decorators'] && (_0x12b911[_0x509268(0x1b7)] = _0x5bae0[_0x509268(0x1b7)]), _0x509268(0x23c) === _0x5bae0['kind'] && (_0x12b911['initializer'] = _0x5bae0[_0x509268(0x2e4)]), _0x12b911; } function _0x3b2c5d(_0x1d2cd7, _0x5043bf) { var _0x6af88f = _0x5612de; void (-0x1c1e + -0x19 * -0xd9 + -0x1 * -0x6ed) !== _0x1d2cd7[_0x6af88f(0x20f)][_0x6af88f(0x32b)] ? _0x5043bf['descriptor'][_0x6af88f(0x32b)] = _0x1d2cd7['descriptor'][_0x6af88f(0x32b)] : _0x5043bf['descriptor']['set'] = _0x1d2cd7[_0x6af88f(0x20f)][_0x6af88f(0x1e4)]; } function _0x872852(_0x11fd7b) { var _0x45b70e = _0x5612de; for (var _0x3d0947 = [], _0x1e20eb = function (_0x3c1862) { var _0x56210d = w_0x25f3; return _0x56210d(0x25a) === _0x3c1862[_0x56210d(0x26f)] && _0x3c1862[_0x56210d(0x392)] === _0x44502e['key'] && _0x3c1862[_0x56210d(0x215)] === _0x44502e[_0x56210d(0x215)]; }, _0x2773ae = -0xdd7 + 0x5 * 0x3f6 + 0x5f7 * -0x1; _0x2773ae < _0x11fd7b[_0x45b70e(0x259)]; _0x2773ae++) { var _0x152571, _0x44502e = _0x11fd7b[_0x2773ae]; if (_0x45b70e(0x25a) === _0x44502e[_0x45b70e(0x26f)] && (_0x152571 = _0x3d0947['find'](_0x1e20eb))) { if (_0x3f9244(_0x44502e[_0x45b70e(0x20f)]) || _0x3f9244(_0x152571[_0x45b70e(0x20f)])) { if (_0x51f3b5(_0x44502e) || _0x51f3b5(_0x152571)) throw new ReferenceError('Duplicated\x20methods\x20(' + _0x44502e[_0x45b70e(0x392)] + _0x45b70e(0x20b)); _0x152571[_0x45b70e(0x20f)] = _0x44502e[_0x45b70e(0x20f)]; } else { if (_0x51f3b5(_0x44502e)) { if (_0x51f3b5(_0x152571)) throw new ReferenceError('Decorators\x20can\x27t\x20be\x20placed\x20on\x20different\x20accessors\x20with\x20for\x20the\x20same\x20property\x20(' + _0x44502e['key'] + ').'); _0x152571[_0x45b70e(0x1b7)] = _0x44502e[_0x45b70e(0x1b7)]; } _0x3b2c5d(_0x44502e, _0x152571); } } else _0x3d0947[_0x45b70e(0x36e)](_0x44502e); } return _0x3d0947; } function _0x51f3b5(_0x3b7753) { var _0x46d225 = _0x5612de; return _0x3b7753[_0x46d225(0x1b7)] && _0x3b7753['decorators']['length']; } function _0x3f9244(_0x197382) { var _0x1ec41e = _0x5612de; return void (0xa8 + 0x12a * -0x4 + 0x400) !== _0x197382 && !(void (-0x2 * -0x955 + -0x965 + -0x945) === _0x197382[_0x1ec41e(0x2e4)] && void (-0x403 + 0xd * 0x1e2 + -0x1477) === _0x197382[_0x1ec41e(0x2e3)]); } function _0x22df7b(_0x1a9e0b, _0x4c3aa6) { var _0x2004a6 = _0x5612de , _0x42d7e1 = _0x1a9e0b[_0x4c3aa6]; if (void (-0x1ab * -0x11 + -0x2164 + 0x1 * 0x509) !== _0x42d7e1 && _0x2004a6(0x1ee) != typeof _0x42d7e1) throw new TypeError('Expected\x20\x27' + _0x4c3aa6 + _0x2004a6(0x200)); return _0x42d7e1; } function _0x44a779(_0x290bd9, _0x3c4184, _0x356c04) { var _0x5e1d0b = _0x5612de; if (!_0x3c4184[_0x5e1d0b(0x2fc)](_0x290bd9)) throw new TypeError(_0x5e1d0b(0x187)); return _0x356c04; } function _0x227cd9(_0x44aab6, _0x47792b) { var _0x50e6e8 = _0x5612de; if (_0x47792b[_0x50e6e8(0x2fc)](_0x44aab6)) throw new TypeError('Cannot\x20initialize\x20the\x20same\x20private\x20elements\x20twice\x20on\x20an\x20object'); } function _0x41d2eb(_0x2d54de, _0x30b42b, _0x3cc210) { _0x227cd9(_0x2d54de, _0x30b42b), _0x30b42b['set'](_0x2d54de, _0x3cc210); } function _0x4fd04a(_0x1b36b3, _0x1a6559) { var _0x1aaac6 = _0x5612de; _0x227cd9(_0x1b36b3, _0x1a6559), _0x1a6559[_0x1aaac6(0x332)](_0x1b36b3); } function _0x4fb313() { throw new TypeError('attempted\x20to\x20reassign\x20private\x20method'); } function _0x4e4f66(_0x9f7990) { return _0x9f7990; } _0x5612de(0x1ee) != typeof Object[_0x5612de(0x2a0)] && Object['defineProperty'](Object, _0x5612de(0x2a0), { 'value': function (_0x478fa2, _0x273756) { var _0x2b8676 = _0x5612de; if (null == _0x478fa2) throw new TypeError(_0x2b8676(0x2cf)); for (var _0x451cfb = Object(_0x478fa2), _0x5c5079 = -0xb5d + -0x15f0 + 0x214e; _0x5c5079 < arguments[_0x2b8676(0x259)]; _0x5c5079++) { var _0x3059a4 = arguments[_0x5c5079]; if (null != _0x3059a4) { for (var _0x3ede90 in _0x3059a4) Object[_0x2b8676(0x344)][_0x2b8676(0x3b8)][_0x2b8676(0x334)](_0x3059a4, _0x3ede90) && (_0x451cfb[_0x3ede90] = _0x3059a4[_0x3ede90]); } } return _0x451cfb; }, 'writable': !(0x3d * -0x43 + 0x2368 + -0x1371), 'configurable': !(0x12a + -0x1692 + 0x1568) }), Object[_0x5612de(0x17f)] || (Object['keys'] = (_0x4d6c78 = Object[_0x5612de(0x344)][_0x5612de(0x3b8)], _0x312e19 = !{ 'toString': null }[_0x5612de(0x1da)]('toString'), _0x2a32a1 = [_0x5612de(0x3ae), _0x5612de(0x2c8), _0x5612de(0x303), _0x5612de(0x3b8), 'isPrototypeOf', _0x5612de(0x1da), 'constructor'], _0x371ac2 = _0x2a32a1[_0x5612de(0x259)], function (_0x457a19) { var _0x2d3152 = _0x5612de; if (_0x2d3152(0x1ee) != typeof _0x457a19 && (_0x2d3152(0x17d) !== _0x1db123(_0x457a19) || null === _0x457a19)) throw new TypeError(_0x2d3152(0x374)); var _0x11aef0, _0x29131e, _0x4c2a63 = []; for (_0x11aef0 in _0x457a19) _0x4d6c78[_0x2d3152(0x334)](_0x457a19, _0x11aef0) && _0x4c2a63[_0x2d3152(0x36e)](_0x11aef0); if (_0x312e19) { for (_0x29131e = -0x2406 + -0x1209 + 0x1 * 0x360f; _0x29131e < _0x371ac2; _0x29131e++) _0x4d6c78['call'](_0x457a19, _0x2a32a1[_0x29131e]) && _0x4c2a63[_0x2d3152(0x36e)](_0x2a32a1[_0x29131e]); } return _0x4c2a63; } )); var _0x45b94b = { '__version__': _0x5612de(0x167), 'feVersion': 0x2, 'domNotValid': !(-0x1e27 + -0x3fa * -0x2 + 0x1634), 'refererKey': _0x5612de(0x318), 'pushVersion': _0x5612de(0x2ca), 'secInfoHeader': _0x5612de(0x248) }; function _0x598972(_0x215b45, _0x2d7329) { var _0x401595 = _0x5612de; if (_0x401595(0x33c) == typeof _0x2d7329) for (var _0x5d92d9, _0x112fe4 = _0x215b45 + '=', _0x28aeed = _0x2d7329[_0x401595(0x342)](/[;&]/), _0x26307f = 0x11 * -0x97 + 0x126 * -0xd + -0x1 * -0x18f5; _0x26307f < _0x28aeed[_0x401595(0x259)]; _0x26307f++) { for (_0x5d92d9 = _0x28aeed[_0x26307f]; '\x20' === _0x5d92d9['charAt'](0x1589 + 0x5e4 + 0x7 * -0x3eb);) _0x5d92d9 = _0x5d92d9[_0x401595(0x1b0)](-0x16d9 + -0x1 * 0x99c + 0x2 * 0x103b, _0x5d92d9['length']); if (0x15cd + -0x1235 + 0x1 * -0x398 === _0x5d92d9[_0x401595(0x2c5)](_0x112fe4)) return _0x5d92d9[_0x401595(0x1b0)](_0x112fe4[_0x401595(0x259)], _0x5d92d9[_0x401595(0x259)]); } } function _0x24dc34(_0x3e96bb) { var _0x22cc3b = _0x5612de; try { var _0xdacad4 = ''; return window['sessionStorage'] && (_0xdacad4 = window['sessionStorage'][_0x22cc3b(0x2d8)](_0x3e96bb)) || window[_0x22cc3b(0x1dc)] && (_0xdacad4 = window[_0x22cc3b(0x1dc)][_0x22cc3b(0x2d8)](_0x3e96bb)) ? _0xdacad4 : _0xdacad4 = _0x598972(_0x3e96bb, document['cookie']); } catch (_0x195c1d) { return ''; } } function _0x1f42cb(_0x247ee6, _0x28501b) { var _0xfed486 = _0x5612de; try { window[_0xfed486(0x3be)] && window['sessionStorage']['setItem'](_0x247ee6, _0x28501b), window[_0xfed486(0x1dc)] && window[_0xfed486(0x1dc)][_0xfed486(0x1c4)](_0x247ee6, _0x28501b), (document[_0xfed486(0x272)] = _0x247ee6 + _0xfed486(0x35e), document[_0xfed486(0x272)] = _0x247ee6 + '=' + _0x28501b + _0xfed486(0x1c1) + new Date(new Date()['getTime']() + (0x10 * 0x792317 + 0x37870478 + -0x2 * 0xd8658f4))[_0xfed486(0x15e)]() + _0xfed486(0x3c1)); } catch (_0x2bd822) { } } function _0x2ecc5a(_0x5893e4) { var _0x3a0d7f = _0x5612de; try { window[_0x3a0d7f(0x3be)] && window['sessionStorage']['removeItem'](_0x5893e4), window[_0x3a0d7f(0x1dc)] && window[_0x3a0d7f(0x1dc)][_0x3a0d7f(0x2c3)](_0x5893e4), document[_0x3a0d7f(0x272)] = _0x5893e4 + _0x3a0d7f(0x35e); } catch (_0x2f5b70) { } } for (var _0x462335 = { 'boe': !(0x1e0d + -0x1093 * 0x1 + -0xd79), 'aid': 0x0, 'dfp': !(-0x1 * 0x703 + 0x1025 + 0x29 * -0x39), 'sdi': !(0x49 * -0x77 + -0x6da * 0x2 + -0x4 * -0xbe9), 'enablePathList': [], '_enablePathListRegex': [], 'urlRewriteRules': [], '_urlRewriteRules': [], 'initialized': !(-0x31 * 0x3d + 0x9df + 0x1cf), 'enableTrack': !(0x89 * -0x1a + -0x21ca + 0x2fb5), 'track': { 'unitTime': 0x0, 'unitAmount': 0x0, 'fre': 0x0 }, 'triggerUnload': !(-0x3e5 * -0x5 + -0x23cb + 0x3 * 0x571), 'region': '', 'regionConf': {}, 'umode': 0x0, 'v': !(-0x1155 + -0x25d4 + -0x1 * -0x372a), '_enableSignature': [], 'perf': !(0x1 * -0x1c2e + 0xef8 + -0xc7 * -0x11), 'xxbg': !(-0xf75 + 0x3 * 0x4e5 + 0x12 * 0xb) }, _0x3d40ff = { 'debug': function (_0x2cf151, _0x4acfb1) { var _0x1b6639 = _0x5612de; _0x462335[_0x1b6639(0x1b6)]; } }, _0x100715 = _0x5612de(0x2d7)[_0x5612de(0x342)](''), _0x1f510e = [], _0x37750d = [], _0x1cb307 = -0xe55 + 0x1c37 + 0x2 * -0x6f1; _0x1cb307 < 0x241b + 0x10 * -0xe6 + -0x1 * 0x14bb; _0x1cb307++) _0x1f510e[_0x1cb307] = _0x100715[_0x1cb307 >> 0xd03 + -0x269a + 0x1 * 0x199b & 0xa64 * 0x3 + -0xf7b + -0xfa2] + _0x100715[0x2bb + -0x3 * 0x92d + 0x18db & _0x1cb307], _0x1cb307 < -0x1 * -0x82 + 0x34 * -0x3b + -0x2 * -0x5c5 && (_0x1cb307 < 0x10f1 * 0x1 + 0x86a * 0x4 + -0x328f ? _0x37750d[-0x2029 + -0x1ef1 + -0x3f4a * -0x1 + _0x1cb307] = _0x1cb307 : _0x37750d[-0x2485 + 0x1d * 0x6d + 0x1883 + _0x1cb307] = _0x1cb307); var _0x55de18 = function (_0x33a277) { var _0x5155ba = _0x5612de; for (var _0x9eb2a1 = _0x33a277[_0x5155ba(0x259)], _0x1edfde = '', _0x2a69c4 = -0x1c39 + 0x569 + 0x16d0; _0x2a69c4 < _0x9eb2a1;) _0x1edfde += _0x1f510e[_0x33a277[_0x2a69c4++]]; return _0x1edfde; } , _0x655940 = function (_0x177456) { for (var _0x1ffb9c = _0x177456['length'] >> 0x1 * 0x104b + 0x10b1 + -0x20fb, _0x3563ef = _0x1ffb9c << -0x1 * -0x263 + -0x77 * -0x21 + 0xd * -0x15d, _0x22520c = new Uint8Array(_0x1ffb9c), _0x32553 = 0x345 * 0x9 + -0x1e9a + -0x1 * -0x12d, _0x11b847 = 0x1 * 0x623 + -0x239f * -0x1 + -0x29c2; _0x11b847 < _0x3563ef;) _0x22520c[_0x32553++] = _0x37750d[_0x177456['charCodeAt'](_0x11b847++)] << -0x1551 + -0xb5a + 0x20af * 0x1 | _0x37750d[_0x177456['charCodeAt'](_0x11b847++)]; return _0x22520c; } , _0x42e709 = { 'encode': _0x55de18, 'decode': _0x655940 } , _0x1e7721 = _0x5612de(0x384) != typeof globalThis ? globalThis : _0x5612de(0x384) != typeof window ? window : _0x5612de(0x384) != typeof global ? global : 'undefined' != typeof self ? self : {}; function _0x3bc8d3(_0x14565d) { var _0x442e14 = _0x5612de; return _0x14565d && _0x14565d[_0x442e14(0x3bd)] && Object[_0x442e14(0x344)][_0x442e14(0x3b8)][_0x442e14(0x334)](_0x14565d, _0x442e14(0x1d3)) ? _0x14565d[_0x442e14(0x1d3)] : _0x14565d; } function _0x3abc0b(_0x1ca7bd) { var _0x292845 = _0x5612de; return _0x1ca7bd && Object[_0x292845(0x344)]['hasOwnProperty'][_0x292845(0x334)](_0x1ca7bd, _0x292845(0x1d3)) ? _0x1ca7bd[_0x292845(0x1d3)] : _0x1ca7bd; } function _0x41ace1(_0x2b7455) { var _0x39470d = _0x5612de; return _0x2b7455 && Object[_0x39470d(0x344)][_0x39470d(0x3b8)]['call'](_0x2b7455, _0x39470d(0x1d3)) && 0x155 + 0x16f8 + -0x184c === Object[_0x39470d(0x17f)](_0x2b7455)[_0x39470d(0x259)] ? _0x2b7455[_0x39470d(0x1d3)] : _0x2b7455; } function _0x3e9554(_0x22797e) { var _0x1fa97f = _0x5612de; if (_0x22797e[_0x1fa97f(0x3bd)]) return _0x22797e; var _0x21d08f = Object[_0x1fa97f(0x175)]({}, _0x1fa97f(0x3bd), { 'value': !(0x1f82 + -0x10d * 0x1d + 0x5 * -0x35) }); return Object[_0x1fa97f(0x17f)](_0x22797e)['forEach'](function (_0x19f7e0) { var _0x1f5a81 = _0x1fa97f , _0x3cf185 = Object[_0x1f5a81(0x2a6)](_0x22797e, _0x19f7e0); Object['defineProperty'](_0x21d08f, _0x19f7e0, _0x3cf185[_0x1f5a81(0x32b)] ? _0x3cf185 : { 'enumerable': !(-0x222 * -0x8 + 0x1 * -0x21ac + 0x84e * 0x2), 'get': function () { return _0x22797e[_0x19f7e0]; } }); }), _0x21d08f; } function _0x56409e(_0x362d96) { var _0x265452 = _0x5612de , _0x5870ef = { 'exports': {} }; return _0x362d96(_0x5870ef, _0x5870ef[_0x265452(0x1b8)]), _0x5870ef['exports']; } function _0x171dc9(_0x5983b9) { var _0x32782f = _0x5612de; throw new Error(_0x32782f(0x36d) + _0x5983b9 + _0x32782f(0x357)); } var _0x90795 = _0x56409e(function (_0xb9c7d1) { !(function () { var _0x2db296 = w_0x25f3 , _0x1f977a = 'input\x20is\x20invalid\x20type' , _0x5a87b4 = _0x2db296(0x17d) == typeof window , _0x11d177 = _0x5a87b4 ? window : {}; _0x11d177[_0x2db296(0x37a)] && (_0x5a87b4 = !(-0x15c4 + 0x97d * -0x2 + 0xb7 * 0x39)); var _0x387fd8 = !_0x5a87b4 && _0x2db296(0x17d) == typeof self , _0x2e1226 = !_0x11d177[_0x2db296(0x216)] && _0x2db296(0x17d) == typeof process && process[_0x2db296(0x194)] && process[_0x2db296(0x194)][_0x2db296(0x287)]; _0x2e1226 ? _0x11d177 = _0x1e7721 : _0x387fd8 && (_0x11d177 = self); var _0x299a21 = !_0x11d177['JS_MD5_NO_COMMON_JS'] && _0xb9c7d1[_0x2db296(0x1b8)], _0x5efc8f = !(-0x8c4 + -0xc3a + -0x1 * -0x14ff), _0x1edc95 = !_0x11d177[_0x2db296(0x2e6)] && 'undefined' != typeof ArrayBuffer, _0x142491 = _0x2db296(0x2d7)[_0x2db296(0x342)](''), _0x146907 = [0x148 * 0x5 + -0x753 * 0x2 + -0x175 * -0x6, 0x1 * 0x340b + 0x460a + 0x1f9 * 0x3, 0x2766cb + -0x6327ac + 0x353 * 0x387b, -(-0xc13e2aae + -0x3aff8440 + 0x17c3daeee)], _0x4d13bd = [-0x1721 + -0x1 * 0x611 + 0x1d32, -0x390 + -0x3a5 * -0x3 + -0x757, 0x5fc + -0x64d * -0x2 + -0x1286, 0x278 + 0x3 * 0x105 + -0x56f], _0x3f581b = [_0x2db296(0x352), _0x2db296(0x238), _0x2db296(0x19b), _0x2db296(0x275), 'arrayBuffer', _0x2db296(0x25f)], _0xcb4d61 = _0x2db296(0x315)[_0x2db296(0x342)](''), _0x5c5e45 = [], _0x54b265; if (_0x1edc95) { var _0x171107 = new ArrayBuffer(0xc87 + -0xcd8 + 0x95); _0x54b265 = new Uint8Array(_0x171107), _0x5c5e45 = new Uint32Array(_0x171107); } !_0x11d177[_0x2db296(0x216)] && Array[_0x2db296(0x2af)] || (Array['isArray'] = function (_0x263669) { var _0x1dd8cd = _0x2db296; return _0x1dd8cd(0x331) === Object['prototype']['toString']['call'](_0x263669); } ), !_0x1edc95 || !_0x11d177[_0x2db296(0x305)] && ArrayBuffer['isView'] || (ArrayBuffer['isView'] = function (_0x4d052f) { var _0x23ceed = _0x2db296; return _0x23ceed(0x17d) == typeof _0x4d052f && _0x4d052f[_0x23ceed(0x275)] && _0x4d052f['buffer'][_0x23ceed(0x2ac)] === ArrayBuffer; } ); var _0x2e393f = function (_0x55b71b) { return function (_0x6901ec) { var _0x38b178 = w_0x25f3; return new _0x22f902(!(0x191d + 0x9e + -0x19bb))[_0x38b178(0x2d5)](_0x6901ec)[_0x55b71b](); } ; } , _0x1edabb = function () { var _0x5664ca = _0x2db296 , _0x4c275f = _0x2e393f(_0x5664ca(0x352)); _0x2e1226 && (_0x4c275f = _0x4d7e2d(_0x4c275f)), _0x4c275f[_0x5664ca(0x3b7)] = function () { return new _0x22f902(); } , _0x4c275f['update'] = function (_0x5c3464) { return _0x4c275f['create']()['update'](_0x5c3464); } ; for (var _0x349d5f = -0x9d * -0x1f + 0x1 * 0x230c + -0x15 * 0x293; _0x349d5f < _0x3f581b[_0x5664ca(0x259)]; ++_0x349d5f) { var _0x1537c4 = _0x3f581b[_0x349d5f]; _0x4c275f[_0x1537c4] = _0x2e393f(_0x1537c4); } return _0x4c275f; } , _0x4d7e2d = function (_0x1afe1b) { var _0x3e0a6e = eval('var _0x13db80 = w_0x25f3;require(_0x13db80(628));') , _0x5bfe7e = eval('var _0x20dc8b = w_0x25f3;require(\'buffer\')[_0x20dc8b(424)];') , _0x3d7396 = function (_0x5142ff) { var _0x30639a = w_0x25f3; if (_0x30639a(0x33c) == typeof _0x5142ff) return _0x3e0a6e[_0x30639a(0x1ca)](_0x30639a(0x35c))[_0x30639a(0x2d5)](_0x5142ff, _0x30639a(0x2a8))[_0x30639a(0x19b)](_0x30639a(0x352)); if (null == _0x5142ff) throw _0x1f977a; return _0x5142ff[_0x30639a(0x2ac)] === ArrayBuffer && (_0x5142ff = new Uint8Array(_0x5142ff)), Array[_0x30639a(0x2af)](_0x5142ff) || ArrayBuffer[_0x30639a(0x2c7)](_0x5142ff) || _0x5142ff[_0x30639a(0x2ac)] === _0x5bfe7e ? _0x3e0a6e[_0x30639a(0x1ca)](_0x30639a(0x35c))['update'](new _0x5bfe7e(_0x5142ff))[_0x30639a(0x19b)](_0x30639a(0x352)) : _0x1afe1b(_0x5142ff); }; return _0x3d7396; }; function _0x22f902(_0xad0da7) { var _0x68ef08 = _0x2db296; if (_0xad0da7) _0x5c5e45[-0x1b4c * 0x1 + -0x126b * 0x2 + 0x4022] = _0x5c5e45[0x143f + -0x2217 + 0xde8] = _0x5c5e45[0x16 * 0x2e + -0x1d7c + 0x1989] = _0x5c5e45[0xa0 * -0x26 + -0x3 * 0x93f + 0x337f * 0x1] = _0x5c5e45[-0xed2 + 0xe1d + 0x2e * 0x4] = _0x5c5e45[-0x10ff + -0x1 * -0x394 + 0xd6f] = _0x5c5e45[-0x36f * -0x2 + 0x1 * 0x1a5 + -0x87e] = _0x5c5e45[-0x1f81 * 0x1 + 0x25ce * -0x1 + -0x1 * -0x4555] = _0x5c5e45[0x1ae7 * 0x1 + 0x1 * -0xe3c + -0xca4 * 0x1] = _0x5c5e45[-0x583 * -0x4 + -0x5f7 * -0x5 + -0x33d7] = _0x5c5e45[0x25e + -0x1d4b * 0x1 + -0x3a * -0x77] = _0x5c5e45[0xef9 + -0x30 * -0x57 + -0x1f3f] = _0x5c5e45[0x11e + -0x18f * 0x7 + 0x9d6] = _0x5c5e45[-0x2521 * -0x1 + 0x1 * 0x1bf4 + 0x4109 * -0x1] = _0x5c5e45[0x19 * 0xa6 + 0x1f06 + -0x2f2f] = _0x5c5e45[-0x22d0 + 0x1f1 * 0xf + 0x5bf] = _0x5c5e45[0x7 * 0x549 + -0xa39 + 0x1 * -0x1ab7] = 0x1 * -0xfb + 0x1995 + 0xc4d * -0x2, this['blocks'] = _0x5c5e45, this[_0x68ef08(0x329)] = _0x54b265; else { if (_0x1edc95) { var _0x44d65b = new ArrayBuffer(0x11 * -0xb + 0xbfd + -0xafe); this[_0x68ef08(0x329)] = new Uint8Array(_0x44d65b), this[_0x68ef08(0x319)] = new Uint32Array(_0x44d65b); } else this[_0x68ef08(0x319)] = [0x45 * 0x10 + -0xd15 + 0x8c5, 0x1d70 + 0x493 + -0x2203 * 0x1, 0x46 * 0x86 + 0x8 * -0x14e + -0x1a34, 0x9bb + 0x25 * 0xdf + -0x29f6, -0x1c * -0xda + 0x188 + 0xcb * -0x20, -0x442 + -0x199a + 0x1ddc, -0x2 * -0x32d + -0x605 * -0x2 + -0x1264, 0x1af * -0x8 + -0xf3c + 0x29c * 0xb, -0x8cc + 0x1c3 + 0x709 * 0x1, 0xb53 * 0x1 + 0x18e6 + -0x3 * 0xc13, -0x2 * 0x135e + 0x444 + 0x2278, 0xe8 * 0x2b + 0x2106 + -0xa * 0x733, 0x3 * 0x449 + 0x1103 + -0x1dde, 0x30 + -0x3 * -0x95 + -0x1ef, 0x3ca * -0x3 + -0x9 * -0x1cb + -0x3 * 0x197, -0x14ce + -0x1b9 + 0x1687, -0xb2f + -0x53f * 0x3 + 0x1aec]; } this['h0'] = this['h1'] = this['h2'] = this['h3'] = this[_0x68ef08(0x385)] = this[_0x68ef08(0x25b)] = this[_0x68ef08(0x1cd)] = -0x1 * 0x4f9 + 0xeb * -0xd + 0x10e8, this[_0x68ef08(0x182)] = this[_0x68ef08(0x1e9)] = !(-0x983 + -0x5b1 * -0x1 + 0x3d3), this[_0x68ef08(0x19f)] = !(-0xcdf + 0x2157 + -0x1478); } _0x22f902[_0x2db296(0x344)][_0x2db296(0x2d5)] = function (_0x36f09d) { var _0x449340 = _0x2db296; if (!this['finalized']) { var _0x462898, _0x1dd68e = typeof _0x36f09d; if (_0x449340(0x33c) !== _0x1dd68e) { if ('object' !== _0x1dd68e) throw _0x1f977a; if (null === _0x36f09d) throw _0x1f977a; if (_0x1edc95 && _0x36f09d['constructor'] === ArrayBuffer) _0x36f09d = new Uint8Array(_0x36f09d); else { if (!(Array[_0x449340(0x2af)](_0x36f09d) || _0x1edc95 && ArrayBuffer['isView'](_0x36f09d))) throw _0x1f977a; } _0x462898 = !(-0xfea + 0x449 * 0x3 + -0x1 * -0x30f); } for (var _0x2c6513, _0x1fd15b, _0x432ef6 = 0xadd + -0x4d7 * 0x2 + 0x1 * -0x12f, _0x383058 = _0x36f09d[_0x449340(0x259)], _0x17a6ed = this[_0x449340(0x319)], _0x161a31 = this[_0x449340(0x329)]; _0x432ef6 < _0x383058;) { if (this[_0x449340(0x1e9)] && (this[_0x449340(0x1e9)] = !(-0x657 + 0x4fb + -0x15d * -0x1), _0x17a6ed[0x22e1 * -0x1 + -0xeb2 + 0x3193] = _0x17a6ed[0x283 * -0xd + -0x141e + 0x34d5], _0x17a6ed[0x1af3 * 0x1 + -0x14d1 * 0x1 + -0x15 * 0x4a] = _0x17a6ed[-0xc3 * -0x2f + -0x49 * -0x31 + 0x3 * -0x1097] = _0x17a6ed[0x1e34 + -0x1 * -0x1837 + -0x3669] = _0x17a6ed[-0x12e3 + -0x1733 * -0x1 + 0x1 * -0x44d] = _0x17a6ed[0x31 * -0x8b + 0x1643 * 0x1 + 0x45c] = _0x17a6ed[0x87 * -0xd + -0x14d5 + 0x1bb5] = _0x17a6ed[0x1d70 + 0x255a + 0x4 * -0x10b1] = _0x17a6ed[0x1312 + 0x2e6 + 0x15f1 * -0x1] = _0x17a6ed[0x1ed9 + 0x551 * -0x1 + -0x1980] = _0x17a6ed[0x269 * -0x7 + 0x1d8 + 0xf10] = _0x17a6ed[0x1134 + -0x1 * -0x15cd + -0xcfd * 0x3] = _0x17a6ed[0x1229 * 0x1 + 0x23 * -0xe3 + 0xceb] = _0x17a6ed[0x15 * 0x13a + -0x23e3 * 0x1 + 0x1 * 0xa2d] = _0x17a6ed[-0x4 * 0x8ee + -0x1d7d + 0x4142 * 0x1] = _0x17a6ed[0x9d9 + 0x1 * -0x215b + 0x34 * 0x74] = _0x17a6ed[0x1e72 + -0x9 * 0x2b + 0xa8 * -0x2c] = 0x857 + 0x1cef + -0x2546), _0x462898) { if (_0x1edc95) { for (_0x1fd15b = this[_0x449340(0x385)]; _0x432ef6 < _0x383058 && _0x1fd15b < -0x836 + 0x1150 + -0xce * 0xb; ++_0x432ef6) _0x161a31[_0x1fd15b++] = _0x36f09d[_0x432ef6]; } else { for (_0x1fd15b = this[_0x449340(0x385)]; _0x432ef6 < _0x383058 && _0x1fd15b < -0x4 * -0x4e8 + -0xedb * 0x1 + -0x485; ++_0x432ef6) _0x17a6ed[_0x1fd15b >> 0x11 * 0x5c + 0x1378 * -0x2 + 0x3 * 0xaf2] |= _0x36f09d[_0x432ef6] << _0x4d13bd[0x1 * -0x4f + -0x2036 + -0x1044 * -0x2 & _0x1fd15b++]; } } else { if (_0x1edc95) { for (_0x1fd15b = this[_0x449340(0x385)]; _0x432ef6 < _0x383058 && _0x1fd15b < 0xc4 * -0x1 + 0x78f * -0x1 + 0x893; ++_0x432ef6) (_0x2c6513 = _0x36f09d[_0x449340(0x195)](_0x432ef6)) < 0xd * 0x1a + 0x8f + -0x161 ? _0x161a31[_0x1fd15b++] = _0x2c6513 : _0x2c6513 < 0x1b9a + 0x1 * -0x4a9 + 0xef1 * -0x1 ? (_0x161a31[_0x1fd15b++] = -0x1aaa * -0x1 + 0x2511 * -0x1 + -0x23b * -0x5 | _0x2c6513 >> 0x1568 + 0x75a * 0x1 + -0x994 * 0x3, _0x161a31[_0x1fd15b++] = 0x54a + -0x1 * 0x1a66 + -0x1cd * -0xc | 0x8ad * 0x4 + 0x6a3 * 0x1 + -0x2918 & _0x2c6513) : _0x2c6513 < 0x1807f + -0x1 * -0x44d4 + 0xed53 * -0x1 || _0x2c6513 >= -0x1 * -0x123e4 + 0x1be31 + -0x5 * 0x66d1 ? (_0x161a31[_0x1fd15b++] = 0x1 * 0x11f + 0xffd * -0x1 + 0xfbe | _0x2c6513 >> 0x1 * 0x65 + 0x533 + -0x11c * 0x5, _0x161a31[_0x1fd15b++] = 0x4 * 0x72b + 0x23c9 * 0x1 + -0x3ff5 | _0x2c6513 >> 0x9 * 0x9 + 0x5 * 0x517 + -0x5 * 0x526 & 0x200b + 0x1 * 0xbe7 + -0x2bb3, _0x161a31[_0x1fd15b++] = 0x1 * 0x128f + 0x11 * -0x6a + -0xb05 | 0x27 * 0x59 + -0x2126 + 0x1 * 0x13d6 & _0x2c6513) : (_0x2c6513 = -0xb295 + 0x185 * 0x139 + -0x2908 + ((-0x1ec2 * -0x1 + -0x183e + -0xf * 0x2b & _0x2c6513) << 0x2e * 0x3 + 0x14b + 0x1 * -0x1cb | 0x1225 * 0x1 + 0x2376 + -0x319c & _0x36f09d[_0x449340(0x195)](++_0x432ef6)), _0x161a31[_0x1fd15b++] = -0x969 + 0xc97 + -0x23e | _0x2c6513 >> -0xc19 + -0x14f9 + 0x2124, _0x161a31[_0x1fd15b++] = 0x202 + 0x1 * 0x187 + -0x309 | _0x2c6513 >> -0x1da3 + -0xa * 0xda + -0x379 * -0xb & 0x2 * -0x117b + -0x7c9 + 0x2afe, _0x161a31[_0x1fd15b++] = 0x1 * -0x1043 + 0x1185 + -0xc2 | _0x2c6513 >> -0x8c6 + 0x1d87 + -0x1d * 0xb7 & 0x2a * 0xe7 + -0x1 * 0x1869 + -0xd3e, _0x161a31[_0x1fd15b++] = -0x58e + 0x6 * -0x216 + 0x949 * 0x2 | 0x2474 * -0x1 + 0xa9 * 0xd + 0x1c1e & _0x2c6513); } else { for (_0x1fd15b = this[_0x449340(0x385)]; _0x432ef6 < _0x383058 && _0x1fd15b < -0x48a * 0x7 + 0x1993 + -0xd * -0x7f; ++_0x432ef6) (_0x2c6513 = _0x36f09d[_0x449340(0x195)](_0x432ef6)) < 0x2 * -0x86 + 0x101 * 0x1f + -0x1 * 0x1d93 ? _0x17a6ed[_0x1fd15b >> -0x1297 + 0x71a + 0xb7f] |= _0x2c6513 << _0x4d13bd[-0x1f26 + 0xd17 + 0x1212 & _0x1fd15b++] : _0x2c6513 < 0x11b7 + -0x150d + 0xb56 ? (_0x17a6ed[_0x1fd15b >> -0x231f + -0x8eb + 0x2c0c] |= (0x1ae0 + 0x70b + -0x212b | _0x2c6513 >> -0x87f + -0x23f * 0x5 + -0x13c * -0x10) << _0x4d13bd[0x3fb + 0x8e1 * -0x1 + 0x4e9 & _0x1fd15b++], _0x17a6ed[_0x1fd15b >> -0x1d97 + 0x6ad + 0x16ec] |= (0x1d0e + 0xf0a + -0x2b98 | 0xf2c + 0xbb * -0x20 + 0x873 & _0x2c6513) << _0x4d13bd[0x8d9 + -0x17e0 + 0xf0a & _0x1fd15b++]) : _0x2c6513 < -0x8b7d + 0x113bf + 0x4fbe || _0x2c6513 >= -0xc9c0 + 0x16d4a * 0x1 + 0x3c76 ? (_0x17a6ed[_0x1fd15b >> 0x2 * 0x74f + 0x1533 + -0x23cf] |= (-0x7 * 0x28 + -0x2 * -0x1bb + -0x17e | _0x2c6513 >> 0x1d5c + 0x1e6c + -0x1 * 0x3bbc) << _0x4d13bd[0x12 * 0x119 + 0x4 * -0x1c6 + -0xca7 & _0x1fd15b++], _0x17a6ed[_0x1fd15b >> -0x26 * 0x2e + -0x12aa * 0x2 + -0x2c2a * -0x1] |= (0x245a + -0x1aa1 + -0x1 * 0x939 | _0x2c6513 >> 0x1fa6 + -0x735 + -0x2f * 0x85 & -0x5d3 + -0x1eb2 * -0x1 + -0x18a0) << _0x4d13bd[0xa40 + 0x1f49 + -0x84e * 0x5 & _0x1fd15b++], _0x17a6ed[_0x1fd15b >> 0x21 * -0xa4 + -0x139 * -0x5 + 0x1 * 0xf09] |= (-0x1434 + 0x5 * -0x35 + 0x15bd | -0xc2d + 0x2655 + 0x25b * -0xb & _0x2c6513) << _0x4d13bd[0x76 * 0x26 + -0x1 * 0x8ad + 0x1c4 * -0x5 & _0x1fd15b++]) : (_0x2c6513 = 0x12e * -0xbf + 0x1cc8a + 0x8c * 0x26 + ((-0x3f5 * 0x3 + -0xfe8 + 0x1fc6 & _0x2c6513) << 0x29a + 0x13a9 + -0x1639 | 0x1 * -0x1dd7 + 0x6a5 + 0x1 * 0x1b31 & _0x36f09d[_0x449340(0x195)](++_0x432ef6)), _0x17a6ed[_0x1fd15b >> 0xd * 0x61 + 0xcb * -0x1e + 0x12df] |= (0x1 * -0xee4 + 0x2 * -0x767 + -0x6 * -0x51b | _0x2c6513 >> 0x1 * 0x1a4d + -0x3 * 0x149 + -0x1660) << _0x4d13bd[-0x1c70 + -0x1 * -0x57b + 0x1 * 0x16f8 & _0x1fd15b++], _0x17a6ed[_0x1fd15b >> 0xef1 + -0xba0 + -0x1 * 0x34f] |= (-0xa3 * -0x3 + 0x26af + 0x140c * -0x2 | _0x2c6513 >> -0x518 + 0x1 * -0xc26 + 0x114a & -0x25 * -0xd1 + -0x1130 * -0x1 + -0x2f26) << _0x4d13bd[-0x73 * -0xb + -0x17d * 0x1 + 0x1 * -0x371 & _0x1fd15b++], _0x17a6ed[_0x1fd15b >> 0x3 * -0x107 + 0x1dbd + -0x1aa6] |= (-0x151c + 0x16 * -0x191 + 0x3812 | _0x2c6513 >> 0xded * -0x1 + 0x45 + 0xdae & -0x2af * -0x7 + -0x49 + 0x1241 * -0x1) << _0x4d13bd[-0x19a2 + -0x7d2 + 0x2177 & _0x1fd15b++], _0x17a6ed[_0x1fd15b >> -0x180e + 0x13d2 + -0x1 * -0x43e] |= (-0x9 * -0x1f5 + -0xf1 * 0xd + -0x6 * 0xd0 | 0x7f * 0x2f + 0x1 * 0x188c + -0x986 * 0x5 & _0x2c6513) << _0x4d13bd[0x1cf9 + 0xb * -0xe7 + -0x1309 & _0x1fd15b++]); } } this['lastByteIndex'] = _0x1fd15b, this[_0x449340(0x25b)] += _0x1fd15b - this['start'], _0x1fd15b >= -0xa42 + 0x2 * -0x11c3 + 0x2e08 ? (this[_0x449340(0x385)] = _0x1fd15b - (-0x2b * 0xe2 + 0xa55 + 0xd * 0x225), this[_0x449340(0x2ba)](), this[_0x449340(0x1e9)] = !(0xc54 + -0x1 * -0xfa3 + -0x1 * 0x1bf7)) : this[_0x449340(0x385)] = _0x1fd15b; } return this[_0x449340(0x25b)] > -0x14462193f + -0xd7facb0 + -0x2 * -0x128f0e2f7 && (this['hBytes'] += this['bytes'] / (0x73e4b4e0 * 0x3 + -0x3fc * -0x40b5a1 + -0x486a * 0x4d396) << -0x1fbb * 0x1 + -0x1623 + 0x35de, this[_0x449340(0x25b)] = this[_0x449340(0x25b)] % (0x7d756c * -0x1e1 + -0x3b6aede * -0x2 + 0x278 * 0xc42bda)), this; } } , _0x22f902[_0x2db296(0x344)][_0x2db296(0x1d9)] = function () { var _0x8b694 = _0x2db296; if (!this[_0x8b694(0x182)]) { this[_0x8b694(0x182)] = !(0x1038 + 0x1 * -0x247f + 0x1d * 0xb3); var _0x52f24a = this[_0x8b694(0x319)] , _0x21cbd8 = this[_0x8b694(0x32e)]; _0x52f24a[_0x21cbd8 >> 0x4cd * 0x4 + -0x1 * -0x1511 + -0x2843] |= _0x146907[0xb2e + -0x10 * -0xbf + -0x41 * 0x5b & _0x21cbd8], _0x21cbd8 >= 0x8fe * 0x3 + -0x231c + -0x42d * -0x2 && (this['hashed'] || this['hash'](), _0x52f24a[0x666 + 0x1432 + -0x1a98] = _0x52f24a[0x24cd + 0x1e1 * 0x14 + 0x19 * -0x2f9], _0x52f24a[-0x1f46 + 0x1 * 0x1015 + 0xf41] = _0x52f24a[0xbe8 + 0x1a96 + 0xa7 * -0x3b] = _0x52f24a[-0xb * 0x14d + -0x1 * 0x59 + -0xeaa * -0x1] = _0x52f24a[-0x1f1 * -0x3 + -0x1d69 + 0x1799] = _0x52f24a[0xa7b * -0x2 + 0xbb1 * -0x1 + -0x20ab * -0x1] = _0x52f24a[-0x11dc + -0xaa + 0x2f * 0x65] = _0x52f24a[-0x1 * 0x1f99 + -0x1e53 * 0x1 + 0x3df2] = _0x52f24a[-0x103 * -0x5 + -0x2451 + 0x1f49] = _0x52f24a[0x21e6 + -0xf9 * 0x1 + -0x1 * 0x20e5] = _0x52f24a[0x88a + -0x1bea + -0x1 * -0x1369] = _0x52f24a[0xf4d + -0x177d * -0x1 + -0x26c0] = _0x52f24a[0x1e22 + 0x655 * 0x3 + -0x3116] = _0x52f24a[-0x196b + 0x19b * -0x10 + -0x2d * -0x123] = _0x52f24a[-0x1 * -0x228d + 0x1cfc + -0x3f7c] = _0x52f24a[-0x13b4 + -0x5bc + 0x197e] = _0x52f24a[0x1e68 + 0x1209 + -0x3062] = 0x26a1 + -0x124 * -0x20 + -0x4b21), _0x52f24a[0xbb3 + 0x1210 + -0x1db5] = this[_0x8b694(0x25b)] << -0x15 * -0x1d7 + 0x2 * -0xc2a + -0xe4c, _0x52f24a[-0x19e2 + 0x28 * 0x55 + 0xca9] = this[_0x8b694(0x1cd)] << -0x178 * 0x6 + 0xa67 + 0x194 * -0x1 | this[_0x8b694(0x25b)] >>> 0x1a65 + -0x18 * 0x44 + -0x16c * 0xe, this['hash'](); } } , _0x22f902[_0x2db296(0x344)][_0x2db296(0x2ba)] = function () { var _0x2f0149 = _0x2db296, _0x227f23, _0x3952af, _0x2a0c94, _0x39eb64, _0x2fd641, _0x113678, _0x128d32 = this[_0x2f0149(0x319)]; this['first'] ? _0x3952af = ((_0x3952af = ((_0x227f23 = ((_0x227f23 = _0x128d32[0xe * 0xd4 + 0x18b * -0x13 + 0x11b9] - (0x1ffe472d + -0x1 * 0x229b4beb + 0x8a3acdb * 0x5)) << -0xe3 + 0x794 * 0x5 + 0x2 * -0x127d | _0x227f23 >>> -0x2125 + 0x21d4 + -0x96) - (-0xa42713c + -0x131 * -0xc4faf + 0xbc9d634) << -0x76 + -0x13ee + 0x1464) ^ (_0x2a0c94 = ((_0x2a0c94 = (-(0x1d99 * -0x236f + 0xdf * -0x44be3 + 0x18092f8b) ^ (_0x39eb64 = ((_0x39eb64 = (-(-0xfc * -0xaf0303 + 0x524d2f32 + -0xcedcd * 0xbb4) ^ 0x1 * 0x7b470ebd + 0x1 * 0x4d64e86c + -0x51347fb2 & _0x227f23) + _0x128d32[-0x2684 + 0x1f4e * -0x1 + 0x8f * 0x7d] - (0x563f0e * -0x16 + 0x59003ae + 0x46faddd * 0x2)) << -0x684 + 0xa * 0x289 + -0x1a * 0xb9 | _0x39eb64 >>> -0x1116 * -0x2 + 0xee9 * -0x1 + -0x132f * 0x1) + _0x227f23 << -0x2687 * -0x1 + 0xfe3 + -0x1 * 0x366a) & (-(0x1a9157f + 0x1e0df645 + -0xf84b74d) ^ _0x227f23)) + _0x128d32[0x144d + 0x15b * -0x18 + 0xc3d] - (-0x1 * 0x5504eeba + 0x76f3f96f * 0x1 + -0x2 * -0x109ad3b9)) << 0x1af4 + -0x12cb + -0x818 | _0x2a0c94 >>> 0x7aa * 0x1 + -0x1994 * -0x1 + -0x212f) + _0x39eb64 << -0x9fc + -0x14 * 0xfa + -0x1d84 * -0x1) & (_0x39eb64 ^ _0x227f23)) + _0x128d32[-0x2a * 0x97 + 0x18c8 + 0x1 * 0x1] - (-0x992 * 0x9361 + 0x23b1c4 + 0x1 * 0x53d34a17)) << -0x7 * 0x1b1 + -0x234d + -0x136 * -0x27 | _0x3952af >>> -0x113f * 0x1 + 0x5 * -0x7f + 0x1cc * 0xb) + _0x2a0c94 << -0x4f0 + -0x1 * -0x1f3d + -0x1a4d : (_0x227f23 = this['h0'], _0x3952af = this['h1'], _0x2a0c94 = this['h2'], _0x3952af = ((_0x3952af += ((_0x227f23 = ((_0x227f23 += ((_0x39eb64 = this['h3']) ^ _0x3952af & (_0x2a0c94 ^ _0x39eb64)) + _0x128d32[0x3a5 + -0x1684 + 0x12df * 0x1] - (0x2b26bdc2 + -0x1 * -0x4468952b + -0x5773 * 0xcfc7)) << -0x1805 + -0x18cb + 0x30d7 | _0x227f23 >>> 0x21ec * -0x1 + -0x53c * -0x1 + 0x1cc9) + _0x3952af << 0xfe0 + 0x16a4 + -0x2684) ^ (_0x2a0c94 = ((_0x2a0c94 += (_0x3952af ^ (_0x39eb64 = ((_0x39eb64 += (_0x2a0c94 ^ _0x227f23 & (_0x3952af ^ _0x2a0c94)) + _0x128d32[0x1 * -0x480 + 0x1 * 0x112f + -0xcae] - (0x1e4d0bcf + 0x1be * -0xbfe2b + 0xdd00bc5)) << -0x26fe + -0x462 + 0x2b6c | _0x39eb64 >>> -0x1 * 0x251 + -0xd16 + 0xf7b) + _0x227f23 << 0x15be * 0x1 + 0x2e3 * 0x8 + -0x2cd6) & (_0x227f23 ^ _0x3952af)) + _0x128d32[-0xa5 * -0x1d + 0x5 * -0x105 + 0x2f * -0x4a] + (0x15 * 0x27c2519 + -0x1cb83878 + 0x654cf23 * 0x2)) << -0x9d5 + -0x221e + -0xeac * -0x3 | _0x2a0c94 >>> -0x718 * 0x2 + -0x5 * -0x556 + -0x425 * 0x3) + _0x39eb64 << 0x223 * -0xb + -0x92 * 0x22 + 0x2ae5) & (_0x39eb64 ^ _0x227f23)) + _0x128d32[-0x141c + 0x376 + 0x10a9] - (0x29f06346 + 0x14e90f87 * -0x3 + -0x3 * -0x1baefecb)) << 0x103a + -0x24f4 + 0x14d0 | _0x3952af >>> -0x16a5 + -0xe92 + 0xbb * 0x33) + _0x2a0c94 << 0x25a2 * 0x1 + 0x1a76 + -0x4018 * 0x1), _0x3952af = ((_0x3952af += ((_0x227f23 = ((_0x227f23 += (_0x39eb64 ^ _0x3952af & (_0x2a0c94 ^ _0x39eb64)) + _0x128d32[-0x2552 + 0x577 + 0x1fdf] - (-0xec5a4e1 + 0x23fe4 * -0x78c + -0x9e * -0x447abf)) << -0x46e + 0x19b5 + -0x8 * 0x2a8 | _0x227f23 >>> 0x100e + 0xd63 + -0x1d58) + _0x3952af << -0x237d + 0x1955 * -0x1 + 0x3cd2) ^ (_0x2a0c94 = ((_0x2a0c94 += (_0x3952af ^ (_0x39eb64 = ((_0x39eb64 += (_0x2a0c94 ^ _0x227f23 & (_0x3952af ^ _0x2a0c94)) + _0x128d32[-0x203 + 0xd54 + -0x2d3 * 0x4] + (0x1 * -0x20e3c675 + 0x8ef8e325 + 0x4e53fd * -0x7e)) << -0x9e3 * 0x2 + 0x2 * -0x8fd + 0x25cc | _0x39eb64 >>> -0x7bd + 0x2 * -0x844 + 0x1859 * 0x1) + _0x227f23 << -0x3dc + -0x122e + 0x326 * 0x7) & (_0x227f23 ^ _0x3952af)) + _0x128d32[-0xbfd + -0x1 * 0xba3 + 0x17a6] - (-0x93f8bbe0 + -0x43dcbb55 + 0x1 * 0x12fa53122)) << 0x185 * -0x10 + -0x76c + 0x1fcd | _0x2a0c94 >>> 0x19b + 0x51 * 0x6d + 0x171 * -0x19) + _0x39eb64 << 0x971 + 0x1958 + -0x5 * 0x6f5) & (_0x39eb64 ^ _0x227f23)) + _0x128d32[-0x978 * -0x3 + 0x315 + -0x2 * 0xfbb] - (-0x99026a + 0xa6ba6 * 0x47 + 0xc4927 * 0x9)) << 0x212 + 0x1 * 0x4c3 + 0xb * -0x9d | _0x3952af >>> -0x152a + 0x1 * -0x17b4 + -0x77c * -0x6) + _0x2a0c94 << -0xa7 * 0x1f + 0x2379 + -0xf40, _0x3952af = ((_0x3952af += ((_0x227f23 = ((_0x227f23 += (_0x39eb64 ^ _0x3952af & (_0x2a0c94 ^ _0x39eb64)) + _0x128d32[-0x251a + -0x14 * -0x18d + 0x61e] + (0xb19f3051 + -0xb487bda1 * 0x1 + 0x6c692628)) << 0x1cc + -0x171a + 0x1555 | _0x227f23 >>> 0x1 * -0x223b + -0x282 + 0x24d6) + _0x3952af << 0x1453 + 0x1aee + -0x2f41) ^ (_0x2a0c94 = ((_0x2a0c94 += (_0x3952af ^ (_0x39eb64 = ((_0x39eb64 += (_0x2a0c94 ^ _0x227f23 & (_0x3952af ^ _0x2a0c94)) + _0x128d32[-0x1c63 + -0x1a88 + 0x36f4] - (0x334017ba + 0x1 * -0x90d7af8d + -0x86 * -0x191cf86)) << -0x1db2 + 0x1 * 0x10b1 + 0xd0d | _0x39eb64 >>> -0x4 * -0x20 + -0xd81 * -0x1 + -0xded) + _0x227f23 << -0x2b0 + 0x4 * -0x5b0 + -0x94 * -0x2c) & (_0x227f23 ^ _0x3952af)) + _0x128d32[-0x1 * -0xf25 + 0xe75 * 0x1 + -0x1d90] - (-0x5 * 0x4eb + 0x12ff3 * -0x1 + 0x1 * 0x1ecd9)) << -0xa43 + 0x1eed + 0x1 * -0x1499 | _0x2a0c94 >>> -0x1878 + -0x92b + 0x21b2) + _0x39eb64 << -0x1 * 0x2515 + -0x8c8 + -0x3b * -0xc7) & (_0x39eb64 ^ _0x227f23)) + _0x128d32[0x80b * -0x4 + 0xff5 + 0x1042] - (-0x2d479161 * 0x5 + -0x139 * 0x33685d + 0x197e398dc)) << -0x178 + -0x22bf + 0x1 * 0x244d | _0x3952af >>> 0x15cc + -0x26b6 + -0x5 * -0x364) + _0x2a0c94 << 0x293 * -0x2 + 0x18f7 + -0x13d1, _0x3952af = ((_0x3952af += ((_0x227f23 = ((_0x227f23 += (_0x39eb64 ^ _0x3952af & (_0x2a0c94 ^ _0x39eb64)) + _0x128d32[-0x1225 + -0xa * -0x348 + 0x13 * -0xc5] + (-0x36f3e05c + 0x4445538c + 0x5e3e9df2)) << -0x1b3 * -0x3 + 0x2702 + -0x2c14 | _0x227f23 >>> -0x2 * 0x1006 + 0x25 * 0xc5 + 0x3ac) + _0x3952af << 0x2501 + 0x18fb + 0xf7f * -0x4) ^ (_0x2a0c94 = ((_0x2a0c94 += (_0x3952af ^ (_0x39eb64 = ((_0x39eb64 += (_0x2a0c94 ^ _0x227f23 & (_0x3952af ^ _0x2a0c94)) + _0x128d32[-0x13cb + -0xcd4 + 0x20ac] - (-0x24 * 0x1e9ae6 + -0xa0ba1 * 0x72 + -0x18cc1 * -0x737)) << 0x702 + 0x2b * -0x35 + 0x1f1 | _0x39eb64 >>> -0x15dd + -0x22eb * -0x1 + 0x1 * -0xcfa) + _0x227f23 << 0x224b * 0x1 + 0x2f * 0x33 + 0x4 * -0xaea) & (_0x227f23 ^ _0x3952af)) + _0x128d32[0xec7 * -0x1 + -0x61 * -0x25 + 0xd0] - (0x48e87164 + 0x613cb841 + -0x509e6d33)) << -0x1c0d + 0x5d0 + 0x164e | _0x2a0c94 >>> 0x156a * 0x1 + 0x16e7 + -0x2c42) + _0x39eb64 << -0x1 * -0x19ec + 0x8b4 + -0x22a0) & (_0x39eb64 ^ _0x227f23)) + _0x128d32[0x1df0 + -0x2365 + -0x1 * -0x584] + (0x34eb7 * 0x25f0 + -0x3 * -0x1a616a08 + -0x82ea7487)) << -0x3 * 0xc8c + 0x1fc2 * -0x1 + -0x457c * -0x1 | _0x3952af >>> -0x2464 + 0x2705 + -0xdd * 0x3) + _0x2a0c94 << -0x504 + 0x2 * -0x5f9 + 0x10f6, _0x3952af = ((_0x3952af += ((_0x39eb64 = ((_0x39eb64 += (_0x3952af ^ _0x2a0c94 & ((_0x227f23 = ((_0x227f23 += (_0x2a0c94 ^ _0x39eb64 & (_0x3952af ^ _0x2a0c94)) + _0x128d32[0x1b4d + 0x2d4 * 0x5 + -0x2970] - (0x30139 * 0x8d + -0x738f7c2 + -0x1 * -0xf7325fb)) << 0x1acc + -0x13e5 + -0x6e2 | _0x227f23 >>> 0x7 * 0x1 + -0x10e9 + 0x10fd * 0x1) + _0x3952af << -0x18fd + 0x10cc + 0x831) ^ _0x3952af)) + _0x128d32[0x2 * 0x131e + -0x2511 + 0x1 * -0x125] - (-0x1f * -0x3953eea + -0x4 * -0x12786c0f + -0x793501d2)) << 0x210d + 0x11 * -0x241 + 0x54d * 0x1 | _0x39eb64 >>> -0x4 * -0x265 + -0x292 + 0xfd * -0x7) + _0x227f23 << -0x200b + -0xa * 0xff + -0x1 * -0x2a01) ^ _0x227f23 & ((_0x2a0c94 = ((_0x2a0c94 += (_0x227f23 ^ _0x3952af & (_0x39eb64 ^ _0x227f23)) + _0x128d32[-0x92 * -0x13 + 0x6b0 + -0x117b] + (-0x4be8e758 + -0x1a3c * -0x1e38f + 0x40b96625)) << -0x1 * 0x13d1 + 0x22da + -0xefb | _0x2a0c94 >>> 0x166d + -0x955 + -0xd06) + _0x39eb64 << -0x13af * 0x1 + -0x1 * -0x1307 + 0xa8) ^ _0x39eb64)) + _0x128d32[-0x547 + -0x1 * 0x1c0b + -0x2152 * -0x1] - (0x7 * 0x14d828a + 0x7d4a6f4 + -0x3 * -0x1c75534)) << 0x1bb9 * -0x1 + -0x26ee + 0x42bb * 0x1 | _0x3952af >>> 0x8 * 0x296 + 0x1ce9 * 0x1 + -0x318d) + _0x2a0c94 << 0x897 * 0x4 + 0x2ca + 0x3 * -0xc62, _0x3952af = ((_0x3952af += ((_0x39eb64 = ((_0x39eb64 += (_0x3952af ^ _0x2a0c94 & ((_0x227f23 = ((_0x227f23 += (_0x2a0c94 ^ _0x39eb64 & (_0x3952af ^ _0x2a0c94)) + _0x128d32[0x107f * -0x1 + -0x1299 + -0x59 * -0x65] - (0x1079c263 + -0x1889 * -0x11026 + -0xbe0716)) << -0x260c + -0x1c23 + 0x2 * 0x211a | _0x227f23 >>> 0x22b * 0x1 + -0x16f3 + -0x14e3 * -0x1) + _0x3952af << 0x183b + -0x5 * -0x72a + -0x3c0d * 0x1) ^ _0x3952af)) + _0x128d32[0x4 * 0x86b + 0x1064 + -0x3206] + (-0x1177f56 + -0x27de7f4 + -0x540d * -0x11d1)) << 0x167 * 0x5 + -0x5 * 0x531 + 0x12fb * 0x1 | _0x39eb64 >>> 0x244b + -0x13 * 0x139 + -0x9 * 0x171) + _0x227f23 << -0xf6d + -0x1f9e + 0x2f0b * 0x1) ^ _0x227f23 & ((_0x2a0c94 = ((_0x2a0c94 += (_0x227f23 ^ _0x3952af & (_0x39eb64 ^ _0x227f23)) + _0x128d32[0x187 * 0x4 + 0x1e69 + -0x2476] - (-0x10191ede + -0xd72a9b7 + 0x44e9e214)) << 0x90 * 0xc + -0x2 * 0x12 + -0x68e | _0x2a0c94 >>> 0x16 * 0x1c5 + 0x137b + 0x1d * -0x203) + _0x39eb64 << -0x14fa + 0x43 * -0x74 + -0x3356 * -0x1) ^ _0x39eb64)) + _0x128d32[-0xf4a + -0x1992 + 0x4 * 0xa38] - (-0xae6496c + 0x21ece5a8 + 0x12567fc)) << 0x1ac8 + -0x1ed2 + 0x3e * 0x11 | _0x3952af >>> 0x1268 + 0x100d + 0x1 * -0x2269) + _0x2a0c94 << 0x1817 * -0x1 + 0x239b + -0xb84 * 0x1, _0x3952af = ((_0x3952af += ((_0x39eb64 = ((_0x39eb64 += (_0x3952af ^ _0x2a0c94 & ((_0x227f23 = ((_0x227f23 += (_0x2a0c94 ^ _0x39eb64 & (_0x3952af ^ _0x2a0c94)) + _0x128d32[0x5 * 0x19c + 0xef3 + -0x16f6] + (0x39d62e7 + 0xb397e9b + 0x130aec64)) << 0x11 * 0x21f + -0x1c66 + -0x7a4 | _0x227f23 >>> -0xf86 + 0x2 * -0x4e5 + 0x879 * 0x3) + _0x3952af << 0x258a + 0x19ec + -0x3f76) ^ _0x3952af)) + _0x128d32[0x914 + 0xd73 + 0x20b * -0xb] - (-0xab52d21 + -0x845d9 * 0x1f7 + 0x57bf62aa)) << -0x1 * 0x2f + -0x1280 + 0x12b8 | _0x39eb64 >>> 0x2e * -0x9 + 0x85 * 0x4b + -0x2542) + _0x227f23 << -0x151 * 0x9 + 0x20da + -0x1501) ^ _0x227f23 & ((_0x2a0c94 = ((_0x2a0c94 += (_0x227f23 ^ _0x3952af & (_0x39eb64 ^ _0x227f23)) + _0x128d32[-0x84 * 0x39 + 0x57f + 0x2fd * 0x8] - (0x852a967 + 0xa4943f2 + -0x770fae0)) << -0x5 * -0x722 + 0x1 * -0x155f + -0xe3d | _0x2a0c94 >>> 0x171b + -0x2709 * 0x1 + 0x8 * 0x200) + _0x39eb64 << -0x267 + 0x6fb * -0x4 + -0x7 * -0x455) ^ _0x39eb64)) + _0x128d32[0xd73 + -0xe3f + 0xd4] + (-0x882b6c12 + 0x26ddcab5 + 0x9d1 * 0x10fa2a)) << -0x1 * -0xb31 + 0xa * -0x175 + 0x375 | _0x3952af >>> 0x106 * 0x26 + 0x2e9 + -0x29c1) + _0x2a0c94 << -0x198a + 0xb33 * -0x1 + 0x24bd, _0x3952af = ((_0x3952af += ((_0x39eb64 = ((_0x39eb64 += (_0x3952af ^ _0x2a0c94 & ((_0x227f23 = ((_0x227f23 += (_0x2a0c94 ^ _0x39eb64 & (_0x3952af ^ _0x2a0c94)) + _0x128d32[0x1d4f + -0x10ae + -0xc94] - (0x76d313f * 0xd + -0x5 * -0x7a635fc + -0x2 * 0x18573b92)) << 0x612 + -0x1 * 0x40 + -0x5cd | _0x227f23 >>> -0x12e9 * 0x2 + -0x1 * -0x19e7 + -0x1 * -0xc06) + _0x3952af << 0x10db + 0x10b5 * 0x1 + -0x3 * 0xb30) ^ _0x3952af)) + _0x128d32[-0x796 + -0x13b9 + 0x1b51] - (-0x17e5b * 0x29 + 0x48058e0 + -0x132c045)) << 0x26d * -0x2 + 0x4 * -0x34e + -0x5 * -0x39f | _0x39eb64 >>> 0x198d * -0x1 + -0x25d4 + -0xa94 * -0x6) + _0x227f23 << 0x206d + -0xd3f + -0x132e) ^ _0x227f23 & ((_0x2a0c94 = ((_0x2a0c94 += (_0x227f23 ^ _0x3952af & (_0x39eb64 ^ _0x227f23)) + _0x128d32[-0x1f * 0x95 + 0x6 * -0x3dc + -0x3 * -0xdbe] + (-0x2 * 0x5ec874bd + -0x4537c41d * -0x1 + 0xdfc82836)) << 0x1336 + -0x12bc + -0x6c | _0x2a0c94 >>> 0x119 * -0x1d + -0x3 * 0xc41 + 0x44aa * 0x1) + _0x39eb64 << 0xe * -0x29c + 0x4ea + 0x1f9e) ^ _0x39eb64)) + _0x128d32[0x985 + -0x1177 + 0x1 * 0x7fe] - (-0x3d273af + -0x1 * -0x72611590 + 0x4471195)) << 0x112 * 0x15 + -0xa30 + -0x412 * 0x3 | _0x3952af >>> 0x1 * 0xe74 + 0x6 * 0x1f7 + -0x1a32) + _0x2a0c94 << 0xf1 + -0xe43 * -0x2 + -0x1d77 * 0x1, _0x3952af = ((_0x3952af += ((_0x113678 = (_0x39eb64 = ((_0x39eb64 += ((_0x2fd641 = _0x3952af ^ _0x2a0c94) ^ (_0x227f23 = ((_0x227f23 += (_0x2fd641 ^ _0x39eb64) + _0x128d32[-0x87 * -0x1 + -0x1 * -0x1626 + -0x16a8] - (-0x3ca72 + -0xac995 + 0x145ac5)) << -0x1231 + -0x1 * -0xb05 + 0x730 | _0x227f23 >>> 0x20dd * 0x1 + -0x16b8 + -0xa09) + _0x3952af << 0x1 * 0xe63 + 0xa34 * -0x2 + 0x605)) + _0x128d32[-0x9a * -0x23 + 0x25fd + -0x3b03] - (0xb06c444e + 0x2d68c * 0x1574 + 0x9 * -0xcf8fe07)) << 0x1f60 * -0x1 + -0x1fc2 + -0x1 * -0x3f2d | _0x39eb64 >>> 0x1c0e + 0x7c * -0x3b + 0x9b * 0x1) + _0x227f23 << -0x1 * 0x7db + 0x143d * 0x1 + -0x2 * 0x631) ^ _0x227f23) ^ (_0x2a0c94 = ((_0x2a0c94 += (_0x113678 ^ _0x3952af) + _0x128d32[0x1315 + 0x22f5 + -0x35ff] + (0x1 * -0xc1ee2861 + 0x5f64a13c + 0xd026e847)) << 0x10b * 0x15 + 0x9e2 + -0x1fb9 | _0x2a0c94 >>> 0x213e + -0x11 * -0x93 + -0x1 * 0x2af1) + _0x39eb64 << -0x4 * 0x7c8 + 0x1 * 0x33d + 0x1be3)) + _0x128d32[0x18c5 * 0x1 + 0x94d * -0x3 + 0x2 * 0x198] - (0xbbbcc3 + 0x1509 * -0x2a32 + 0x4d6a0f3)) << 0x12b5 * 0x1 + -0xc * 0xa2 + -0xb06 | _0x3952af >>> -0x596 * 0x5 + 0x2 * -0x655 + 0x28a1) + _0x2a0c94 << 0x5f * -0x36 + 0x2b1 * 0x4 + 0x946, _0x3952af = ((_0x3952af += ((_0x113678 = (_0x39eb64 = ((_0x39eb64 += ((_0x2fd641 = _0x3952af ^ _0x2a0c94) ^ (_0x227f23 = ((_0x227f23 += (_0x2fd641 ^ _0x39eb64) + _0x128d32[-0xaa9 + 0x6 * 0x54e + 0x7e * -0x2b] - (-0x1309d * -0x67b3 + -0x8e97ecd6 + 0x6e74d9cb)) << -0x1 * 0x1169 + -0x1c3f + 0x2dac | _0x227f23 >>> -0x12f * 0x1d + 0x178 + 0x20f7) + _0x3952af << -0x1554 + 0x2 * -0x136e + 0x9 * 0x6b0)) + _0x128d32[0x8bd * 0x4 + 0x993 + -0x2c83] + (-0x1ec1ce29 + 0x12d9d323 + 0x57c6caaf)) << 0x2f9 * 0x1 + 0x1a55 + -0x1d43 | _0x39eb64 >>> -0xdf6 + 0x94 * 0x2 + 0xce3) + _0x227f23 << -0xd5 + -0x197e + 0x1a53) ^ _0x227f23) ^ (_0x2a0c94 = ((_0x2a0c94 += (_0x113678 ^ _0x3952af) + _0x128d32[0x697 * -0x1 + -0x617 + 0xcb5] - (0x2 * -0x11de459 + 0xaabba88 + 0xd4c2ca)) << -0x1198 * 0x1 + -0x42c * 0x3 + 0x2 * 0xf16 | _0x2a0c94 >>> 0x28e * 0x2 + 0x1547 + -0x125 * 0x17) + _0x39eb64 << 0x45 * 0x21 + -0x14 * -0x7 + 0x971 * -0x1)) + _0x128d32[-0x1 * -0x118b + 0x1f4 + -0x1375] - (0x2 * -0x34ed62bd + -0x6d39a445 + -0x11854ad4f * -0x1)) << -0x27f * -0x1 + -0x4 * 0x31a + 0xa00 | _0x3952af >>> -0x319 + -0x1 * -0x247d + -0x215b * 0x1) + _0x2a0c94 << -0x1bc7 * 0x1 + 0x1 * -0x4f7 + 0x20be, _0x3952af = ((_0x3952af += ((_0x113678 = (_0x39eb64 = ((_0x39eb64 += ((_0x2fd641 = _0x3952af ^ _0x2a0c94) ^ (_0x227f23 = ((_0x227f23 += (_0x2fd641 ^ _0x39eb64) + _0x128d32[-0x1 * -0x1eb5 + -0x1ba5 + -0x3 * 0x101] + (0x338d6ddd + 0x1 * 0x3db4c799 + -0x48a6b6b0)) << 0x126a + 0x1 * 0x407 + -0x166d * 0x1 | _0x227f23 >>> -0x1f97 + -0x2566 * -0x1 + 0x5b3 * -0x1) + _0x3952af << 0x2175 + 0x2187 * 0x1 + -0x42fc)) + _0x128d32[0x1701 + 0x1fb6 + 0x36b7 * -0x1] - (0x1e38ad99 + -0x3 * -0x1727b1c + -0xd3146e7)) << 0x15 * -0x18d + -0x4 * -0x75e + 0x324 | _0x39eb64 >>> -0x7f8 + 0x1 * 0xc1 + 0x74c) + _0x227f23 << -0x86 * 0x27 + -0x1 * 0x1a93 + -0x17 * -0x20b) ^ _0x227f23) ^ (_0x2a0c94 = ((_0x2a0c94 += (_0x113678 ^ _0x3952af) + _0x128d32[0x1 * -0x1c6f + -0x10f0 + -0x4a * -0x9d] - (-0x353a5 * 0xce7 + 0x510a05c * 0xe + 0xf144056)) << 0x2c * -0x8f + -0x20 * -0x11b + 0x2af * -0x4 | _0x2a0c94 >>> -0x167c * -0x1 + 0x1bef * -0x1 + 0x583) + _0x39eb64 << -0x3 * 0x3b + 0x1555 + 0xa52 * -0x2)) + _0x128d32[0x35 * -0x27 + -0x1 * 0xdb8 + -0x1 * -0x15d1] + (0x2e39f97 * 0x1 + -0x1 * 0x58f09b4 + 0x7338722)) << 0x1d3 * -0x6 + 0x1 * 0x171b + 0x406 * -0x3 | _0x3952af >>> 0x3c1 + -0x1cbf + 0x1907) + _0x2a0c94 << 0x1b73 + 0x1d44 + 0x38b7 * -0x1, _0x3952af = ((_0x3952af += ((_0x113678 = (_0x39eb64 = ((_0x39eb64 += ((_0x2fd641 = _0x3952af ^ _0x2a0c94) ^ (_0x227f23 = ((_0x227f23 += (_0x2fd641 ^ _0x39eb64) + _0x128d32[0x30b * 0x2 + 0xa52 + -0x105f] - (0x131 * 0x25e655 + -0xe61e * 0x48b + 0xe5c * -0x33bb)) << 0x17a1 + -0x26 * 0x3f + 0x3 * -0x4c1 | _0x227f23 >>> 0x1079 + -0x13 * 0xf5 + 0x1d2) + _0x3952af << -0xf27 + -0x1 * -0x21d1 + 0x955 * -0x2)) + _0x128d32[0x20e * -0x3 + -0x4a7 * 0x8 + 0x2b6e] - (0x1 * 0x79f0ea3 + -0x184cea1d + -0x11d * -0x2590d9)) << 0x12e3 + 0x12b * -0x2 + -0x1082 | _0x39eb64 >>> -0x485 * 0x2 + -0x61 * 0x1a + 0x12f9) + _0x227f23 << 0x1 * -0x34c + -0x6 * 0x4dc + -0x1 * -0x2074) ^ _0x227f23) ^ (_0x2a0c94 = ((_0x2a0c94 += (_0x113678 ^ _0x3952af) + _0x128d32[-0x3e3 + 0x22d * -0x2 + 0x84c] + (-0xdb8446c + -0x1 * -0x5cb3a83 + 0x278f86e1)) << -0x7 * -0x3f1 + 0x17f * 0x7 + -0x2600 | _0x2a0c94 >>> -0x47e + 0xce * 0x2 + 0x2 * 0x179) + _0x39eb64 << 0x1 * 0x9fe + 0x1 * 0x17f5 + -0x21f3)) + _0x128d32[-0x18 * -0x18e + -0x1dff + -0x74f * 0x1] - (0x12fee507 + -0x1 * 0x6a5c4769 + -0x1 * -0x92b10bfd)) << 0x5 * -0x52a + -0x1e75 + 0xde * 0x41 | _0x3952af >>> 0x116b + -0x1 * 0x9ed + 0x53 * -0x17) + _0x2a0c94 << 0x264e * -0x1 + -0x11fd + 0x384b, _0x3952af = ((_0x3952af += ((_0x39eb64 = ((_0x39eb64 += (_0x3952af ^ ((_0x227f23 = ((_0x227f23 += (_0x2a0c94 ^ (_0x3952af | ~_0x39eb64)) + _0x128d32[-0x1524 + 0x65 * 0x5b + 0x1 * -0xec3] - (0x63c0c3 * 0x1f + 0x11a61fd * -0xc + -0xcff1dfb * -0x1)) << 0x36c + -0x4eb * 0x5 + 0x1531 | _0x227f23 >>> -0x941 * -0x2 + -0x1b * -0x9b + -0x22c1) + _0x3952af << 0x1862 + -0x1359 + -0x509) | ~_0x2a0c94)) + _0x128d32[-0x26d + -0x6 * 0xbd + 0x6e2] + (-0x690c7 * 0xd6d + 0x2c437c8e * 0x1 + -0x3786a162 * -0x2)) << 0x54a + -0x1bd3 + 0x1693 | _0x39eb64 >>> 0x1076 + 0xa0d + -0x1a6d) + _0x227f23 << -0x1d37 + 0x1 * 0x14fb + 0x83c) ^ ((_0x2a0c94 = ((_0x2a0c94 += (_0x227f23 ^ (_0x39eb64 | ~_0x3952af)) + _0x128d32[0x190f * -0x1 + -0x1503 + 0x2e20] - (0x12aa3 * 0x80ad + 0x5 * 0x123e2fa3 + -0x9ce661fd)) << -0x204a + -0x22cf + 0x4328 | _0x2a0c94 >>> 0x1 * 0xcac + -0x1bbf * -0x1 + -0x142d * 0x2) + _0x39eb64 << -0x11b4 + 0x10b1 * 0x1 + -0x25 * -0x7) | ~_0x227f23)) + _0x128d32[0x1 * -0x10d + 0x5f4 + -0x19 * 0x32] - (0x2181582 + -0x55ca10e + 0x6b0eb53)) << -0xd3d * 0x1 + -0x1dd7 + -0x1 * -0x2b29 | _0x3952af >>> 0x676 + 0xa84 * 0x1 + -0x10ef) + _0x2a0c94 << -0x2 * 0xc83 + 0x15 * -0x10c + -0x1781 * -0x2, _0x3952af = ((_0x3952af += ((_0x39eb64 = ((_0x39eb64 += (_0x3952af ^ ((_0x227f23 = ((_0x227f23 += (_0x2a0c94 ^ (_0x3952af | ~_0x39eb64)) + _0x128d32[0x2 * -0x527 + 0x246c + -0x8e * 0x2f] + (0x62359ef1 + 0xc3bd213a + -0xc0976668)) << -0x22fa * 0x1 + 0x2af + 0x2051 | _0x227f23 >>> -0x92b + -0x4b4 * 0x1 + 0xdf9) + _0x3952af << 0x494 + -0x1902 + -0x1 * -0x146e) | ~_0x2a0c94)) + _0x128d32[0x1d8 * 0x1 + 0x1 * -0x5ce + 0x3f9] - (-0x324 * -0x49722 + 0xde * 0x31ad3b + -0x1bba29be * -0x2)) << 0x941 + -0x1651 + -0x4e * -0x2b | _0x39eb64 >>> 0x2 * -0xc2 + -0x112a + 0x12c4) + _0x227f23 << 0x1369 + -0x1261 + -0x3 * 0x58) ^ ((_0x2a0c94 = ((_0x2a0c94 += (_0x227f23 ^ (_0x39eb64 | ~_0x3952af)) + _0x128d32[0x10c5 + 0x78f + 0x1 * -0x184a] - (-0x3b62a * 0x6 + 0x15245b + 0x89612 * 0x2)) << 0x252f + -0x5 * -0x661 + -0x4505 | _0x2a0c94 >>> 0x251e + 0x6 * 0x2b7 + -0x3557 * 0x1) + _0x39eb64 << -0x1 * -0xa79 + -0x8d * -0x33 + -0x2690) | ~_0x227f23)) + _0x128d32[0x395 * 0x8 + -0x24a9 + -0x52 * -0x19] - (-0xc3191853 + -0x57fdcd8 * 0x29 + -0x141d00ee * -0x1b)) << 0x132b * -0x1 + -0x13 * -0x15 + 0x11b1 | _0x3952af >>> -0x175f + 0x16 * 0xc2 + -0x2 * -0x35f) + _0x2a0c94 << -0xc * -0x2de + -0x1 * -0x261a + 0x1 * -0x4882, _0x3952af = ((_0x3952af += ((_0x39eb64 = ((_0x39eb64 += (_0x3952af ^ ((_0x227f23 = ((_0x227f23 += (_0x2a0c94 ^ (_0x3952af | ~_0x39eb64)) + _0x128d32[0x4 * -0x527 + -0xb71 + 0x2015 * 0x1] + (0x2 * -0x2405587c + 0x963dc885 * 0x1 + 0x217566c2)) << 0x14cb + -0x647 * -0x1 + -0x1b0c | _0x227f23 >>> -0xbd * 0x30 + -0x13 * 0x14f + 0x7 * 0x8a1) + _0x3952af << -0xd * 0x215 + 0xcc + 0x1a45) | ~_0x2a0c94)) + _0x128d32[-0x638 * 0x6 + -0x25fa + 0x4b59] - (0xab1cd2 + -0x184b1f6 + 0x2acae44)) << -0x177b + -0x500 + 0x1c85 | _0x39eb64 >>> -0x1e73 + -0x3 * 0x1f1 + -0x2 * -0x122e) + _0x227f23 << 0xb5 * -0x3 + 0x158f + -0x4 * 0x4dc) ^ ((_0x2a0c94 = ((_0x2a0c94 += (_0x227f23 ^ (_0x39eb64 | ~_0x3952af)) + _0x128d32[-0xe39 + 0x452 + 0x9ed] - (0x5a22d2a4 + 0x3a146128 + 0x154a34 * -0x298)) << 0x1 * -0x1fd0 + 0x1837 * 0x1 + 0x7a8 | _0x2a0c94 >>> 0x1212 + 0x2 * -0xc47 + 0x68d) + _0x39eb64 << -0x20ba + 0xbb2 + -0x8 * -0x2a1) | ~_0x227f23)) + _0x128d32[0x283 * 0x8 + -0x5f * 0x37 + 0x5e] + (-0x15db9faa + 0x3841d * -0x1db3 + 0xcc505a92)) << -0x172f + -0x5f * -0x1 + 0x16e5 | _0x3952af >>> 0x6c + 0x2 * 0x530 + 0xac1 * -0x1) + _0x2a0c94 << 0x3 * 0x312 + 0x19 * -0x107 + 0x1079, _0x3952af = ((_0x3952af += ((_0x39eb64 = ((_0x39eb64 += (_0x3952af ^ ((_0x227f23 = ((_0x227f23 += (_0x2a0c94 ^ (_0x3952af | ~_0x39eb64)) + _0x128d32[-0x5bc + -0x1 * -0x887 + -0x1 * 0x2c7] - (0xd0d1959 + 0x9a4d1b1 + -0xe05698c)) << 0x3 * 0x6a7 + 0x252e + -0x391d | _0x227f23 >>> -0x2292 + -0x1385 + 0x1 * 0x3631) + _0x3952af << -0x2483 * -0x1 + 0x93e + 0xd * -0x385) | ~_0x2a0c94)) + _0x128d32[-0x1fd2 + 0x1bb0 + -0x1 * -0x42d] - (-0x9b7 * 0xd9fae + -0x9fdcf8b * -0x7 + 0xb3bb54 * 0xb8)) << 0x1f * 0xec + -0x260 + -0x18a * 0x11 | _0x39eb64 >>> 0x140 * -0x6 + 0x2231 * -0x1 + 0x29c7) + _0x227f23 << 0x26ef * -0x1 + -0x112b + 0x2b * 0x14e) ^ ((_0x2a0c94 = ((_0x2a0c94 += (_0x227f23 ^ (_0x39eb64 | ~_0x3952af)) + _0x128d32[-0x1 * -0x100d + 0xdc0 + -0x1d * 0x107] + (0x43dc7475 + 0x524cbd91 + -0x82661 * 0xd2b)) << -0x1f * -0xf + -0x1d3a + -0x1 * -0x1b78 | _0x2a0c94 >>> 0x235b + 0x162b + -0x3975) + _0x39eb64 << -0xf * -0x27b + -0xc58 + -0x18dd) | ~_0x227f23)) + _0x128d32[-0x72 * -0x1f + 0xac4 + -0x1889] - (0xa * -0x3c812a2 + -0x2c * 0x1bc4a2 + -0x3 * -0x15053b89)) << 0x4 * 0x9c2 + -0xd46 * 0x2 + -0xc67 | _0x3952af >>> -0x8b9 + -0x1ed3 * 0x1 + -0x1 * -0x2797) + _0x2a0c94 << -0x1 * -0xede + 0x15d3 + -0x1f * 0x12f, this['first'] ? (this['h0'] = _0x227f23 + (-0x295123b + 0xc62a6020 * 0x1 + -0x22 * 0x2b71052) << -0x2 * -0x602 + -0x1 * 0x12c2 + 0x6be, this['h1'] = _0x3952af - (0x3a5b52c + 0x1d978e61 + 0x7 * -0x26f46ba) << 0x5ea * 0x4 + 0xe5 + -0x4e9 * 0x5, this['h2'] = _0x2a0c94 - (0xa6be44c7 + 0xc54d85a4 + -0x104c6a769) << 0x6d * 0x53 + 0x1809 + -0x3b60, this['h3'] = _0x39eb64 + (-0x1fdbc0b + 0x1 * 0x1a1047ff + 0x85f6 * -0xf0d) << 0x26e8 + -0x5d * 0x1f + 0x151 * -0x15, this[_0x2f0149(0x19f)] = !(0x25a2 + -0x5 * 0x1d5 + -0x38f * 0x8)) : (this['h0'] = this['h0'] + _0x227f23 << 0x923 + 0xb1c + -0x143f, this['h1'] = this['h1'] + _0x3952af << -0x23 * 0xc0 + 0x1aef + -0xaf, this['h2'] = this['h2'] + _0x2a0c94 << -0x1b53 + -0x1acc + -0xa3 * -0x55, this['h3'] = this['h3'] + _0x39eb64 << -0xba0 * 0x1 + 0x146 + 0xa * 0x109); } , _0x22f902[_0x2db296(0x344)]['hex'] = function () { this['finalize'](); var _0x58d18c = this['h0'] , _0x4e2807 = this['h1'] , _0x41534b = this['h2'] , _0x136921 = this['h3']; return _0x142491[_0x58d18c >> 0x3cb * -0x4 + -0x6e * -0x3 + 0xde6 & 0x3b * 0x47 + 0x1567 + -0x25b5] + _0x142491[-0x1731 + -0x16e + 0x9 * 0x2be & _0x58d18c] + _0x142491[_0x58d18c >> -0xa78 + 0x18d4 + 0xe5 * -0x10 & 0x81e + -0x6 * 0x529 + 0x16e7] + _0x142491[_0x58d18c >> 0x1894 + -0x3c + -0x1850 & -0x2228 * -0x1 + 0x3 * 0x4ff + -0x3116] + _0x142491[_0x58d18c >> -0x67 * -0x1 + -0x1842 + 0x17ef & 0x580 * -0x6 + 0x1b * 0x171 + -0x5dc] + _0x142491[_0x58d18c >> -0x1 * -0x709 + 0x1594 + -0x1 * 0x1c8d & 0x1633 + -0x70d + -0xf17] + _0x142491[_0x58d18c >> -0xaf6 + 0x1ddb * 0x1 + -0x12c9 & -0x4b * 0x7b + 0x5 * 0x41b + 0xf91] + _0x142491[_0x58d18c >> -0x1 * 0x1543 + -0x1795 * -0x1 + -0x23a & -0x1e70 + 0xcc5 + -0x11ba * -0x1] + _0x142491[_0x4e2807 >> 0x651 + -0x10f7 * -0x2 + -0x283b & 0x1199 + -0x47 * -0x59 + -0x2a39] + _0x142491[0xe3 * -0x22 + 0x2479 + 0x644 * -0x1 & _0x4e2807] + _0x142491[_0x4e2807 >> 0x121a + 0x1a84 + -0x2c92 & -0x65 * -0x4a + -0x5db + -0x1748] + _0x142491[_0x4e2807 >> 0x14a3 + -0x11 * 0x55 + -0x5 * 0x2fe & -0x24e3 * -0x1 + 0xf64 + -0x3438] + _0x142491[_0x4e2807 >> 0x1cf + -0x1c89 + -0x2 * -0xd67 & -0xd47 * -0x2 + -0x1 * 0x221b + 0x1e7 * 0x4] + _0x142491[_0x4e2807 >> 0x673 + 0x1d12 + -0x1 * 0x2375 & 0x12ce + -0x8 * -0xb5 + -0x1867] + _0x142491[_0x4e2807 >> 0x1 * 0x1499 + 0x1a6b * 0x1 + -0x98 * 0x4f & 0x1516 + -0x147 * -0x2 + -0x1795 * 0x1] + _0x142491[_0x4e2807 >> 0x4 * 0x570 + -0x2 * 0x622 + -0x2 * 0x4b2 & -0x6 * -0x15d + -0x7eb + -0x34] + _0x142491[_0x41534b >> 0x1738 + -0x6 * 0x23b + -0x3 * 0x346 & -0x97 * -0x3a + 0x3a6 + -0x25cd] + _0x142491[-0x48 * -0x11 + 0x1 * -0x2502 + 0x13 * 0x1b3 & _0x41534b] + _0x142491[_0x41534b >> -0x1 * -0x1556 + 0x3 * 0x623 + -0x27b3 * 0x1 & -0x6b * 0x1f + 0x1 * -0x49 + -0x3 * -0x46f] + _0x142491[_0x41534b >> -0x794 + -0x1 * 0x2443 + 0x2bdf & 0x26ca + -0xb02 + 0x2f * -0x97] + _0x142491[_0x41534b >> 0x177f + -0x57b + -0x11f * 0x10 & 0x1eed + 0xbb2 + -0x2a90] + _0x142491[_0x41534b >> 0xab7 * -0x2 + 0x3ba + 0x11c4 & -0x1621 + 0xa3f * 0x2 + -0xd9 * -0x2] + _0x142491[_0x41534b >> 0x607 + -0x2065 + 0x1a7a & 0x2 * -0xb0f + 0x629 * -0x5 + 0x1a7d * 0x2] + _0x142491[_0x41534b >> -0x2205 + 0x20ac + 0x171 & -0x1 * -0x1337 + 0x6e * -0x1 + -0x22 * 0x8d] + _0x142491[_0x136921 >> -0x845 + -0x1 * 0x267b + 0x1762 * 0x2 & 0x340 + -0x21 * 0xcb + 0x16fa] + _0x142491[0x4 * -0x8e2 + -0xdea + 0x3181 & _0x136921] + _0x142491[_0x136921 >> 0x1784 + 0xa * 0x31b + -0x1 * 0x3686 & -0x26 * 0x4f + -0x1 * -0xca8 + 0xdf * -0x1] + _0x142491[_0x136921 >> 0x1823 * -0x1 + 0xe5c * -0x2 + 0x1 * 0x34e3 & 0x34e * 0x6 + 0x47f + -0x1844 * 0x1] + _0x142491[_0x136921 >> 0x1 * -0x10af + 0x39a + 0xd29 & -0xea2 + 0x2 * -0xad2 + 0x2455] + _0x142491[_0x136921 >> 0xadf + 0x2db * -0x7 + 0x92e & -0x493 * 0x3 + 0x15e1 + -0x819] + _0x142491[_0x136921 >> -0x22d6 + 0x20 * -0x17 + 0x25d2 & -0x17 * 0x83 + 0x1214 + -0x640] + _0x142491[_0x136921 >> -0x96e + -0x1e99 + 0x281f & -0x18c8 + 0x33 * 0x89 + -0x274]; } , _0x22f902['prototype'][_0x2db296(0x3ae)] = _0x22f902[_0x2db296(0x344)]['hex'], _0x22f902[_0x2db296(0x344)]['digest'] = function () { var _0x5a0a9e = _0x2db296; this[_0x5a0a9e(0x1d9)](); var _0x2a8796 = this['h0'] , _0x2d7c31 = this['h1'] , _0x1378d3 = this['h2'] , _0x2e667f = this['h3']; return [0x1 * 0x4bd + 0x1f15 + 0x5 * -0x6f7 & _0x2a8796, _0x2a8796 >> 0xbcd + -0x25f7 + 0x1a32 & 0x1507 + -0x177d + 0x1 * 0x375, _0x2a8796 >> -0x141c + 0x1ae2 + -0x6b6 & 0x132a + -0x3 * 0x423 + 0xb * -0x86, _0x2a8796 >> -0x1 * -0x261f + 0x115 * -0xc + -0x190b & 0x1233 * -0x1 + 0xacf * -0x3 + 0x339f, -0x16a0 + 0x1002 + 0x79d * 0x1 & _0x2d7c31, _0x2d7c31 >> -0x2 * 0xff4 + -0x541 * 0x2 + -0xe26 * -0x3 & 0x54e + -0x1c * -0x148 + -0x282f * 0x1, _0x2d7c31 >> -0x4 * -0x83f + 0x20ab * -0x1 + -0x41 & 0x427 * 0x5 + 0xdd1 + -0x2195 * 0x1, _0x2d7c31 >> 0xf * -0x201 + 0x1c26 + -0x9 * -0x39 & 0x7 * -0x235 + 0x1be + 0xeb4 * 0x1, 0x34 * 0x9b + 0xa36 + -0x28b3 & _0x1378d3, _0x1378d3 >> 0x463 * 0x1 + -0x1f0d + 0x1ab2 & 0x13 * -0x17f + -0x536 + 0x22a2, _0x1378d3 >> 0x4a7 + -0x1a4 + -0x2f3 & 0x1066 + 0xb3d + -0x1aa4, _0x1378d3 >> -0xc00 + -0x2 * 0xfcb + 0x2bae & 0x11a7 * -0x1 + 0x3a1 * -0x1 + 0x1647, -0xa38 + -0x1304 + 0x1e3b & _0x2e667f, _0x2e667f >> 0x1102 + 0x1896 + -0x2f8 * 0xe & -0x15b2 * -0x1 + -0xa6 * 0x31 + -0xf * -0xbd, _0x2e667f >> 0x25a2 + -0x4 * 0x103 + -0x2186 & -0xa83 * -0x3 + -0x15f1 + -0x899, _0x2e667f >> 0xb95 * 0x3 + -0x1a34 + -0x873 & 0xfb6 * -0x2 + 0x3b * 0x3a + 0x130d]; } , _0x22f902[_0x2db296(0x344)][_0x2db296(0x238)] = _0x22f902['prototype'][_0x2db296(0x19b)], _0x22f902['prototype'][_0x2db296(0x249)] = function () { var _0x45ab2e = _0x2db296; this[_0x45ab2e(0x1d9)](); var _0x31d586 = new ArrayBuffer(0x13 * -0x20e + -0x4 * -0x62f + 0xe5e) , _0x2d9fbd = new Uint32Array(_0x31d586); return _0x2d9fbd[-0xbcc + -0xf5 * -0x3 + -0x5 * -0x1c9] = this['h0'], _0x2d9fbd[0x5 * 0x103 + 0x26ee + -0x2bfc] = this['h1'], _0x2d9fbd[0x7d + -0x1b05 * -0x1 + -0x1b80] = this['h2'], _0x2d9fbd[0x1 * 0x1c1c + 0x1 * 0x4eb + -0x2104] = this['h3'], _0x31d586; } , _0x22f902['prototype']['buffer'] = _0x22f902[_0x2db296(0x344)][_0x2db296(0x249)], _0x22f902[_0x2db296(0x344)][_0x2db296(0x25f)] = function () { var _0x16e12f = _0x2db296; for (var _0x1dad5c, _0x24f20d, _0x70d99b, _0xb48dfc = '', _0x13e0cb = this[_0x16e12f(0x238)](), _0x12e65e = -0x719 * -0x5 + -0x747 * -0x5 + 0x170 * -0x32; _0x12e65e < -0x8c + 0x164f + -0x15b4;) _0x1dad5c = _0x13e0cb[_0x12e65e++], _0x24f20d = _0x13e0cb[_0x12e65e++], _0x70d99b = _0x13e0cb[_0x12e65e++], _0xb48dfc += _0xcb4d61[_0x1dad5c >>> 0x13b + 0x209 * -0xb + 0x152a] + _0xcb4d61[0x13d3 + 0x4c7 + -0x185b & (_0x1dad5c << 0x1334 + 0x1dd7 + 0x7 * -0x701 | _0x24f20d >>> -0x3b3 * 0xa + -0x17b * -0x13 + -0x1 * -0x8e1)] + _0xcb4d61[-0x25dd + 0x14bd + 0x115f & (_0x24f20d << -0x787 + 0x888 + -0xff | _0x70d99b >>> 0xe74 + -0x104f + 0xd * 0x25)] + _0xcb4d61[0x68e + -0x1082 + -0x175 * -0x7 & _0x70d99b]; return _0x1dad5c = _0x13e0cb[_0x12e65e], _0xb48dfc += _0xcb4d61[_0x1dad5c >>> 0x2b * -0x77 + 0x29e * 0x2 + 0xec3] + _0xcb4d61[_0x1dad5c << -0x14c0 + -0x2 * -0x11a4 + 0x2 * -0x742 & 0xe5f + -0x1dab + 0xf8b * 0x1] + '=='; } ; var _0x336469 = _0x1edabb(); _0x299a21 ? _0xb9c7d1[_0x2db296(0x1b8)] = _0x336469 : (_0x11d177[_0x2db296(0x35c)] = _0x336469, _0x5efc8f && (void (0x1118 + -0x25 * 0x98 + 0x1a0 * 0x3))(function () { return _0x336469; })); }()); }); function _0x5dd467(_0x2e8eb5) { var _0x344c50 = _0x5612de; return w_0x5c3140(_0x344c50(0x333), { get 0x0() { return _0x90795; }, 0x1: arguments, 0x2: _0x2e8eb5 }, this); } function _0x176a57() { var _0x33742c = _0x5612de; return !!document[_0x33742c(0x373)]; } function _0x1230e7() { return 'undefined' != typeof InstallTrigger; } function _0xf8ccf1() { var _0x25d57d = _0x5612de; return /constructor/i[_0x25d57d(0x22b)](window['HTMLElement']) || _0x25d57d(0x226) === (!window['safari'] || _0x25d57d(0x384) != typeof safari && safari['pushNotification'])[_0x25d57d(0x3ae)](); } function _0x30c916() { var _0x56f7c2 = _0x5612de; return new Date()[_0x56f7c2(0x16d)](); } function _0x5af46a(_0xeaff35) { return null == _0xeaff35 ? '' : 'boolean' == typeof _0xeaff35 ? _0xeaff35 ? '1' : '0' : _0xeaff35; } function _0x325f58(_0x3fdcb7, _0x2838bc) { var _0x17d12c = _0x5612de; _0x2838bc || (_0x2838bc = _0x17d12c(0x24c)); for (var _0x3b8f2c = '', _0x98f0de = _0x3fdcb7; _0x98f0de > -0x2 * -0x90e + 0x4 * -0x3ae + -0x364; --_0x98f0de) _0x3b8f2c += _0x2838bc[Math[_0x17d12c(0x1cf)](Math['random']() * _0x2838bc[_0x17d12c(0x259)])]; return _0x3b8f2c; } var _0x39693d = { 'sec': 0x9, 'asgw': 0x5, 'init': 0x0 } , _0x6caf = { 'bogusIndex': 0x0, 'msNewTokenList': [], 'moveList': [], 'clickList': [], 'keyboardList': [], 'activeState': [], 'aidList': [] }; function _0x53b77d(_0x357347) { return w_0x5c3140('484e4f4a403f5243001714366d6da13c00000025f8c25369000000ee00110307070002161103021200031103070700021303062b2f11030207000335490700044211010044001400011101014a1200001100010700010d05000000003c000e00054303491101034a12000607000711000143024911010433000611010412000833000911010412000812000947002100110107070002161101021200031101070700021303062b2f110102070003354902110105430047004f11010433002511010412000a11010412000b190400962934001111010412000c11010412000d190400962947002100110107070002161101021200031101070700021303062b2f11010207000335490842000e0e7170737c7b7045677a657067616c027c7108717077607272706707707b63767a71700003727061047c7b737a02307607767a7b667a797007737c67707760720a7a60617067427c71617d0a7c7b7b7067427c71617d0b7a606170675d707c727d610b7c7b7b70675d707c727d61', { get 0x0() { return Image; }, 0x1: Object, get 0x2() { return _0x6caf; }, get 0x3() { return console; }, get 0x4() { return window; }, get 0x5() { return _0x1230e7; }, 0x6: arguments, 0x7: _0x357347 }, this); } function _0x3ed707() { var _0x2077c7 = _0x5612de; return w_0x5c3140(_0x2077c7(0x31d), { get 0x0() { return navigator; }, get 0x1() { var _0x573a52 = _0x2077c7; return _0x573a52(0x384) != typeof global ? global : void (-0x11 * -0x1cf + 0x95 * 0x16 + -0x1 * 0x2b8d); }, 0x2: Object, get 0x3() { var _0x4b9092 = _0x2077c7; return _0x4b9092(0x384) != typeof process ? process : void (-0x65 * 0x2 + 0x2579 * 0x1 + -0x1 * 0x24af); }, get 0x4() { return _0x1db123; }, 0x5: arguments }, this); } function _0x328bde(_0xacb410, _0x4ed7bd, _0x49d137) { var _0x197e65 = _0x5612de , _0x2d8b28 = _0x197e65(0x2ce) , _0x4dd4b8 = '='; _0x49d137 && (_0x4dd4b8 = ''), _0x4ed7bd && (_0x2d8b28 = _0x4ed7bd); for (var _0x1aa067, _0x3b8c55 = '', _0x2d1a36 = 0xe0f * 0x2 + -0x35b * -0x8 + 0x29e * -0x15; _0xacb410[_0x197e65(0x259)] >= _0x2d1a36 + (0x73 * -0x26 + -0xb4e + 0x1c63);) _0x1aa067 = (-0x1cb9 + 0x2c6 + -0x1 * -0x1af2 & _0xacb410[_0x197e65(0x195)](_0x2d1a36++)) << 0x1de9 + 0x1c + -0x1 * 0x1df5 | (-0x1 * 0xd91 + -0x23a + 0x10ca & _0xacb410[_0x197e65(0x195)](_0x2d1a36++)) << -0x16 * 0x135 + 0x9e2 * 0x3 + -0x188 * 0x2 | -0x752 + -0x2 * 0xaf3 + 0x1e37 & _0xacb410[_0x197e65(0x195)](_0x2d1a36++), _0x3b8c55 += _0x2d8b28[_0x197e65(0x25c)]((0x1c78125 + -0x1df1122 + -0xb * -0x190d17 & _0x1aa067) >> 0x59 * 0x4f + -0x1176 + -0x1 * 0x9ef), _0x3b8c55 += _0x2d8b28['charAt']((-0x1 * 0x13b6b + -0x13 * -0x65aa + -0x26033 & _0x1aa067) >> 0x17ea * -0x1 + 0xef7 + 0x8ff), _0x3b8c55 += _0x2d8b28['charAt']((-0x1 * 0x16bd + 0x115 + 0x10a * 0x24 & _0x1aa067) >> 0x29c * -0x1 + -0x1 * 0x1475 + 0x1717), _0x3b8c55 += _0x2d8b28[_0x197e65(0x25c)](-0xc * 0x196 + 0x1fae + -0xc67 & _0x1aa067); return _0xacb410[_0x197e65(0x259)] - _0x2d1a36 > -0x69f + -0x1 * 0xe5c + 0x14fb && (_0x1aa067 = (0x740 + -0x1ed1 + 0x1890 & _0xacb410['charCodeAt'](_0x2d1a36++)) << -0x7 * 0xec + 0x2167 * 0x1 + -0x1ae3 | (_0xacb410['length'] > _0x2d1a36 ? (0x16aa + -0x762 * 0x1 + -0xe49 & _0xacb410[_0x197e65(0x195)](_0x2d1a36)) << 0x1af7 + -0x26be + 0xbcf : 0x1 * -0x649 + -0x1 * 0xe16 + 0x2e9 * 0x7), _0x3b8c55 += _0x2d8b28[_0x197e65(0x25c)]((-0x1 * 0x484e9 + -0x13c88 * -0x4a + -0x28bd * -0x40d & _0x1aa067) >> 0x6c * -0x4a + -0x81c * -0x2 + 0xf12), _0x3b8c55 += _0x2d8b28['charAt']((-0x1ad98 + 0x1a5c8 + 0x3f7d0 & _0x1aa067) >> 0x1f95 + -0x1c06 + -0x383), _0x3b8c55 += _0xacb410['length'] > _0x2d1a36 ? _0x2d8b28[_0x197e65(0x25c)]((-0x2447 + -0x1 * -0x1efe + 0x1509 & _0x1aa067) >> 0x1d66 + 0x2 * 0x202 + -0x2164) : _0x4dd4b8, _0x3b8c55 += _0x4dd4b8), _0x3b8c55; } function _0x389396(_0x49fdfd, _0x32eedf) { var _0x36dcb8 = _0x5612de; return w_0x5c3140(_0x36dcb8(0x3ad), { 0x0: arguments, 0x1: _0x49fdfd, 0x2: _0x32eedf }, this); } function _0x3262d3(_0xb01975) { var _0x55341a = _0x5612de; return _0x55341a(0x315)[_0x55341a(0x2c5)](_0xb01975); } function _0xb5350b(_0x1289f5) { var _0x341e09 = _0x5612de, _0x5b1849, _0x40ad1e, _0x341488, _0x5980c5, _0x31f759, _0x5bebd7 = ''; for (_0x5b1849 = 0x13d5 + -0xc4f * 0x1 + -0x786; _0x5b1849 < _0x1289f5[_0x341e09(0x259)] - (0xe50 * 0x1 + 0x17f3 + -0x2640); _0x5b1849 += -0xeed + 0x1 * 0x10c0 + -0x1cf * 0x1) _0x40ad1e = _0x3262d3(_0x1289f5[_0x341e09(0x25c)](_0x5b1849)), _0x341488 = _0x3262d3(_0x1289f5[_0x341e09(0x25c)](_0x5b1849 + (0x1103 + 0xa86 + 0x1 * -0x1b88))), _0x5980c5 = _0x3262d3(_0x1289f5[_0x341e09(0x25c)](_0x5b1849 + (-0x1c54 + -0xf15 + 0x2b6b))), _0x31f759 = _0x3262d3(_0x1289f5['charAt'](_0x5b1849 + (0xd1a + 0x1 * -0x175d + -0x5 * -0x20e))), _0x5bebd7 += String[_0x341e09(0x1e2)](_0x40ad1e << 0x33 * -0x3f + 0xe73 * -0x2 + 0x2975 | _0x341488 >>> 0x12d2 + 0x851 + -0x1b1f), '=' !== _0x1289f5[_0x341e09(0x25c)](_0x5b1849 + (0x51 * -0x25 + -0x21d * 0x12 + 0x31c1 * 0x1)) && (_0x5bebd7 += String['fromCharCode'](_0x341488 << -0xa0c + 0xee3 + -0x4d3 & 0x1 * 0x397 + -0x113f + 0xe98 | _0x5980c5 >>> 0x2161 + 0x1b7 * -0x5 + 0xc66 * -0x2 & 0x22bb + -0xf08 + 0x6 * -0x346)), '=' !== _0x1289f5[_0x341e09(0x25c)](_0x5b1849 + (0x125d * 0x2 + -0x3d * -0x31 + 0x146 * -0x26)) && (_0x5bebd7 += String[_0x341e09(0x1e2)](_0x5980c5 << -0x2650 + 0x17 * 0xe9 + -0x3 * -0x5cd & 0x754 + -0x5df + -0xb5 | _0x31f759)); return _0x5bebd7; } _0x6caf[_0x5612de(0x380)] = -0x14e * -0x2 + 0x2519 + -0x217 * 0x13, _0x6caf['msToken'] = '', _0x6caf[_0x5612de(0x263)] = _0x39693d[_0x5612de(0x3b9)], _0x6caf[_0x5612de(0x3b5)] = '', _0x6caf['ttwid'] = '', _0x6caf[_0x5612de(0x316)] = '', _0x6caf[_0x5612de(0x339)] = ''; var _0x28d239 = 0x778 + 0xa41 + 0x1 * -0x11b9, _0x2d6b72, _0x1e9bba, _0xdc7355, _0xf08186; function _0x33f406(_0x4b5fb5) { var _0x1d1da4 = _0x5612de; return _0x4b5fb5 &= -0x2632 + 0x3 * -0x591 + 0x3724, String[_0x1d1da4(0x1e2)](_0x4b5fb5 + (_0x4b5fb5 < 0x22f * 0x1 + -0x1cac + 0x3 * 0x8dd ? -0xd * 0x123 + 0x19ac + -0x552 * 0x2 : _0x4b5fb5 < -0x1 * -0x1646 + 0x1 * -0x1f21 + -0x305 * -0x3 ? -0x2b4 * 0x1 + -0x45 * 0xb + -0x2 * -0x2f9 : _0x4b5fb5 < 0x1 * 0x70c + 0x123f + -0x190d * 0x1 ? -(0x1f73 + -0x1 * -0x3d7 + -0x46 * 0x81) : -(-0x248f + 0x20d3 + 0x3cd))); } function _0x4da136(_0x620f42) { var _0xca40fa = _0x33f406; return _0xca40fa(_0x620f42 >> 0x420 * -0x4 + -0x53f * -0x4 + -0x464) + _0xca40fa(_0x620f42 >> -0x1492 + -0x11c3 + -0x3 * -0xccd) + _0xca40fa(_0x620f42 >> 0x1479 + 0x571 * 0x6 + -0x795 * 0x7) + _0xca40fa(_0x620f42 >> -0x1d * 0x4f + 0x313 * -0xb + 0x2aca) + _0xca40fa(_0x620f42); } _0x2d6b72 = _0x1e9bba = function (_0x5d2d39) { return _0x2d6b72 = _0xdc7355, _0x28d239 = _0x5d2d39, _0x4da136(_0x5d2d39 >> -0x418 * 0x8 + 0x9fe + 0x7c * 0x2f); } , _0xdc7355 = function (_0x135d9a) { _0x2d6b72 = _0xf08186; var _0x9cdabb = _0x28d239 << -0xa41 + -0x164f + 0x2b9 * 0xc | _0x135d9a >>> 0xee4 + 0x1 * -0x143 + 0xcd * -0x11; return _0x28d239 = _0x135d9a, _0x4da136(_0x9cdabb); } , _0xf08186 = function (_0x36c054) { return _0x2d6b72 = _0x1e9bba, _0x4da136(_0x28d239 << -0x302 + 0x1 * 0x2447 + -0x7 * 0x4bd | _0x36c054 >>> -0x1f5e + 0x1aa0 + 0x3d * 0x14) + _0x33f406(_0x36c054); } ; var _0x539097 = -0x2 * -0x621a4af5 + 0xc681287f + -0xec7e44b0, _0x12ada0; function _0x75d5b3(_0x5a0afa, _0x1ccbbd) { var _0x13780b = _0x5612de , _0xc312d4 = _0x5a0afa['length'] , _0x102845 = _0xc312d4 << -0x3 * -0x711 + 0x9a9 * 0x2 + -0x2883; if (_0x1ccbbd) { var _0x25438e = _0x5a0afa[_0xc312d4 - (0x1b18 + 0x1 * 0xfb6 + -0x1 * 0x2acd)]; if (_0x25438e < (_0x102845 -= 0x1f * 0x62 + -0xac0 + -0x8d * 0x2) - (-0xfed + -0x8a * 0x23 + 0x22ce) || _0x25438e > _0x102845) return null; _0x102845 = _0x25438e; } for (var _0x41e0a1 = -0x13b + -0x1963 + -0xd4f * -0x2; _0x41e0a1 < _0xc312d4; _0x41e0a1++) _0x5a0afa[_0x41e0a1] = String['fromCharCode'](-0x30 * -0x12 + -0x235b + -0x15 * -0x192 & _0x5a0afa[_0x41e0a1], _0x5a0afa[_0x41e0a1] >>> -0x1839 * -0x1 + -0x5 * -0x196 + 0x1 * -0x201f & -0x2 * 0x2b3 + 0x1 * 0x19c9 + -0x1364, _0x5a0afa[_0x41e0a1] >>> -0x12b * 0x1 + 0xa54 * 0x1 + -0x11 * 0x89 & 0xc36 * 0x2 + 0x2 * 0x1259 + 0x1 * -0x3c1f, _0x5a0afa[_0x41e0a1] >>> 0x564 + 0x2325 + -0x15 * 0x1ed & 0x2673 + -0x1411 + -0x1163); var _0x5ef85e = _0x5a0afa[_0x13780b(0x24f)](''); return _0x1ccbbd ? _0x5ef85e['substring'](0x3 * -0x388 + -0xe41 + 0x18d9, _0x102845) : _0x5ef85e; } function _0x183951(_0x15f4d9, _0x59200b) { var _0x4f373f = _0x5612de, _0x40761a, _0x6155ee = _0x15f4d9[_0x4f373f(0x259)], _0x21fe82 = _0x6155ee >> 0xb8d * -0x1 + -0x1 * 0x5fe + -0x1 * -0x118d; 0x4 * -0xbc + 0x12f0 + -0x1000 != (-0x79 * 0x2 + -0xb1 * 0x16 + 0x102b & _0x6155ee) && ++_0x21fe82, _0x59200b ? (_0x40761a = new Array(_0x21fe82 + (0x13 * -0x65 + -0x14ef + 0xfb * 0x1d)))[_0x21fe82] = _0x6155ee : _0x40761a = new Array(_0x21fe82); for (var _0x56bca4 = -0x9e0 + 0x51 * 0x1a + 0x1a6; _0x56bca4 < _0x6155ee; ++_0x56bca4) _0x40761a[_0x56bca4 >> -0xc1b * -0x2 + -0xc2 * -0x21 + 0x1 * -0x3136] |= _0x15f4d9['charCodeAt'](_0x56bca4) << ((0x3 * -0x4cd + -0xdfc + 0x1c66 & _0x56bca4) << 0x1 * -0x133b + 0x21a5 + 0xe67 * -0x1); return _0x40761a; } function _0x25c901(_0x431b9d) { return 0x1a1299587 + 0x91539 * -0x1c1b + -0x202b07 * -0x2ed & _0x431b9d; } function _0x38d33e(_0x1aed31, _0x212e8a, _0x427b15, _0x8fcd39, _0x4eaad6, _0x532b8e) { return (_0x427b15 >>> -0x1 * -0x1ddb + -0x20f7 + -0x3 * -0x10b ^ _0x212e8a << -0x1 * -0x407 + -0x132e + -0xf29 * -0x1) + (_0x212e8a >>> -0x828 + -0xf23 + -0x9d * -0x26 ^ _0x427b15 << -0xd51 + 0xc9f + 0xb6) ^ (_0x1aed31 ^ _0x212e8a) + (_0x532b8e[0x9ad + -0x763 + -0xb * 0x35 & _0x8fcd39 ^ _0x4eaad6] ^ _0x427b15); } function _0x231484(_0xefe811) { var _0x13eaf5 = _0x5612de; return _0xefe811[_0x13eaf5(0x259)] < -0xa13 + 0x1086 + -0x66f && (_0xefe811[_0x13eaf5(0x259)] = -0x1371 + -0x1ec1 * -0x1 + 0x5a6 * -0x2), _0xefe811; } function _0x3d58e7(_0x5e28f2, _0x468b52) { var _0x50c100 = _0x5612de, _0x7c35df, _0x4c97b3, _0x5e0270, _0x3034db, _0x56f817, _0x3084b8, _0x66df40 = _0x5e28f2[_0x50c100(0x259)], _0x5506f5 = _0x66df40 - (-0x1c76 + 0x2246 + -0x5cf); for (_0x4c97b3 = _0x5e28f2[_0x5506f5], _0x5e0270 = 0x22ee + 0xa15 * -0x1 + -0x18d9 * 0x1, _0x3084b8 = 0x1 * 0x36f + -0x2707 + 0x2398 | Math[_0x50c100(0x1cf)](0xc72 * 0x2 + -0xe3 * 0x2b + 0xd43 + (0x1a + 0x1c2a + -0x1c10) / _0x66df40); _0x3084b8 > 0x1 * -0x1cd + -0x2476 + 0x2643; --_0x3084b8) { for (_0x3034db = (_0x5e0270 = _0x25c901(_0x5e0270 + _0x539097)) >>> 0x3b * -0x2f + 0x1290 + 0x3 * -0x293 & 0x1533 + -0x116d + -0x3c3, _0x56f817 = 0x8 * 0x23b + -0xb89 + 0x64f * -0x1; _0x56f817 < _0x5506f5; ++_0x56f817) _0x7c35df = _0x5e28f2[_0x56f817 + (0x3b * 0x5f + -0x1e1 * 0x1 + -0x1403)], _0x4c97b3 = _0x5e28f2[_0x56f817] = _0x25c901(_0x5e28f2[_0x56f817] + _0x38d33e(_0x5e0270, _0x7c35df, _0x4c97b3, _0x56f817, _0x3034db, _0x468b52)); _0x7c35df = _0x5e28f2[-0x1570 + -0x1 * -0x385 + -0x8b * -0x21], _0x4c97b3 = _0x5e28f2[_0x5506f5] = _0x25c901(_0x5e28f2[_0x5506f5] + _0x38d33e(_0x5e0270, _0x7c35df, _0x4c97b3, _0x5506f5, _0x3034db, _0x468b52)); } return _0x5e28f2; } function _0x4aaf04(_0x33a7ba, _0x386196) { var _0x257c0b = _0x5612de, _0x379288, _0x247a8b, _0x334bb9, _0x2fa83f, _0x417a88, _0x2a3ae5 = _0x33a7ba[_0x257c0b(0x259)], _0x429a0d = _0x2a3ae5 - (0xc1 * -0x11 + 0x1f99 + -0xb * 0x1b5); for (_0x379288 = _0x33a7ba[-0x1 * -0x1397 + -0x13 * -0x187 + -0x309c], _0x334bb9 = _0x25c901(Math[_0x257c0b(0x1cf)](0xe52 * -0x1 + -0x10 * 0xb + -0x34 * -0x4a + (-0x3 * -0xd9 + 0x9f * 0x2f + 0xfc4 * -0x2) / _0x2a3ae5) * _0x539097); -0x2e * 0x1f + 0x2223 + -0x47 * 0x67 !== _0x334bb9; _0x334bb9 = _0x25c901(_0x334bb9 - _0x539097)) { for (_0x2fa83f = _0x334bb9 >>> 0x437 * 0x5 + -0xfd * 0x22 + -0xc89 * -0x1 & 0x1 * 0x863 + 0x2267 * 0x1 + -0x2f * 0xe9, _0x417a88 = _0x429a0d; _0x417a88 > 0xfc7 + -0x788 * -0x1 + -0x15f * 0x11; --_0x417a88) _0x247a8b = _0x33a7ba[_0x417a88 - (0x3b * -0x4f + 0x6d * -0xa + 0xb3c * 0x2)], _0x379288 = _0x33a7ba[_0x417a88] = _0x25c901(_0x33a7ba[_0x417a88] - _0x38d33e(_0x334bb9, _0x379288, _0x247a8b, _0x417a88, _0x2fa83f, _0x386196)); _0x247a8b = _0x33a7ba[_0x429a0d], _0x379288 = _0x33a7ba[-0x1fb + 0x1952 * -0x1 + -0x1d * -0xf1] = _0x25c901(_0x33a7ba[0x7 * 0x59 + 0x5 * -0x2d2 + 0xbab] - _0x38d33e(_0x334bb9, _0x379288, _0x247a8b, -0xbac + -0x7 * 0xea + -0x606 * -0x3, _0x2fa83f, _0x386196)); } return _0x33a7ba; } function _0x532596(_0x2394ed) { var _0x22f5e8 = _0x5612de; if (/^[\x00-\x7f]*$/[_0x22f5e8(0x22b)](_0x2394ed)) return _0x2394ed; for (var _0x47e840 = [], _0x5f04b8 = _0x2394ed[_0x22f5e8(0x259)], _0x150666 = -0xe97 + 0xd02 + 0x195, _0x19b157 = 0xf1c + -0x1f5 * 0x8 + 0x8c; _0x150666 < _0x5f04b8; ++_0x150666, ++_0x19b157) { var _0x44de53 = _0x2394ed[_0x22f5e8(0x195)](_0x150666); if (_0x44de53 < -0x1 * -0xb7b + 0x1566 + -0x2061) _0x47e840[_0x19b157] = _0x2394ed[_0x22f5e8(0x25c)](_0x150666); else { if (_0x44de53 < -0x3 * 0xcd2 + 0x1168 + 0x1 * 0x1d0e) _0x47e840[_0x19b157] = String['fromCharCode'](0x2 * 0x579 + 0x263c * 0x1 + 0x306e * -0x1 | _0x44de53 >> 0xca4 * -0x1 + 0x6f * 0x13 + -0x46d * -0x1, 0x7 * -0x5 + 0x35 * 0x73 + -0x172c | 0xf88 * -0x1 + -0x108f * -0x1 + -0xc8 & _0x44de53); else { if (!(_0x44de53 < -0xaebb * 0x1 + -0x3 * -0x3a46 + 0x6f7 * 0x1f || _0x44de53 > 0x1679b * 0x1 + -0x121 * -0x15 + -0x9f51)) { if (_0x150666 + (0x15a6 + 0x1ec1 + -0x26 * 0x161) < _0x5f04b8) { var _0x254ff6 = _0x2394ed[_0x22f5e8(0x195)](_0x150666 + (-0x42 * -0x7a + 0x4 * 0x91d + -0x43e7)); if (_0x44de53 < -0x181e4 + 0xbcbb + 0x1a129 && -0x17db * -0xf + 0xac6d * -0x1 + -0x3d8 * -0x9 <= _0x254ff6 && _0x254ff6 <= 0x159a + 0x17967 + 0x9b9 * -0x12) { var _0x304310 = -0x1862f + 0x1121d + 0x2 * 0xba09 + ((0x1 * -0x140f + -0x18dd + 0x30eb & _0x44de53) << -0xb0 * -0x8 + 0x875 + -0x7 * 0x1fd | 0x2301 + 0x171f + 0x3621 * -0x1 & _0x254ff6); _0x47e840[_0x19b157] = String[_0x22f5e8(0x1e2)](0x1afc * -0x1 + 0x26f4 + -0xb08 | _0x304310 >> 0xb * 0x14e + -0x119 * -0x1d + -0x2e1d & 0x1037 * 0x1 + -0x1090 + -0x2 * -0x4c, 0x1922 + 0x4 * 0x351 + -0x25e6 | _0x304310 >> 0x1e93 + -0x517 + -0x1970 & 0x2481 + 0x1591 + -0x39d3, -0xace * -0x1 + 0x1ef3 + -0x2941 | _0x304310 >> -0xb7f * -0x3 + -0x1 * 0xe7d + -0x13fa & -0x10d * -0x6 + 0x252a * 0x1 + -0x2b39, -0x1c61 * 0x1 + 0x1 * -0x5e7 + -0x13e * -0x1c | 0x13 * -0x1f9 + 0x16 * -0x164 + -0x37 * -0x13e & _0x304310), ++_0x150666; continue; } } throw new Error(_0x22f5e8(0x297)); } _0x47e840[_0x19b157] = String[_0x22f5e8(0x1e2)](-0x170b + 0x109b + 0x750 | _0x44de53 >> -0xbf0 * -0x2 + 0x25bc + -0x3d90, 0x4ec + 0x1f0c + -0x8de * 0x4 | _0x44de53 >> 0x2 * 0x829 + -0x1b * -0x10c + -0x2c90 & -0x2322 + 0x19d5 + 0x34 * 0x2f, 0x2a1 + 0x1 * 0x1ac0 + 0x1 * -0x1ce1 | -0xe95 + 0x1cf * 0x11 + 0x1 * -0xfeb & _0x44de53); } } } return _0x47e840[_0x22f5e8(0x24f)](''); } function _0x45fbef(_0x316827, _0x2a039f) { var _0x1c80ac = _0x5612de; for (var _0x4921e2 = new Array(_0x2a039f), _0x79a402 = 0x20bd + -0x10b1 + -0x1 * 0x100c, _0x28b895 = -0xd2c + -0x1730 + 0x1 * 0x245c, _0x2bc88b = _0x316827[_0x1c80ac(0x259)]; _0x79a402 < _0x2a039f && _0x28b895 < _0x2bc88b; _0x79a402++) { var _0x43b518 = _0x316827[_0x1c80ac(0x195)](_0x28b895++); switch (_0x43b518 >> -0x1342 + 0x67 * -0x53 + 0x34ab) { case -0x410 * -0x3 + -0xd9 + -0xb57: case -0x17cb + 0x2362 + -0xb96 * 0x1: case -0x24 * 0x7f + -0x1 * -0xcfc + 0x19 * 0x32: case 0x2025 + 0x1102 + -0x3124: case 0x1b0d + 0xcb4 + 0xd3f * -0x3: case -0x11d9 + 0x9 * 0x16d + -0x1 * -0x509: case 0x79 * 0x49 + 0x1 * 0x1b8e + -0x3e09 * 0x1: case 0x26c1 + -0x2137 * 0x1 + -0x583: _0x4921e2[_0x79a402] = _0x43b518; break; case 0x10f * -0x13 + 0x426 + 0x1003: case 0x164d * 0x1 + 0xb5a * 0x3 + 0x1c27 * -0x2: if (!(_0x28b895 < _0x2bc88b)) throw new Error('Unfinished\x20UTF-8\x20octet\x20sequence'); _0x4921e2[_0x79a402] = (-0x1f7 * 0xb + -0x1fa0 + 0x2ab * 0x14 & _0x43b518) << -0x233b + 0x160e + 0xd33 | -0x1e40 + 0x22b9 + -0x2 * 0x21d & _0x316827[_0x1c80ac(0x195)](_0x28b895++); break; case -0x2512 + -0x1595 + 0x3ab5: if (!(_0x28b895 + (0xba3 * -0x1 + -0x17dd * 0x1 + 0x1 * 0x2381) < _0x2bc88b)) throw new Error(_0x1c80ac(0x394)); _0x4921e2[_0x79a402] = (0x247f * -0x1 + 0xec2 * 0x1 + -0x136 * -0x12 & _0x43b518) << 0x1 * 0x1bcd + -0x3dd + -0x17e4 | (0x1631 + -0x1058 + 0x2 * -0x2cd & _0x316827[_0x1c80ac(0x195)](_0x28b895++)) << 0x889 * 0x4 + 0x11c8 * 0x2 + -0x45ae | -0x698 * -0x4 + -0x2f * -0x4f + -0x28a2 & _0x316827[_0x1c80ac(0x195)](_0x28b895++); break; case -0x85 * -0x8 + 0x1148 + -0x1561: if (!(_0x28b895 + (0x1 * -0x8f1 + -0x67 * 0x3e + 0x21e5) < _0x2bc88b)) throw new Error(_0x1c80ac(0x394)); var _0x1740ac = ((0x1 * -0x1d4f + 0x1 * 0x24bf + -0x769 & _0x43b518) << 0xb19 + 0x4a * -0x48 + -0x3 * -0x343 | (-0x1ab4 + -0x205 * -0x8 + -0x3 * -0x399 & _0x316827[_0x1c80ac(0x195)](_0x28b895++)) << 0x1e52 + -0x144b + -0x5 * 0x1ff | (0x4c5 * -0x3 + 0x1 * -0x70b + 0x1599 & _0x316827[_0x1c80ac(0x195)](_0x28b895++)) << -0xf6e + -0x37 * -0x86 + 0x239 * -0x6 | 0x2 * -0xc31 + 0x26b5 + -0xe14 & _0x316827[_0x1c80ac(0x195)](_0x28b895++)) - (0x1 * -0x6b6b + -0x14502 + -0x1 * -0x2b06d); if (!(0x86 * 0xd + -0x2210 + 0x1b42 <= _0x1740ac && _0x1740ac <= 0x141d1 * 0x1 + -0x3044 * -0x8b + 0x1 * -0xb76be)) throw new Error(_0x1c80ac(0x24e) + _0x1740ac['toString'](-0x959 * 0x3 + -0x50e * -0x5 + -0x1 * -0x2d5)); _0x4921e2[_0x79a402++] = _0x1740ac >> -0x1a89 * 0x1 + 0x125 * -0x1 + 0x1bb8 & -0x2 * 0x12e4 + 0xc1e + 0x1da9 | -0x4e * -0x332 + -0x6764 * -0x2 + -0xf004 * 0x1, _0x4921e2[_0x79a402] = 0x23f0 + 0x2b * 0x11 + -0x4 * 0x8b3 & _0x1740ac | 0xb45d + -0xd352 + 0x3 * 0x53a7; break; default: throw new Error(_0x1c80ac(0x330) + _0x43b518[_0x1c80ac(0x3ae)](-0x1f71 + 0xdc3 * -0x1 + 0x2d44)); } } return _0x79a402 < _0x2a039f && (_0x4921e2[_0x1c80ac(0x259)] = _0x79a402), String[_0x1c80ac(0x1e2)]['apply'](String, _0x4921e2); } function _0x25bfe1(_0x5ad020, _0x1001f1) { var _0xa8facb = _0x5612de; for (var _0xdb2671 = [], _0x67f891 = new Array(-0x29 * 0x169 + 0x1d * -0x3c7 + -0x6 * -0x313a), _0x3fce02 = -0x47c * -0x1 + -0xc83 + 0x2ad * 0x3, _0x47254b = 0x1010 + -0xb * 0x29 + -0xe4d, _0x4445a9 = _0x5ad020[_0xa8facb(0x259)]; _0x3fce02 < _0x1001f1 && _0x47254b < _0x4445a9; _0x3fce02++) { var _0x488af6 = _0x5ad020['charCodeAt'](_0x47254b++); switch (_0x488af6 >> -0x1148 + 0xbd2 + 0x57a) { case -0x1 * -0xb6b + 0x16f6 + -0x2261: case -0x1678 + -0x1c12 + 0x3 * 0x10d9: case 0xd01 * -0x2 + 0x2c2 + -0x1a * -0xe5: case 0x1985 + 0x49 * -0x6d + 0x593: case 0xbac + 0xd1a * 0x1 + -0x18c2: case -0x11 * -0x1 + 0x1 * -0x18bd + 0x18b1: case 0x1e6 + -0x129a * -0x1 + 0x1 * -0x147a: case -0x1 * 0xf49 + 0xed + 0xe63: _0x67f891[_0x3fce02] = _0x488af6; break; case 0x30e * 0xb + 0x255b + -0x1 * 0x46e9: case 0x71 * -0xf + -0x1371 + 0x1a1d: if (!(_0x47254b < _0x4445a9)) throw new Error(_0xa8facb(0x394)); _0x67f891[_0x3fce02] = (-0x407 * -0x5 + -0xcb5 + 0x74f * -0x1 & _0x488af6) << -0x585 + -0xbf8 + 0x1183 | -0x1 * 0x1e25 + 0x6d * -0x3b + 0x1281 * 0x3 & _0x5ad020['charCodeAt'](_0x47254b++); break; case -0x676 * -0x4 + -0x351 + -0x1679: if (!(_0x47254b + (0x1b63 + -0x5 * 0x463 + -0x573) < _0x4445a9)) throw new Error(_0xa8facb(0x394)); _0x67f891[_0x3fce02] = (0x2dd * 0x7 + -0x15dd + 0x1e1 & _0x488af6) << 0x141c + 0x18e3 + -0x2cf3 | (-0x1 * -0x619 + 0x1 * 0x18bf + -0xa33 * 0x3 & _0x5ad020[_0xa8facb(0x195)](_0x47254b++)) << 0x1343 * -0x1 + -0x9 * 0x198 + 0x21a1 | 0x455 + -0x1ef3 + 0x1add & _0x5ad020['charCodeAt'](_0x47254b++); break; case -0x21f + -0x13f9 * 0x1 + -0x6b * -0x35: if (!(_0x47254b + (-0xde * -0xb + 0x20cf + -0x2a57) < _0x4445a9)) throw new Error(_0xa8facb(0x394)); var _0x59357e = ((0x1081 + -0x1b6f + 0xaf5 & _0x488af6) << -0x1 * -0x511 + -0x45 * 0x55 + 0x11ea | (0x125a * 0x2 + 0x4bd * -0x2 + -0x1afb & _0x5ad020[_0xa8facb(0x195)](_0x47254b++)) << 0xbf4 * -0x2 + -0x1512 + 0x2d06 | (0x1aae + 0xd81 + -0x27f0 & _0x5ad020[_0xa8facb(0x195)](_0x47254b++)) << 0x1759 + -0x9a1 * -0x1 + 0x4c * -0x6f | 0x352 * -0xb + -0xde7 + 0x32ac & _0x5ad020[_0xa8facb(0x195)](_0x47254b++)) - (-0x57e9 + 0x19207 + -0x3a1e); if (!(0x41b * -0x7 + 0x1658 + 0x1 * 0x665 <= _0x59357e && _0x59357e <= -0xf6 * -0x7ed + -0x1bcf3b + -0x4 * -0x90c5f)) throw new Error('Character\x20outside\x20valid\x20Unicode\x20range:\x200x' + _0x59357e['toString'](-0x40f + -0x5 * -0x61f + 0x1c4 * -0xf)); _0x67f891[_0x3fce02++] = _0x59357e >> -0x15e2 + 0x1 * -0xb51 + -0x43 * -0x7f & -0x11a2 + 0xb * -0x29 + 0x7cc * 0x3 | 0x1 * -0x3d14 + 0x159a9 + -0x1 * 0x4495, _0x67f891[_0x3fce02] = 0x1 * 0x101 + -0x499 + 0x797 & _0x59357e | 0x1dec + 0x87f7 + 0x361d; break; default: throw new Error('Bad\x20UTF-8\x20encoding\x200x' + _0x488af6[_0xa8facb(0x3ae)](-0x11 * 0x1dc + 0x1a53 + 0x559)); } if (_0x3fce02 >= -0xb3 * -0x16e + 0x1616 + 0x1a6 * -0x5b) { var _0x2ceb4e = _0x3fce02 + (0x4 * -0x8fe + 0x665 * 0x3 + 0x10ca); _0x67f891[_0xa8facb(0x259)] = _0x2ceb4e, _0xdb2671[_0xdb2671[_0xa8facb(0x259)]] = String[_0xa8facb(0x1e2)][_0xa8facb(0x207)](String, _0x67f891), _0x1001f1 -= _0x2ceb4e, _0x3fce02 = -(-0x18 * -0x111 + 0x2 * 0x9e3 + -0x2d5d); } } return _0x3fce02 > 0x1 * 0xb5d + -0x20a3 + 0x1546 && (_0x67f891['length'] = _0x3fce02, _0xdb2671[_0xdb2671[_0xa8facb(0x259)]] = String['fromCharCode'][_0xa8facb(0x207)](String, _0x67f891)), _0xdb2671['join'](''); } function _0x31dfb5(_0x360a73, _0xe765ba) { var _0x293f34 = _0x5612de; return (null == _0xe765ba || _0xe765ba < -0x1605 + 0xf7 * 0xe + 0x883) && (_0xe765ba = _0x360a73[_0x293f34(0x259)]), -0x6 * 0x30b + -0x1608 + 0x284a === _0xe765ba ? '' : /^[\x00-\x7f]*$/[_0x293f34(0x22b)](_0x360a73) || !/^[\x00-\xff]*$/[_0x293f34(0x22b)](_0x360a73) ? _0xe765ba === _0x360a73[_0x293f34(0x259)] ? _0x360a73 : _0x360a73[_0x293f34(0x3a7)](0x169 + 0x887 + 0x6a * -0x18, _0xe765ba) : _0xe765ba < 0x1d0d6 + 0x73e5 + -0x4 * 0x512f ? _0x45fbef(_0x360a73, _0xe765ba) : _0x25bfe1(_0x360a73, _0xe765ba); } function _0xdda738(_0x21d8ce, _0x4bf3f9) { var _0x46d91f = _0x5612de; return null == _0x21d8ce || 0x1 * -0xab1 + -0x2567 * -0x1 + -0x1ab6 === _0x21d8ce[_0x46d91f(0x259)] ? _0x21d8ce : (_0x21d8ce = _0x532596(_0x21d8ce), _0x4bf3f9 = _0x532596(_0x4bf3f9), _0x75d5b3(_0x3d58e7(_0x183951(_0x21d8ce, !(0xad5 + 0xaa1 + -0x2 * 0xabb)), _0x231484(_0x183951(_0x4bf3f9, !(-0x106c + -0x1173 * -0x2 + -0x1279)))), !(-0x1 * 0x2c8 + -0xc8 + 0x391))); } function _0x4bb829(_0x1f763e, _0x47e9cd) { var _0x18b798 = _0x5612de; return null == _0x1f763e || -0x12 * 0xe1 + 0x3cb * -0x5 + 0x22c9 === _0x1f763e[_0x18b798(0x259)] ? _0x1f763e : (_0x47e9cd = _0x532596(_0x47e9cd), _0x31dfb5(_0x75d5b3(_0x4aaf04(_0x183951(_0x1f763e, !(-0x40 * -0x16 + -0x133 * -0x1d + -0x2846)), _0x231484(_0x183951(_0x47e9cd, !(0x1e64 + 0xcee + -0x2b51)))), !(-0x1daa + -0xbb1 + 0x295b)))); } function _0x39dfe4() { var _0x2fd4da = _0x5612de , _0x21e994 = ''; try { window[_0x2fd4da(0x3be)] && (_0x21e994 = window[_0x2fd4da(0x3be)]['getItem']('_byted_param_sw')), _0x21e994 && !window[_0x2fd4da(0x1dc)] || (_0x21e994 = window['localStorage'][_0x2fd4da(0x2d8)]('_byted_param_sw')); } catch (_0x3b8cd9) { } if (_0x21e994) try { var _0x25acb5 = _0x4bb829(_0xb5350b(_0x21e994[_0x2fd4da(0x2a5)](0x19 * 0xc9 + -0x1bc8 + -0x82f * -0x1)), _0x21e994[_0x2fd4da(0x2a5)](0x2 * 0xa88 + -0x25 * -0x94 + -0x2a74, 0x2c6 * 0x5 + -0x14c6 + 0x1bc * 0x4)); if ('on' === _0x25acb5) return !(0x527 * -0x7 + 0x7b7 + 0x1c5a); if (_0x2fd4da(0x304) === _0x25acb5) return !(0x1b29 * -0x1 + -0xd8d + 0x1 * 0x28b7); } catch (_0x1da1e4) { } return !(-0x158c + 0x10 * -0x13d + 0x1 * 0x295d); } function _0x1b4bf1() { var _0x4eb990 = _0x5612de; return w_0x5c3140(_0x4eb990(0x211), { get 0x0() { var _0x448d97 = _0x4eb990; return _0x448d97(0x384) != typeof navigator ? navigator : void (-0xd95 * -0x2 + -0x1 * 0x16ed + 0x5 * -0xd9); }, get 0x1() { var _0x389269 = _0x4eb990; return _0x389269(0x384) != typeof window ? window : void (0x63e + 0xb * -0x32b + -0x1c9b * -0x1); }, get 0x2() { return _0x1db123; }, 0x3: Object, get 0x4() { return 'undefined' != typeof document ? document : void (0xe3a + 0x2499 + -0x32d3); }, get 0x5() { var _0x813809 = _0x4eb990; return _0x813809(0x384) != typeof location ? location : void (-0x1 * -0x2145 + 0x54e * -0x7 + -0x1 * -0x3dd); }, get 0x6() { return _0x176a57; }, get 0x7() { return 'undefined' != typeof history ? history : void (-0x17d + -0x1ef4 + 0x2071); }, 0x8: arguments }, this); } function _0x532cd9() { var _0x3b3ba1 = _0x5612de; return w_0x5c3140(_0x3b3ba1(0x190), { get 0x0() { return _0x176a57; }, get 0x1() { return navigator; }, get 0x2() { return PluginArray; }, get 0x3() { return window; }, 0x4: arguments }, this); } function _0x3391fc() { var _0x2aea9b = _0x5612de; return w_0x5c3140(_0x2aea9b(0x266), { get 0x0() { return _0x12ada0; }, get 0x1() { return navigator; }, 0x2: Object, get 0x3() { return window; }, 0x4: arguments, 0x5: RegExp }, this); } function _0x21fa28() { var _0x1106df = _0x5612de; return w_0x5c3140(_0x1106df(0x17b), { set 0x0(_0x2b3a0b) { _0x12ada0 = _0x2b3a0b; }, 0x1: Object, get 0x2() { return window; }, 0x3: arguments }, this); } function _0x49b1d7(_0x4f4807) { var _0x4f1753 = _0x5612de; return w_0x5c3140(_0x4f1753(0x171), { get 0x0() { return _0x1230e7; }, get 0x1() { return indexedDB; }, get 0x2() { return _0xf8ccf1; }, get 0x3() { return window; }, get 0x4() { return DOMException; }, get 0x5() { return _0x176a57; }, get 0x6() { return _0x6caf; }, 0x7: arguments, 0x8: _0x4f4807 }, this); } function _0x462256() { var _0x1ed1aa = _0x5612de; return w_0x5c3140(_0x1ed1aa(0x163), { get 0x0() { return _0x176a57; }, get 0x1() { return document; }, get 0x2() { return navigator; }, 0x3: arguments, 0x4: RegExp }, this); } function _0x3cee0e() { var _0x5f1e22 = _0x5612de; return w_0x5c3140(_0x5f1e22(0x2dd), { get 0x0() { return navigator; }, get 0x1() { return window; }, 0x2: arguments, 0x3: RegExp }, this); } function _0x1f9824() { var _0x1d4f72 = _0x5612de , _0x5821af = ''; if (_0x6caf['PLUGIN']) _0x5821af = _0x6caf[_0x1d4f72(0x354)]; else { for (var _0x580f96 = [], _0x3ca683 = navigator[_0x1d4f72(0x1ba)] || [], _0x15f771 = 0x389 * 0x5 + -0x2a * -0xe2 + -0x36c1; _0x15f771 < -0xe3b * 0x1 + 0x1a76 + -0x6 * 0x209; _0x15f771++) try { for (var _0x5a4f6c = _0x3ca683[_0x15f771], _0x3a1f6 = [], _0x1a6da9 = -0x869 + -0x111 * 0x14 + -0x1dbd * -0x1; _0x1a6da9 < _0x5a4f6c[_0x1d4f72(0x259)]; _0x1a6da9++) _0x5a4f6c[_0x1d4f72(0x37c)](_0x1a6da9) && _0x3a1f6[_0x1d4f72(0x36e)](_0x5a4f6c['item'](_0x1a6da9)[_0x1d4f72(0x1ab)]); var _0x1ae7c8 = _0x5a4f6c[_0x1d4f72(0x341)] + ''; _0x5a4f6c['version'] && (_0x1ae7c8 += _0x5a4f6c[_0x1d4f72(0x2c1)] + ''), _0x1ae7c8 += _0x5a4f6c['filename'] + '', _0x1ae7c8 += _0x3a1f6[_0x1d4f72(0x24f)](''), _0x580f96['push'](_0x1ae7c8); } catch (_0x4e31c6) { } _0x5821af = _0x580f96[_0x1d4f72(0x24f)]('##'), _0x6caf[_0x1d4f72(0x354)] = _0x5821af; } return _0x5821af[_0x1d4f72(0x2a5)](-0x1 * 0x26c + -0x1e2f + 0x209b, -0x26ff + 0x1b6e * 0x1 + 0xf91); } function _0x30412e() { var _0x5a747f = _0x5612de , _0x5640f8 = []; try { var _0x3405cb = navigator[_0x5a747f(0x1ba)]; if (_0x3405cb) { for (var _0x53b06e = -0x8aa + 0x341 + 0x115 * 0x5; _0x53b06e < _0x3405cb[_0x5a747f(0x259)]; _0x53b06e++) for (var _0xa74ec6 = -0x1829 + 0x1f9c + -0x773; _0xa74ec6 < _0x3405cb[_0x53b06e][_0x5a747f(0x259)]; _0xa74ec6++) { var _0x2bcc6c = [_0x3405cb[_0x53b06e][_0x5a747f(0x19e)], _0x3405cb[_0x53b06e][_0xa74ec6][_0x5a747f(0x1ab)], _0x3405cb[_0x53b06e][_0xa74ec6][_0x5a747f(0x169)]][_0x5a747f(0x24f)]('|'); _0x5640f8['push'](_0x2bcc6c); } } } catch (_0x11c0f4) { } return _0x5640f8; } function _0x28e2ec() { var _0x3301ae = _0x5612de; return w_0x5c3140(_0x3301ae(0x1a6), { get 0x0() { return navigator; }, get 0x1() { return _0x1f9824; }, get 0x2() { return window; }, 0x3: arguments }, this); } function _0x5863d1() { var _0x247350 = _0x5612de; return w_0x5c3140(_0x247350(0x21c), { get 0x0() { return _0x462335; }, get 0x1() { return _0x39dfe4; }, get 0x2() { return _0x1b4bf1; }, get 0x3() { return _0x53b77d; }, get 0x4() { return _0x49b1d7; }, get 0x5() { return _0x3ed707; }, get 0x6() { return _0x532cd9; }, get 0x7() { return _0x3391fc; }, get 0x8() { return _0x462256; }, get 0x9() { return _0x3cee0e; }, get 0xa() { return _0x28e2ec; }, get 0xb() { return _0x6caf; }, 0xc: arguments }, this); } function _0x2a900b(_0x29489b) { var _0x3163ac = _0x5612de; for (var _0x2edc36 = Object[_0x3163ac(0x17f)](_0x29489b), _0x4da10f = -0x26c9 + -0x11f1 * -0x1 + 0x14d8, _0x26fc90 = _0x2edc36[_0x3163ac(0x259)] - (0x1cea + -0x5 * 0x2f5 + 0x8 * -0x1c4); _0x26fc90 >= -0x2 * -0x397 + 0x17ce + -0x1efc; _0x26fc90--) { _0x4da10f = (_0x29489b[_0x2edc36[_0x26fc90]] ? 0x455 * 0x9 + 0x1715 + -0x3e11 : -0x1 * -0x22f7 + 0x1f6c * 0x1 + -0x203 * 0x21) << _0x2edc36[_0x3163ac(0x259)] - _0x26fc90 - (-0x64c * -0x6 + -0x163 + -0x224 * 0x11) | _0x4da10f; } return _0x4da10f; } function _0x246aeb(_0x1b02ae, _0x32abc9) { var _0x172517 = _0x5612de; for (var _0x4d2e6c = 0x1e44 + 0x2 * 0x443 + 0x2 * -0x1365; _0x4d2e6c < _0x32abc9[_0x172517(0x259)]; _0x4d2e6c++) _0x1b02ae = (-0xa15f + -0x1a803 + 0x349a1) * _0x1b02ae + _0x32abc9[_0x172517(0x195)](_0x4d2e6c) >>> -0x2c6 + 0x2 * -0x126c + 0x279e; return _0x1b02ae; } function _0x184783(_0x9867bc, _0x21d0e6) { var _0x25b989 = _0x5612de; for (var _0x21703e = -0xa6 * -0x1 + 0xbf * -0x5 + -0x315 * -0x1; _0x21703e < _0x21d0e6[_0x25b989(0x259)]; _0x21703e++) _0x9867bc = (-0x6275 + 0xd966 + -0x5f * -0x172) * (_0x9867bc ^ _0x21d0e6['charCodeAt'](_0x21703e)) >>> -0xf * 0x1a + 0x7 * -0x543 + 0x443 * 0x9; return _0x9867bc; } function _0xf119da(_0x57529f, _0x19340f) { var _0x1de06d = _0x5612de; for (var _0x3e7b02 = 0x45a * 0x1 + -0xe5d * 0x2 + 0x1860; _0x3e7b02 < _0x19340f[_0x1de06d(0x259)]; _0x3e7b02++) { var _0x25af5d = _0x19340f[_0x1de06d(0x195)](_0x3e7b02); if (_0x25af5d >= -0x12321 + 0x417 * -0xa + 0x22407 && _0x25af5d <= 0x92d4 + 0xb6fd + -0x6dd2 && _0x3e7b02 < _0x19340f[_0x1de06d(0x259)]) { var _0xfb40a0 = _0x19340f['charCodeAt'](_0x3e7b02 + (0x688 * 0x4 + 0x145 * 0x17 + -0x3752)); -0xa62a + -0x10ba7 + -0x5d67 * -0x7 == (0xfbaf + 0x1ce63 + -0x1ce12 & _0xfb40a0) && (_0x25af5d = ((-0x9fe * 0x1 + 0x2 * -0xf33 + 0x2c63 & _0x25af5d) << -0x86d * 0x4 + -0x211 * 0x9 + -0x1 * -0x3457) + (-0x1b17 + 0xb6c + 0x13aa & _0xfb40a0) + (0x2 * 0xab9c + -0x18f24 + -0x4 * -0x4dfb), _0x3e7b02 += -0x1e31 + -0xa * 0x38b + -0x150 * -0x32); } _0x57529f = (-0x3a * 0x7e4 + -0x3827 * -0x2 + 0x151 * 0x1c9) * _0x57529f + _0x25af5d >>> 0x3e7 + -0x1 * -0x5e7 + -0x9ce; } return _0x57529f; } function _0x53f850(_0x450920) { var _0x30ac3f = _0x5612de , _0x4eeea4 = _0x450920 || ''; return _0x4eeea4 = (_0x4eeea4 = -(-0x13 * -0x5a + 0xb2f + -0x11dc) !== (_0x4eeea4 = _0x4eeea4[_0x30ac3f(0x377)](/(http:\/\/|https:\/\/|\/\/)?[^\/]*/, ''))['indexOf']('?') ? _0x4eeea4[_0x30ac3f(0x3a7)](-0x819 * 0x3 + 0x1d1c + -0x9 * 0x89, _0x4eeea4[_0x30ac3f(0x2c5)]('?')) : _0x4eeea4) || '/'; } function _0x446110(_0xc80785) { var _0x5bf664 = _0x5612de , _0x471548 = _0xc80785 || '' , _0x2cf363 = _0x471548[_0x5bf664(0x1c2)](/[?](\w+=.*&?)*/) , _0x137237 = (_0x471548 = _0x2cf363 ? _0x2cf363[-0x2292 + -0x4 * 0x520 + 0x173 * 0x26][_0x5bf664(0x3a7)](0x112 * 0x11 + 0x61 + -0x2 * 0x949) : '') ? _0x471548[_0x5bf664(0x342)]('&') : null , _0x290269 = {}; if (_0x137237) { for (var _0x15cd4c = 0xd54 + -0x49f + 0x2e7 * -0x3; _0x15cd4c < _0x137237[_0x5bf664(0x259)]; _0x15cd4c++) _0x290269[_0x137237[_0x15cd4c]['split']('=')[0x65c + 0x3f8 * -0x2 + 0x194]] = _0x137237[_0x15cd4c][_0x5bf664(0x342)]('=')[0x15bb * -0x1 + -0x50b * 0x4 + 0x29e8]; } return _0x290269; } function _0x3a1cf3(_0x8af04, _0xe4b509) { var _0x28f81c = _0x5612de; if (!_0x8af04 || '{}' === JSON[_0x28f81c(0x1e6)](_0x8af04)) return {}; for (var _0x257ee8 = Object[_0x28f81c(0x17f)](_0x8af04)['sort'](), _0x24c84a = {}, _0x4bfbcd = -0x1 * 0x21fb + -0x25 * -0x16 + 0x1ecd; _0x4bfbcd < _0x257ee8[_0x28f81c(0x259)]; _0x4bfbcd++) _0x24c84a[_0x257ee8[_0x4bfbcd]] = _0xe4b509 ? _0x8af04[_0x257ee8[_0x4bfbcd]] + '' : _0x8af04[_0x257ee8[_0x4bfbcd]]; return _0x24c84a; } function _0x20b77a(_0x3745f1) { var _0x211da5 = _0x5612de; return Array[_0x211da5(0x2af)](_0x3745f1) ? _0x3745f1['map'](_0x20b77a) : _0x3745f1 instanceof Object ? Object[_0x211da5(0x17f)](_0x3745f1)[_0x211da5(0x38e)]()[_0x211da5(0x376)](function (_0x4b45b3, _0x5188a8) { return _0x4b45b3[_0x5188a8] = _0x20b77a(_0x3745f1[_0x5188a8]), _0x4b45b3; }, {}) : _0x3745f1; } function _0x483e03(_0x44c650) { var _0x498394 = _0x5612de; if (!_0x44c650 || '{}' === JSON[_0x498394(0x1e6)](_0x44c650)) return ''; for (var _0x52341a = Object['keys'](_0x44c650)['sort'](), _0x3c151a = '', _0x4f5c3f = -0xefc + 0x25ba + 0x29 * -0x8e; _0x4f5c3f < _0x52341a[_0x498394(0x259)]; _0x4f5c3f++) _0x3c151a += [_0x52341a[_0x4f5c3f]] + '=' + _0x44c650[_0x52341a[_0x4f5c3f]] + '&'; return _0x3c151a; } function _0x4bae98() { var _0x33d77a = _0x5612de; try { return !!window[_0x33d77a(0x3be)]; } catch (_0x2002b2) { return !(0xcab + -0xcff + 0x2 * 0x2a); } } function _0x275e5a() { try { return !!window['localStorage']; } catch (_0x2df8fe) { return !(-0x1ff * -0xd + 0x1cf1 * -0x1 + -0x17f * -0x2); } } function _0x4fdb47() { var _0x34b6b1 = _0x5612de; try { return !!window[_0x34b6b1(0x218)]; } catch (_0x3b6071) { return !(0xedb * -0x1 + 0x41b * -0x5 + -0x2 * -0x11b1); } } function _0x1afbc2() { return _0x5af46a(_0x4fdb47()) + _0x5af46a(_0x275e5a()) + _0x5af46a(_0x4bae98()); } function _0xc6f828(_0x23a724) { var _0x198ea0 = _0x5612de, _0x112670, _0x256bde = document[_0x198ea0(0x260)](_0x198ea0(0x24d)); _0x256bde[_0x198ea0(0x302)] = 0x1813 * -0x1 + 0x1f94 + 0x751 * -0x1, _0x256bde[_0x198ea0(0x245)] = -0x205f + 0x4f6 + -0x1b79 * -0x1; var _0xa760ad = _0x256bde['getContext']('2d'); _0xa760ad[_0x198ea0(0x18f)] = '14px\x20serif', _0xa760ad[_0x198ea0(0x196)]('龘ฑภ경', 0x2162 * 0x1 + 0xbbb + -0x2d1b, -0x1 * -0x138a + -0x164e + 0x90 * 0x5), _0xa760ad[_0x198ea0(0x3a0)] = -0xa67 + 0x7c * 0x39 + 0x103 * -0x11, _0xa760ad[_0x198ea0(0x268)] = 0x22cf * 0x1 + -0x4d3 + 0x5 * -0x5ff, _0xa760ad[_0x198ea0(0x340)] = _0x198ea0(0x21b), _0xa760ad['arc'](-0x1 * -0x5dd + -0x6de + 0x109, -0x336 * 0x4 + -0x167a + 0x712 * 0x5, -0x7c8 + 0xb80 + -0x3b * 0x10, 0x411 + 0x38 * -0xaa + 0x3d * 0x8b, 0x7f * -0x20 + 0x11bb + -0x1d9), _0xa760ad['stroke'](), _0x112670 = _0x256bde[_0x198ea0(0x239)](); for (var _0x367ff8 = 0xfa7 * -0x1 + -0x53 * 0x6d + 0x32fe; _0x367ff8 < 0x17b + -0x1b0e * 0x1 + 0x2b * 0x99; _0x367ff8++) _0x23a724 = (0x1675 * -0xc + -0xe91a + 0x2f6d5) * _0x23a724 + _0x112670['charCodeAt'](_0x23a724 % _0x112670[_0x198ea0(0x259)]) >>> -0x44 + 0xfa0 * -0x2 + -0x1f84 * -0x1; return _0x23a724; } var _0x37a93a = -0x1b3b + 0x2136 + -0x1 * 0x5fb; function _0x18b4be() { var _0x4d1378 = _0x5612de; try { return _0x37a93a || (_0x462335[_0x4d1378(0x219)] ? -(-0x1 * 0x19d5 + -0x1e5d + 0x3833) : _0x37a93a = _0xc6f828(0x15 * 0x144d79ba + 0x8831fd63 + -0x153df3ab6)); } catch (_0x3c9d0d) { return -(0x1e58 + -0x26 * -0xcb + 0x3c79 * -0x1); } } function _0x1a39c4() { if (_0x37a93a) return _0x37a93a; _0x37a93a = _0xc6f828(-0x361cdc2a * -0x5 + 0x17c5 * 0xb369b + -0x2f7 * 0x6a0ca6); } var _0x45ece5 = { 'fpProfileUrl': _0x5612de(0x172) }; function _0x130155() { var _0x5e9262 = _0x5612de , _0x380346 = window[_0x5e9262(0x328)]; return _0x380346[_0x5e9262(0x302)] + '_' + _0x380346[_0x5e9262(0x245)] + '_' + _0x380346[_0x5e9262(0x17e)]; } function _0x2a76f8() { var _0x1d2b79 = _0x5612de , _0xa8cb6a = window['screen']; return _0xa8cb6a[_0x1d2b79(0x38a)] + '_' + _0xa8cb6a[_0x1d2b79(0x276)]; } function _0x3da279() { return new Promise(function (_0x2a10ed) { var _0x7d3f27 = w_0x25f3; if (_0x7d3f27(0x1b2) in navigator) try { navigator['getBattery']()[_0x7d3f27(0x1ed)](function (_0x13ff1d) { var _0x479b99 = _0x7d3f27; _0x2a10ed(_0x13ff1d[_0x479b99(0x3ac)] + '_' + _0x13ff1d[_0x479b99(0x2b0)] + '_' + _0x13ff1d[_0x479b99(0x353)] + '_' + _0x13ff1d[_0x479b99(0x213)]); }); } catch (_0x2c28b8) { _0x2a10ed(''); } else _0x2a10ed(''); } ); } var _0x47ec2a = {}; function _0x299f3a() { var _0x2531b6 = _0x5612de, _0x1a8a34, _0x2e0843 = _0x2531b6(0x33b), _0x29ea20 = -0x12a4 + 0x30b + 0xf99; void (-0x2b * 0xa + -0x1d8d + 0x1f3b) !== navigator[_0x2e0843] && (_0x29ea20 = navigator[_0x2e0843]); try { document[_0x2531b6(0x188)](_0x2531b6(0x240)), _0x1a8a34 = !(-0x1f61 + 0x19 * 0x22 + 0x1c0f); } catch (_0x3992e0) { _0x1a8a34 = !(0x17 * -0x5d + -0x1 * 0x209 + 0x377 * 0x3); } var _0x11190a = _0x2531b6(0x1ea) in window; return Object[_0x2531b6(0x2a0)](_0x47ec2a, { 'maxTouchPoints': _0x29ea20, 'touchEvent': _0x1a8a34, 'touchStart': _0x11190a }), _0x29ea20 + '_' + _0x1a8a34 + '_' + _0x11190a; } function _0xbe842b() { return _0x47ec2a; } function _0x4649a1() { var _0x3b6f20 = _0x5612de , _0x4256a7 = new Date(); _0x4256a7[_0x3b6f20(0x22d)](-0x1477 + 0xde4 * -0x1 + 0xb74 * 0x3), _0x4256a7[_0x3b6f20(0x294)](-0x360 + -0x263d + 0x29a2); var _0x213a03 = -_0x4256a7[_0x3b6f20(0x237)](); _0x4256a7[_0x3b6f20(0x294)](-0x9b5 + 0x5 * 0x77e + -0x2 * 0xddb); var _0x5a2621 = -_0x4256a7[_0x3b6f20(0x237)](); return Math['min'](_0x213a03, _0x5a2621); } function _0x487576() { var _0x5557a1 = _0x5612de; if (_0x6caf[_0x5557a1(0x2db)]) return _0x6caf[_0x5557a1(0x2db)]; try { var _0x4f28ec = document[_0x5557a1(0x260)](_0x5557a1(0x24d))[_0x5557a1(0x2f9)]('webgl') , _0x36473d = _0x4f28ec['getExtension'](_0x5557a1(0x375)) , _0x3ebb8d = _0x4f28ec[_0x5557a1(0x383)](_0x36473d['UNMASKED_VENDOR_WEBGL']) + '/' + _0x4f28ec['getParameter'](_0x36473d[_0x5557a1(0x39e)]); return _0x6caf[_0x5557a1(0x2db)] = _0x3ebb8d, _0x3ebb8d; } catch (_0x2b9fa5) { return ''; } } function _0x4f323e() { var _0x2968a5 = _0x5612de , _0xf55c22 = [_0x2968a5(0x27b), 'sans-serif', _0x2968a5(0x1fb)] , _0x3ed5f8 = {} , _0x2fef11 = {}; if (!document[_0x2968a5(0x189)]) return '0'; for (var _0x6f70bc = -0x2271 + 0x2f * 0x72 + 0xd83, _0x3e66d7 = _0xf55c22; _0x6f70bc < _0x3e66d7['length']; _0x6f70bc++) { var _0x189f91 = _0x3e66d7[_0x6f70bc] , _0x2bf145 = document[_0x2968a5(0x260)](_0x2968a5(0x1f7)); _0x2bf145['innerHTML'] = _0x2968a5(0x2a3), _0x2bf145['style'][_0x2968a5(0x362)] = '72px', _0x2bf145['style']['fontFamily'] = _0x189f91, document[_0x2968a5(0x189)][_0x2968a5(0x26c)](_0x2bf145), _0x3ed5f8[_0x189f91] = _0x2bf145[_0x2968a5(0x2ee)], _0x2fef11[_0x189f91] = _0x2bf145['offsetHeight'], document[_0x2968a5(0x189)][_0x2968a5(0x30d)](_0x2bf145); } var _0x4f59cf, _0x4abd0f = ['Trebuchet\x20MS', _0x2968a5(0x177), _0x2968a5(0x25d), 'Segoe\x20UI', _0x2968a5(0x321), _0x2968a5(0x192), 'MT\x20Extra', _0x2968a5(0x3a9), _0x2968a5(0x28e), _0x2968a5(0x346), 'Meiryo', _0x2968a5(0x247), _0x2968a5(0x1ef), _0x2968a5(0x1d8), 'IrisUPC', 'Palatino', 'Colonna\x20MT', 'Playbill', _0x2968a5(0x1f4), _0x2968a5(0x288), _0x2968a5(0x1f2), _0x2968a5(0x38d), 'OPTIMA', _0x2968a5(0x2d9), _0x2968a5(0x348), _0x2968a5(0x2b3), 'Savoye\x20LET', _0x2968a5(0x3c5), _0x2968a5(0x21e)]; _0x4f59cf = -0x255b * -0x1 + -0x1 * -0x139d + -0x38f8; for (var _0x16c300 = -0x1 * 0xc3e + -0x20ff + 0x2d3d; _0x16c300 < _0x4abd0f[_0x2968a5(0x259)]; _0x16c300++) { var _0x2c3be4, _0x37b937 = _0x350075(_0xf55c22); try { for (_0x37b937['s'](); !(_0x2c3be4 = _0x37b937['n']())[_0x2968a5(0x1e3)];) { var _0x400340 = _0x2c3be4['value'] , _0x200821 = document[_0x2968a5(0x260)]('span'); _0x200821[_0x2968a5(0x36a)] = _0x2968a5(0x2a3), _0x200821['style'][_0x2968a5(0x362)] = _0x2968a5(0x283), _0x200821[_0x2968a5(0x280)]['fontFamily'] = _0x4abd0f[_0x16c300] + ',' + _0x400340, document['body'][_0x2968a5(0x26c)](_0x200821); var _0x533f71 = _0x200821[_0x2968a5(0x2ee)] !== _0x3ed5f8[_0x400340] || _0x200821[_0x2968a5(0x2a4)] !== _0x2fef11[_0x400340]; if (document[_0x2968a5(0x189)][_0x2968a5(0x30d)](_0x200821), _0x533f71) { _0x16c300 < -0x45 * -0x8a + -0x7a9 + -0x1d6b && (_0x4f59cf |= -0x239 + 0x3 * -0x458 + 0xf42 << _0x16c300); break; } } } catch (_0x5c1c6e) { _0x37b937['e'](_0x5c1c6e); } finally { _0x37b937['f'](); } } return _0x4f59cf[_0x2968a5(0x3ae)](-0xe97 + -0x183c + -0x1 * -0x26e3); } function _0x5090f5() { var _0x458c87 = _0x5612de; try { new WebSocket(_0x458c87(0x199)); } catch (_0x34e866) { return _0x34e866[_0x458c87(0x312)]; } } function _0x468d57() { var _0x2ef0c5 = _0x5612de; return eval[_0x2ef0c5(0x3ae)]()[_0x2ef0c5(0x259)]; } function _0x5bbaf0() { var _0x5ec561 = _0x5612de , _0x5971d6 = window['RTCPeerConnection'] || window[_0x5ec561(0x2e0)] || window[_0x5ec561(0x19a)] , _0x5aac87 = []; return new Promise(function (_0x4e6ec1) { var _0x5af634 = _0x5ec561; (_0x176a57() || navigator[_0x5af634(0x229)][_0x5af634(0x202)]()[_0x5af634(0x2c5)](_0x5af634(0x20e)) > -0x707 + -0x106 * 0x10 + -0x1 * -0x1767) && _0x4e6ec1(''); try { if (_0x5971d6 && 'function' == typeof _0x5971d6) { var _0xadabb6 = new _0x5971d6({ 'iceServers': [{ 'urls': _0x5af634(0x27c) }] }) , _0x4405e6 = function () { } , _0x4de1d1 = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/; _0xadabb6[_0x5af634(0x1eb)] = function () { var _0x374eb0 = _0x5af634; _0x374eb0(0x162) === _0xadabb6['iceGatheringState'] && (_0xadabb6[_0x374eb0(0x27a)](), _0xadabb6 = null); } , _0xadabb6['onicecandidate'] = function (_0x4e2717) { var _0x18e62e = _0x5af634; if (_0x4e2717 && _0x4e2717[_0x18e62e(0x227)] && _0x4e2717[_0x18e62e(0x227)][_0x18e62e(0x227)]) { if ('' === _0x4e2717['candidate'][_0x18e62e(0x227)]) return; var _0x1d1cd1 = _0x4de1d1['exec'](_0x4e2717[_0x18e62e(0x227)][_0x18e62e(0x227)]); if (null !== _0x1d1cd1 && _0x1d1cd1[_0x18e62e(0x259)] > -0x1675 * 0x1 + -0x19 * 0xdd + 0x19 * 0x1c3) { var _0xbaa956 = _0x1d1cd1[-0x1bdd * 0x1 + 0x13 * -0x1df + -0x11 * -0x3bb]; -(0x1 * -0x12c1 + -0xa * 0x38c + -0x4ee * -0xb) === _0x5aac87[_0x18e62e(0x2c5)](_0xbaa956) && _0x5aac87[_0x18e62e(0x36e)](_0xbaa956); } } else _0x4e6ec1(_0x5aac87[_0x18e62e(0x24f)]()); } , _0xadabb6[_0x5af634(0x1e5)]('') // setTimeout(function () { // var _0x36b72e = _0x5af634; // _0x4e6ec1(_0x5aac87[_0x36b72e(0x24f)]()); // }, 0x2205 * 0x1 + 0x1d4 * -0x10 + -0x67 * 0x7); var _0x38a358 = _0xadabb6[_0x5af634(0x2f5)](); _0x38a358 instanceof Promise ? _0x38a358[_0x5af634(0x1ed)](function (_0x33e26c) { return _0xadabb6['setLocalDescription'](_0x33e26c); })[_0x5af634(0x1ed)](_0x4405e6) : _0xadabb6['createOffer'](function (_0x1c6e51) { _0xadabb6['setLocalDescription'](_0x1c6e51, _0x4405e6, _0x4405e6); }, _0x4405e6); } else _0x4e6ec1(''); } catch (_0x31debb) { _0x4e6ec1(''); } } ); } function _0x2e02ca() { var _0x74cdcb = _0x5612de; return _0x74cdcb(0x3af)[_0x74cdcb(0x377)](/[xy]/g, function (_0x5790dd) { var _0x52f0b5 = _0x74cdcb , _0x2eeaf3 = (0x506 + -0x85c + -0x2 * -0x1b3) * Math[_0x52f0b5(0x18e)]() | 0xbb9 * -0x1 + 0x1 * 0x9dd + 0x1dc; return ('x' == _0x5790dd ? _0x2eeaf3 : -0xd1f * 0x1 + 0x1df6 + -0x1 * 0x10d4 & _0x2eeaf3 | 0x182d + -0x10 * 0x262 + -0xdfb * -0x1)[_0x52f0b5(0x3ae)](0x1a65 + 0x3 * 0x17a + 0x13b * -0x19); }); } function _0x2e2fa0(_0x1a6938) { var _0x5aaa98 = _0x5612de; return -0x14 * 0x1df + 0x649 + 0x1f45 === _0x1a6938[_0x5aaa98(0x259)] && _0x246aeb(-0x21ad + 0x819 * -0x1 + 0x29c6, _0x1a6938[_0x5aaa98(0x1b0)](-0x1a55 * 0x1 + 0x1279 + 0x7dc, 0x31b + -0xcb4 * -0x1 + -0xfaf))[_0x5aaa98(0x3ae)]()['substring'](0x17cc + 0x1 * -0xd19 + -0x3 * 0x391, -0x1ff * 0x1 + 0x2579 + -0x1c6 * 0x14) === _0x1a6938[_0x5aaa98(0x1b0)](0x1746 + 0x7b8 + -0x1ede, -0x369 * -0x2 + -0x5e5 * 0x4 + 0xbc * 0x17); } function _0x178d7c() { var _0x4d5814 = _0x5612de , _0x2d4341 = _0x24dc34(_0x4d5814(0x262)); return _0x2d4341 && _0x2e2fa0(_0x2d4341) || _0x1f42cb(_0x4d5814(0x262), _0x2d4341 = ((_0x2d4341 = _0x2e02ca()) + _0x246aeb(0x17 * 0x12d + -0x6 * 0x3f7 + -0x341, _0x2d4341))[_0x4d5814(0x1b0)](0x2d1 * -0xd + 0x3a * -0x7c + 0x40b5, 0x188e + 0x13 * -0x127 + -0x287)), _0x2d4341; } function _0x3c0a68(_0x449fe8, _0x5ede0c) { var _0x40bd5b = _0x5612de , _0x401085 = null; try { _0x401085 = document['getElementsByTagName'](_0x40bd5b(0x221))[-0x2581 + 0x844 + -0x1d3d * -0x1]; } catch (_0x599ca4) { _0x401085 = document['body']; } if (null !== _0x401085) { var _0x3a3665 = document[_0x40bd5b(0x260)](_0x40bd5b(0x361)) , _0x215422 = '_' + parseInt((0x3df0 + 0x13d + -0x181d) * Math['random'](), 0x1 * -0x2141 + -0x1 * -0xf82 + 0x11c9) + '_' + new Date()[_0x40bd5b(0x16d)](); _0x449fe8 += _0x40bd5b(0x1be) + _0x215422, _0x3a3665[_0x40bd5b(0x16b)] = _0x449fe8, window[_0x215422] = function (_0x1efd79) { var _0x4afb72 = _0x40bd5b; try { _0x5ede0c(_0x1efd79), _0x401085[_0x4afb72(0x30d)](_0x3a3665), delete window[_0x215422]; } catch (_0xf98f95) { } } , _0x401085[_0x40bd5b(0x26c)](_0x3a3665); } } function _0x4072ad(_0x18cd85) { return w_0x5c3140('484e4f4a403f524300022c395eafc0a4000000004a04440d00000030110104324700040700004202110100030443011400011100010211010102110102110104110001430207000143021842000200404f4c4d4a4b484946474445424340415e5f5c5d5a5b58595657546f6c6d6a6b686966676465626360617e7f7c7d7a7b78797677743e3f3c3d3a3b383936372320', { get 0x0() { return _0x325f58; }, get 0x1() { return _0x328bde; }, get 0x2() { return _0xdda738; }, 0x3: arguments, 0x4: _0x18cd85 }, this); } function _0x5c0cdd(_0x380d2b, _0x1c644a) { var _0x349270 = _0x5612de; if (_0x1c644a) { for (var _0x25fd23 = -0xbdc + -0xd78 * -0x2 + -0xf14, _0x2c655d = -0x1 * -0xbdb + 0xc2 + -0xc9d; _0x2c655d < _0x380d2b[_0x349270(0x259)]; _0x2c655d++) _0x380d2b[_0x2c655d]['p'] && (_0x380d2b[_0x2c655d]['r'] = _0x1c644a[_0x25fd23++]); } var _0x20da32 = ''; _0x380d2b[_0x349270(0x254)](function (_0x564ece) { _0x20da32 += _0x5af46a(_0x564ece['r']) + '^^'; }), _0x20da32 += _0x30c916(); var _0x2803a3 = _0x2e02ca() , _0x273650 = Math[_0x349270(0x1cf)](_0x2803a3[_0x349270(0x195)](0x127a + 0x22e0 + -0x3557 * 0x1) / (0x1 * 0x199f + -0xab4 + -0x1 * 0xee3)) + _0x2803a3[_0x349270(0x195)](-0x15 * 0xca + -0x1ff6 * -0x1 + 0x1 * -0xf61) % (-0x1085 + -0x553 + 0x15e0) , _0x102025 = _0x2803a3['substring'](0x1 * -0x1ebf + 0x13a1 + 0x1e * 0x5f, 0x5c7 + -0x5a5 + -0x5 * 0x6 + _0x273650); _0x20da32 = _0x328bde(_0xdda738(_0x20da32, _0x102025) + _0x2803a3); var _0x197e34 = _0x45ece5['fpProfileUrl']; _0x3c0a68(_0x197e34 += _0x349270(0x173) + encodeURIComponent(_0x20da32) + '&', function (_0x5b4c95) { var _0x3c0c48 = _0x349270; 0x1db6 + -0x21d * 0x1 + -0x1b99 == _0x5b4c95[_0x3c0c48(0x356)] && _0x5b4c95['fp'] && (_0x462335[_0x3c0c48(0x35f)] = _0x5b4c95['fp'], _0x462335[_0x3c0c48(0x363)] = _0x4072ad(_0x5b4c95['fp']), _0x1f42cb('tt_scid', _0x5b4c95['fp'])); }); } function _0x1c3b6d(_0x20f8c9) { var _0x596053 = _0x5612de; return w_0x5c3140(_0x596053(0x242), { get 0x0() { return navigator; }, get 0x1() { return window; }, get 0x2() { return document; }, get 0x3() { return _0x30c916; }, get 0x4() { return _0x1afbc2; }, get 0x5() { return _0x18b4be; }, get 0x6() { return _0x130155; }, get 0x7() { return _0x2a76f8; }, get 0x8() { return _0x3da279; }, get 0x9() { return _0x299f3a; }, get 0xa() { return _0x4649a1; }, get 0xb() { return _0x487576; }, get 0xc() { return _0x4f323e; }, get 0xd() { return _0x1f9824; }, get 0xe() { return _0x24dc34; }, get 0xf() { return _0x5090f5; }, get 0x10() { return _0x468d57; }, get 0x11() { return _0x5bbaf0; }, get 0x12() { return _0x45b94b; }, get 0x13() { return _0x178d7c; }, get 0x14() { return _0x5af46a; }, 0x15: Promise, get 0x16() { return _0x5c0cdd; }, 0x17: arguments, 0x18: _0x20f8c9 }, this); } function _0x20cbf3(_0x38a8fe, _0x406d4b, _0x2e7a9b) { var _0x1b116c = _0x5612de; return w_0x5c3140(_0x1b116c(0x233), { 0x0: String, 0x1: Date, get 0x2() { return _0x45b94b; }, get 0x3() { return _0x184783; }, get 0x4() { return location; }, 0x5: parseInt, get 0x6() { return _0x5863d1; }, 0x7: JSON, get 0x8() { return _0xf119da; }, get 0x9() { return _0x3a1cf3; }, get 0xa() { return _0x20b77a; }, get 0xb() { return _0x446110; }, 0xc: Object, get 0xd() { return _0x483e03; }, get 0xe() { return _0x53f850; }, get 0xf() { return _0x2a900b; }, get 0x10() { return _0x18b4be; }, get 0x11() { return _0x462335; }, get 0x12() { return _0x4072ad; }, get 0x13() { return _0x24dc34; }, get 0x14() { return navigator; }, 0x15: arguments, 0x16: _0x38a8fe, 0x17: _0x406d4b, 0x18: _0x2e7a9b }, this); } function _0x5e5a64(_0x27ec41, _0x4e1246) { var _0x15a192 = _0x5612de; for (var _0x3f84da = {}, _0x389521 = -0x4bd * -0x1 + -0x28a + -0x233; _0x389521 < _0x4e1246['length']; _0x389521++) { var _0x58af98 = _0x4e1246[_0x389521] , _0x1b7f4e = _0x27ec41[_0x58af98]; null == _0x1b7f4e && (_0x1b7f4e = !(-0x1f76 + -0x1c06 + 0x3b7d * 0x1)), null === _0x1b7f4e || 'function' != typeof _0x1b7f4e && _0x15a192(0x17d) !== _0x1db123(_0x1b7f4e) || (_0x1b7f4e = !(-0x2 * -0xef9 + -0xa1 * 0x3 + -0xb * 0x28d)), _0x3f84da[_0x58af98] = _0x1b7f4e; } return _0x3f84da; } function _0x2a6ac2() { var _0x42b9bc = _0x5612de; return _0x5e5a64(navigator, [_0x42b9bc(0x36b), 'appName', _0x42b9bc(0x17a), _0x42b9bc(0x246), _0x42b9bc(0x3a4), _0x42b9bc(0x360), _0x42b9bc(0x1fa), _0x42b9bc(0x33b), _0x42b9bc(0x38c), _0x42b9bc(0x212), 'vendorSub', 'doNotTrack', _0x42b9bc(0x24a), _0x42b9bc(0x2e2), _0x42b9bc(0x292), _0x42b9bc(0x390), _0x42b9bc(0x38f)]); } function _0x226933() { var _0xe67fb0 = _0x5612de; return _0x5e5a64(window, [_0xe67fb0(0x29d), _0xe67fb0(0x222), _0xe67fb0(0x306), _0xe67fb0(0x26e), _0xe67fb0(0x366), 'isSecureContext', _0xe67fb0(0x3c0), _0xe67fb0(0x399), 'locationbar', _0xe67fb0(0x29f), _0xe67fb0(0x39f), _0xe67fb0(0x2e0), 'postMessage', 'webkitRequestAnimationFrame', _0xe67fb0(0x2de), 'netscape']); } function _0x1e5a7f() { var _0x577bf2 = _0x5612de; return _0x5e5a64(document, [_0x577bf2(0x335), _0x577bf2(0x253), _0x577bf2(0x373), 'layers', _0x577bf2(0x2c0)]); } function _0x11a1d6() { var _0x3927af = _0x5612de , _0x306255 = document[_0x3927af(0x260)](_0x3927af(0x24d)) , _0x3b8708 = null; try { _0x3b8708 = _0x306255[_0x3927af(0x2f9)](_0x3927af(0x26b)) || _0x306255['getContext'](_0x3927af(0x3aa)); } catch (_0x30a5e5) { } return _0x3b8708 || (_0x3b8708 = null), _0x3b8708; } function _0x39c3d8(_0xc6dcf6) { var _0x59367a = _0x5612de , _0x5a3a25 = _0xc6dcf6[_0x59367a(0x1d7)](_0x59367a(0x1f1)) || _0xc6dcf6[_0x59367a(0x1d7)](_0x59367a(0x2e5)) || _0xc6dcf6[_0x59367a(0x1d7)](_0x59367a(0x31a)); if (_0x5a3a25) { var _0x5cca98 = _0xc6dcf6[_0x59367a(0x383)](_0x5a3a25[_0x59367a(0x1bf)]); return 0xb * -0x2b8 + 0xa * 0xe + 0x1d5c === _0x5cca98 && (_0x5cca98 = -0xe6 + -0x1909 + -0xe5 * -0x1d), _0x5cca98; } return null; } function _0x425568() { var _0x556a1e = _0x5612de; if (_0x6caf[_0x556a1e(0x23d)]) return _0x6caf[_0x556a1e(0x23d)]; var _0x4f4b6e = _0x11a1d6(); if (!_0x4f4b6e) return {}; var _0x58c017 = { 'supportedExtensions': _0x4f4b6e[_0x556a1e(0x30f)]() || [], 'antialias': _0x4f4b6e[_0x556a1e(0x1c5)]()[_0x556a1e(0x1a5)], 'blueBits': _0x4f4b6e[_0x556a1e(0x383)](_0x4f4b6e[_0x556a1e(0x27e)]), 'depthBits': _0x4f4b6e['getParameter'](_0x4f4b6e[_0x556a1e(0x252)]), 'greenBits': _0x4f4b6e[_0x556a1e(0x383)](_0x4f4b6e[_0x556a1e(0x3c3)]), 'maxAnisotropy': _0x39c3d8(_0x4f4b6e), 'maxCombinedTextureImageUnits': _0x4f4b6e['getParameter'](_0x4f4b6e[_0x556a1e(0x1d2)]), 'maxCubeMapTextureSize': _0x4f4b6e[_0x556a1e(0x383)](_0x4f4b6e[_0x556a1e(0x300)]), 'maxFragmentUniformVectors': _0x4f4b6e['getParameter'](_0x4f4b6e['MAX_FRAGMENT_UNIFORM_VECTORS']), 'maxRenderbufferSize': _0x4f4b6e[_0x556a1e(0x383)](_0x4f4b6e[_0x556a1e(0x2da)]), 'maxTextureImageUnits': _0x4f4b6e[_0x556a1e(0x383)](_0x4f4b6e[_0x556a1e(0x269)]), 'maxTextureSize': _0x4f4b6e[_0x556a1e(0x383)](_0x4f4b6e[_0x556a1e(0x203)]), 'maxVaryingVectors': _0x4f4b6e[_0x556a1e(0x383)](_0x4f4b6e[_0x556a1e(0x264)]), 'maxVertexAttribs': _0x4f4b6e[_0x556a1e(0x383)](_0x4f4b6e['MAX_VERTEX_ATTRIBS']), 'maxVertexTextureImageUnits': _0x4f4b6e[_0x556a1e(0x383)](_0x4f4b6e[_0x556a1e(0x205)]), 'maxVertexUniformVectors': _0x4f4b6e[_0x556a1e(0x383)](_0x4f4b6e[_0x556a1e(0x19d)]), 'shadingLanguageVersion': _0x4f4b6e['getParameter'](_0x4f4b6e['SHADING_LANGUAGE_VERSION']), 'stencilBits': _0x4f4b6e['getParameter'](_0x4f4b6e['STENCIL_BITS']), 'version': _0x4f4b6e[_0x556a1e(0x383)](_0x4f4b6e['VERSION']) }; return _0x6caf[_0x556a1e(0x23d)] = _0x58c017, _0x58c017; } function _0x18707d() { var _0x52b59d = _0x5612de , _0x56cb86 = {}; return _0x56cb86[_0x52b59d(0x270)] = _0x2a6ac2(), _0x56cb86[_0x52b59d(0x393)] = _0x226933(), _0x56cb86[_0x52b59d(0x208)] = _0x1e5a7f(), _0x56cb86[_0x52b59d(0x26b)] = _0x425568(), _0x56cb86[_0x52b59d(0x381)] = _0x487576(), _0x56cb86['plugins'] = _0x1f9824(), _0x6caf['SECINFO'] = _0x56cb86, _0x56cb86; } function _0x572e48() { var _0x151b17 = _0x5612de; return w_0x5c3140(_0x151b17(0x1d1), { get 0x0() { return _0x6caf; }, get 0x1() { return _0x18707d; }, 0x2: Date, get 0x3() { return _0x325f58; }, get 0x4() { return _0x328bde; }, get 0x5() { return _0xdda738; }, 0x6: JSON, 0x7: arguments }, this); } var _0x522d20 = { 'kCallTypeDirect': 0x0, 'kCallTypeInterceptor': 0x1 } , _0x2e10da = { 'kHttp': 0x0, 'kWebsocket': 0x1 } , _0x3f742e = _0x45b94b; function _0x5646fa(_0x29e841) { var _0x2ecc2b = _0x5612de; for (var _0x5d379d, _0x5e2317, _0x34abf7 = [], _0x448a08 = -0x25ac + 0x1 * -0x1a4 + -0x128 * -0x22; _0x448a08 < _0x29e841[_0x2ecc2b(0x259)]; _0x448a08++) { _0x5d379d = _0x29e841[_0x2ecc2b(0x195)](_0x448a08), _0x5e2317 = []; do { _0x5e2317[_0x2ecc2b(0x36e)](-0x8d * -0x3e + 0x24d * -0x1 + -0x1eda & _0x5d379d), _0x5d379d >>= -0x6d * 0x11 + 0x75 * 0x11 + 0x10 * -0x8; } while (_0x5d379d); _0x34abf7 = _0x34abf7[_0x2ecc2b(0x2b8)](_0x5e2317['reverse']()); } return _0x34abf7; } function _0x6bc4ae(_0x4e8b7c) { } function _0x5b890d(_0x592717) { } function _0x16f345(_0x1a40af) { } function _0x361214(_0x426175) { } function _0xc35122(_0xe69c60, _0x3e14a1, _0x2d525a) { } var _0x5dde58 = { 'WEB_DEVICE_INFO': 0x8 }; function _0x4df596(_0x14c951, _0x4c06b0) { var _0x3cd5a5 = _0x5612de; return JSON[_0x3cd5a5(0x1e6)]({ 'magic': 0x20200422, 'version': 0x1, 'dataType': _0x14c951, 'strData': _0x4c06b0, 'tspFromClient': new Date()[_0x3cd5a5(0x16d)]() }); } function _0x29a5ac(_0x1a16de, _0x1af868, _0x2e45c3, _0x2a9bcc) { var _0x233cbb = _0x5612de; return _0x32af0e(_0x233cbb(0x3a1), _0x1a16de, _0x1af868, _0x2e45c3, _0x2a9bcc); } function _0x32af0e(_0x58e2fa, _0x35c6ef, _0x5ce009, _0xba3041, _0x20e80f) { var _0x393ad1 = _0x5612de , _0x4841fc = new XMLHttpRequest(); if (_0x4841fc[_0x393ad1(0x18b)](_0x58e2fa, _0x35c6ef, !(-0xb * 0x8b + -0x233 + 0x82c)), _0x20e80f && (_0x4841fc[_0x393ad1(0x1c6)] = !(0x1 * -0x260f + -0x2 * 0xcdf + 0x3fcd)), _0xba3041) for (var _0x3f91e1 = -0x2ce + -0x1 * 0xe0f + 0x10dd, _0x2f8bec = Object['keys'](_0xba3041); _0x3f91e1 < _0x2f8bec[_0x393ad1(0x259)]; _0x3f91e1++) { var _0x475f86 = _0x2f8bec[_0x3f91e1] , _0x42c2b4 = _0xba3041[_0x475f86]; _0x4841fc[_0x393ad1(0x3ab)](_0x475f86, _0x42c2b4); } _0x4841fc[_0x393ad1(0x20a)](_0x5ce009); } function _0x2cd488(_0x751be6, _0x4c12d4) { var _0x253a32 = _0x5612de; return _0x4c12d4 || (_0x4c12d4 = null), !!navigator['sendBeacon'] && (navigator[_0x253a32(0x24b)](_0x751be6, _0x4c12d4), !(-0x1da1 + 0x1e93 + -0xf2)); } function _0x4a2daf(_0x1ea426, _0x5a460b) { var _0x22c57e = _0x5612de; try { window[_0x22c57e(0x1dc)] && window[_0x22c57e(0x1dc)][_0x22c57e(0x1c4)](_0x1ea426, _0x5a460b); } catch (_0xe6e06d) { } } function _0x34f60a(_0x1e7505) { var _0x389acb = _0x5612de; try { window[_0x389acb(0x1dc)] && window[_0x389acb(0x1dc)][_0x389acb(0x2c3)](_0x1e7505); } catch (_0x5daf1b) { } } function _0x3d13cf(_0x3480fd) { var _0x1ccc66 = _0x5612de; try { return window[_0x1ccc66(0x1dc)] ? window['localStorage']['getItem'](_0x3480fd) : null; } catch (_0x3bb8f0) { return null; } } function _0x21db29(_0x584f42, _0xde0f43) { var _0x4464a1 = _0x5612de; for (var _0x2b8d40, _0x22bd9c = [], _0x13f024 = 0x1856 * 0x1 + -0x6a * 0x20 + -0x16 * 0x81, _0x5648fb = '', _0x4c41a3 = -0x2 * -0xc67 + -0xf91 + -0x5 * 0x1d9; _0x4c41a3 < -0x1 * -0x322 + -0x25ce + 0x23ac; _0x4c41a3++) _0x22bd9c[_0x4c41a3] = _0x4c41a3; for (var _0x4d4c13 = -0x18e4 + -0x3 * -0x10f + 0x15b7; _0x4d4c13 < -0x36a * -0x8 + 0x1cf7 + 0x126d * -0x3; _0x4d4c13++) _0x13f024 = (_0x13f024 + _0x22bd9c[_0x4d4c13] + _0x584f42[_0x4464a1(0x195)](_0x4d4c13 % _0x584f42[_0x4464a1(0x259)])) % (-0x577 * 0x3 + 0x4eb + 0xc7a), _0x2b8d40 = _0x22bd9c[_0x4d4c13], _0x22bd9c[_0x4d4c13] = _0x22bd9c[_0x13f024], _0x22bd9c[_0x13f024] = _0x2b8d40; var _0x11b95d = -0x7d8 + -0x256 + -0x517 * -0x2; _0x13f024 = -0x1bef + -0xbc5 * -0x3 + -0x760 * 0x1; for (var _0x15a79f = 0xd * 0x12e + 0x80 * -0x3d + 0xf2a; _0x15a79f < _0xde0f43[_0x4464a1(0x259)]; _0x15a79f++) _0x13f024 = (_0x13f024 + _0x22bd9c[_0x11b95d = (_0x11b95d + (0x8ed + -0x1 * -0x836 + -0x22 * 0x81)) % (0x32b * 0x7 + 0x2311 + -0x2 * 0x1c1f)]) % (-0x1 * -0x106d + 0x35e * 0x5 + -0x2043), _0x2b8d40 = _0x22bd9c[_0x11b95d], _0x22bd9c[_0x11b95d] = _0x22bd9c[_0x13f024], _0x22bd9c[_0x13f024] = _0x2b8d40, _0x5648fb += String[_0x4464a1(0x1e2)](_0xde0f43[_0x4464a1(0x195)](_0x15a79f) ^ _0x22bd9c[(_0x22bd9c[_0x11b95d] + _0x22bd9c[_0x13f024]) % (0x179 * -0x7 + 0x2385 + -0x2 * 0xc1b)]); return _0x5648fb; } var _0x45bf15 = _0x21db29 , _0x48a082 = {}; function _0x641e3d(_0x21dbac, _0x4a67ec) { var _0x51016d = _0x5612de; return w_0x5c3140(_0x51016d(0x3c4), { 0x0: String, 0x1: Math, get 0x2() { return _0x45bf15; }, get 0x3() { return _0x389396; }, 0x4: arguments, 0x5: _0x21dbac, 0x6: _0x4a67ec }, this); } _0x48a082['pb'] = -0xd0f * -0x1 + 0xade * 0x2 + -0x22c9, _0x48a082['json'] = 0x405 + -0x1 * -0xe59 + -0x125d; var _0x216650 = { 'kNoMove': 0x2, 'kNoClickTouch': 0x4, 'kNoKeyboardEvent': 0x8, 'kMoveFast': 0x10, 'kKeyboardFast': 0x20, 'kFakeOperations': 0x40 } , _0x5dc9cc = { 'sTm': 0x0, 'acc': 0x0 }; function _0x18a9f7() { var _0x4e02b7 = _0x5612de; try { var _0x41737c = _0x3d13cf(_0x4e02b7(0x343)); _0x41737c ? Object[_0x4e02b7(0x2a0)](_0x5dc9cc, JSON[_0x4e02b7(0x3b1)](_0x41737c)) : (_0x5dc9cc[_0x4e02b7(0x293)] = new Date()[_0x4e02b7(0x16d)](), _0x5dc9cc[_0x4e02b7(0x27d)] = -0x175c + -0x1066 + 0x27c2); } catch (_0x3da833) { _0x5dc9cc[_0x4e02b7(0x293)] = new Date()[_0x4e02b7(0x16d)](), _0x5dc9cc[_0x4e02b7(0x27d)] = -0x1 * -0x1129 + -0x1f1f + -0x1 * -0xdf6, _0x26e186(); } } function _0x26e186() { var _0x48e1a8 = _0x5612de; _0x4a2daf(_0x48e1a8(0x343), JSON['stringify'](_0x5dc9cc)); } var _0xf63a81 = { 'T_MOVE': 0x1, 'T_CLICK': 0x2, 'T_KEYBOARD': 0x3 } , _0x2786f5 = !(0x1f31 * -0x1 + 0x1 * 0x22c7 + 0x83 * -0x7) , _0x9fb121 = [] , _0x207cc5 = [] , _0x191fa5 = [] , _0xe06992 = { 'ubcode': 0x0 } , _0x388856 = function (_0x4e0342, _0x5ca571) { return _0x4e0342 + _0x5ca571; } , _0x20a3d9 = function (_0x2309ca) { return _0x2309ca * _0x2309ca; }; function _0x9c0be2(_0x232ed8, _0x432cf8) { var _0x59688f = _0x5612de; if (_0x232ed8[_0x59688f(0x259)] > 0x1 * 0xa93 + 0x1d7a + -0xd17 * 0x3 && _0x232ed8['splice'](0x1ab7 + 0x1 * -0x2147 + -0x1a4 * -0x4, 0x617 + 0x280 + 0x1 * -0x833), _0x232ed8[_0x59688f(0x259)] > -0x1c57 + 0x1059 + 0xbfe) { var _0x97b7e7 = _0x232ed8[_0x232ed8['length'] - (-0xc0e + 0xb26 * 0x2 + 0xa3d * -0x1)]; if (_0x432cf8['d'] - _0x97b7e7['d'] <= 0x5 * -0x1a3 + -0x91 * -0x32 + 0x5 * -0x407 || 'y' in _0x432cf8 && _0x432cf8['x'] === _0x97b7e7['x'] && _0x432cf8['y'] === _0x97b7e7['y']) return; } _0x232ed8[_0x59688f(0x36e)](_0x432cf8); } function _0x1d26db(_0x261f4b, _0x3c3c83, _0x131716) { var _0x10f2ca = _0x5612de; if (_0x462335['enableTrack']) { if (_0x131716 !== _0xf63a81['T_MOVE']) return _0x131716 === _0xf63a81[_0x10f2ca(0x1f0)] ? (_0x261f4b[_0x10f2ca(0x259)] >= 0x1167 + -0x856 * -0x4 + 0x1 * -0x30cb && _0x450b73(), void _0x261f4b[_0x10f2ca(0x36e)](_0x3c3c83)) : _0x131716 === _0xf63a81[_0x10f2ca(0x355)] ? (_0x261f4b[_0x10f2ca(0x259)] > -0x1b6 * 0x16 + -0x115 * 0x17 + 0x11 * 0x3cb && _0x450b73(), void _0x261f4b['push'](_0x3c3c83)) : void (-0x15a4 + -0xc9f + 0x2243); if (_0x261f4b['length'] >= -0x5b9 + -0x1b5 + -0x2 * -0x4b1 && _0x450b73(), _0x261f4b[_0x10f2ca(0x259)] > 0x13e * 0x6 + 0x4a5 * -0x2 + 0x1d6) { var _0x40dc65 = _0x261f4b[_0x261f4b[_0x10f2ca(0x259)] - (-0x233e + 0x6bf + 0x1c80)] , _0x243563 = _0x40dc65['x'] , _0x7ef629 = _0x40dc65['y'] , _0x293666 = _0x40dc65['ts']; if (_0x243563 === _0x3c3c83['x'] && _0x7ef629 === _0x3c3c83['y']) return; if (_0x3c3c83['ts'] - _0x293666 < -0x1 * -0x9dc + -0x7 * -0x32d + -0x1e23) return; } _0x261f4b['push'](_0x3c3c83); } } var _0x19ffaf = { 'init': 0x0, 'running': 0x1, 'exit': 0x2, 'flush': 0x3 }; function _0x450b73(_0xd6648d) { var _0x5b1d4d = _0x5612de; return w_0x5c3140(_0x5b1d4d(0x3a2), { get 0x0() { return _0x6caf; }, get 0x1() { return _0x19ffaf; }, 0x2: Date, get 0x3() { return _0x5dc9cc; }, get 0x4() { return _0x462335; }, get 0x5() { return _0x26e186; }, 0x6: Object, get 0x7() { return _0x4df596; }, get 0x8() { return _0x5dde58; }, get 0x9() { return _0x641e3d; }, 0xa: JSON, get 0xb() { return _0x48a082; }, get 0xc() { return _0x2cd488; }, get 0xd() { return _0x29a5ac; }, 0xe: arguments, 0xf: _0xd6648d }, this); } function _0x58c311() { var _0x30dab3 = _0x5612de; _0x462335[_0x30dab3(0x37f)] && _0x450b73(_0x19ffaf['exit']); } var _0x1a755e = {}; _0x1a755e[_0x5612de(0x2d4)] = _0x3dcfd5, _0x1a755e['touchmove'] = _0x3dcfd5, _0x1a755e[_0x5612de(0x364)] = _0x56114b, _0x1a755e[_0x5612de(0x1d6)] = _0x19d89b, _0x1a755e[_0x5612de(0x2d3)] = _0x19d89b; var _0x18582a = !(0x1796 + -0xf7d + -0x818); function _0x1dbe74() { var _0x101f5b = _0x5612de; if (document && document[_0x101f5b(0x2f6)] && !_0x18582a) { for (var _0x1dfebe = 0x1 * -0xe36 + 0x6e8 + -0xa * -0xbb, _0x5af0b0 = Object[_0x101f5b(0x17f)](_0x1a755e); _0x1dfebe < _0x5af0b0[_0x101f5b(0x259)]; _0x1dfebe++) { var _0x2ecc3c = _0x5af0b0[_0x1dfebe]; document[_0x101f5b(0x2f6)](_0x2ecc3c, _0x1a755e[_0x2ecc3c]); } _0x18582a = !(-0x1 * -0xfcb + 0x21 * 0x65 + -0x1cd0); } } function _0x3dcfd5(_0x1f9f7e) { var _0x4f99fd = _0x5612de , _0x395631 = _0x1f9f7e , _0x1f0c31 = _0x1f9f7e['type']; _0x1f9f7e[_0x4f99fd(0x379)] && _0x4f99fd(0x299) === _0x1f0c31 && (_0x395631 = _0x1f9f7e['touches'][-0x9 * -0x3f4 + 0x1 * 0xc73 + -0x3007], _0x2786f5 = !(-0x129a + 0x1bca + -0x930)); var _0x5d0e65 = { 'x': Math[_0x4f99fd(0x1cf)](_0x395631['clientX']), 'y': Math[_0x4f99fd(0x1cf)](_0x395631['clientY']), 'd': Date[_0x4f99fd(0x34d)]() }; _0x9c0be2(_0x9fb121, _0x5d0e65), _0x1d26db(_0x6caf[_0x4f99fd(0x21d)], { 'ts': _0x5d0e65['d'], 'x': _0x5d0e65['x'], 'y': _0x5d0e65['y'] }, _0xf63a81[_0x4f99fd(0x2be)]); } function _0x56114b(_0x35e1c1) { var _0x3de3de = _0x5612de , _0x411a0a = 0x22e7 + -0x5 * -0x133 + -0x28e6; (_0x35e1c1[_0x3de3de(0x28c)] || _0x35e1c1['ctrlKey'] || _0x35e1c1['metaKey'] || _0x35e1c1[_0x3de3de(0x22e)]) && (_0x411a0a = 0xe7c + -0x3 * 0x143 + -0xab2); var _0x5239f5 = { 'x': _0x411a0a, 'd': Date['now']() }; _0x9c0be2(_0x191fa5, _0x5239f5), _0x1d26db(_0x6caf[_0x3de3de(0x391)], { 'ts': _0x5239f5['d'] }, _0xf63a81[_0x3de3de(0x355)]); } function _0x19d89b(_0x39a86c) { var _0x3bb659 = _0x5612de , _0x4a9de2 = _0x39a86c , _0x33eb04 = _0x39a86c['type']; _0x39a86c[_0x3bb659(0x379)] && 'touchstart' === _0x33eb04 && (_0x4a9de2 = _0x39a86c['touches'][0x1491 + 0x1 * -0x1216 + -0x1 * 0x27b], _0x2786f5 = !(-0x1 * -0x853 + 0x12df * 0x2 + -0x2e11)); var _0xf57aea = { 'x': Math['floor'](_0x4a9de2[_0x3bb659(0x2bf)]), 'y': Math[_0x3bb659(0x1cf)](_0x4a9de2['clientY']), 'd': Date['now']() }; _0x9c0be2(_0x207cc5, _0xf57aea), _0x1d26db(_0x6caf[_0x3bb659(0x3b6)], { 'ts': _0xf57aea['d'], 'x': _0xf57aea['x'], 'y': _0xf57aea['y'] }, _0xf63a81[_0x3bb659(0x1f0)]); } function _0x42fe9b(_0x320405) { var _0x2bf333 = _0x5612de; return _0x320405['reduce'](_0x388856) / _0x320405[_0x2bf333(0x259)]; } function _0x3ea7d6(_0xbdd4a6) { var _0x1e5c1e = _0x5612de; if (_0xbdd4a6[_0x1e5c1e(0x259)] <= -0x1 * -0x2420 + 0xc87 * -0x1 + 0x5e6 * -0x4) return -0x1 * -0x309 + 0x15 * 0x9d + -0xfea * 0x1; var _0x3deca4 = _0x42fe9b(_0xbdd4a6) , _0x58472b = _0xbdd4a6['map'](function (_0x508d28) { return _0x508d28 - _0x3deca4; }); return Math[_0x1e5c1e(0x317)](_0x58472b['map'](_0x20a3d9)[_0x1e5c1e(0x376)](_0x388856) / (_0xbdd4a6[_0x1e5c1e(0x259)] - (0xa * 0x127 + -0x2103 + -0xe * -0x189))); } function _0x52f064(_0x2031c9, _0x5a5379, _0xb6ab78) { var _0x1d0db5 = _0x5612de , _0xcfe257 = 0xae8 + -0xb * -0x6f + -0xfad , _0x4f9c60 = -0x894 + -0x12a7 + 0x1 * 0x1b3b; if (_0x2031c9[_0x1d0db5(0x259)] > _0x5a5379) { for (var _0x147b41 = [], _0x5ca0e0 = -0x10a8 + -0x212f + 0x3 * 0x109d; _0x5ca0e0 < _0x2031c9[_0x1d0db5(0x259)] - (0x557 * -0x7 + -0x5 * -0xc6 + 0x14a * 0x1a); _0x5ca0e0++) { var _0x2a170e = _0x2031c9[_0x5ca0e0 + (-0x20c6 * -0x1 + 0x1f31 + -0x3ff6)] , _0x1f8ecf = _0x2031c9[_0x5ca0e0] , _0x1b82ec = _0x2a170e['d'] - _0x1f8ecf['d']; _0x1b82ec && (_0xb6ab78 ? _0x147b41[_0x1d0db5(0x36e)]((0xf98 + 0x90f + 0x2 * -0xc53) / _0x1b82ec) : _0x147b41[_0x1d0db5(0x36e)](Math[_0x1d0db5(0x317)](_0x20a3d9(_0x2a170e['x'] - _0x1f8ecf['x']) + _0x20a3d9(_0x2a170e['y'] - _0x1f8ecf['y'])) / _0x1b82ec)); } _0xcfe257 = _0x42fe9b(_0x147b41), 0x5 * -0x79d + 0xf2d * -0x1 + -0xeb * -0x3a === (_0x4f9c60 = _0x3ea7d6(_0x147b41)) && (_0x4f9c60 = 0xcc5 + -0x1 * -0x9e1 + -0x16a6 + 0.01); } return [_0xcfe257, _0x4f9c60]; } function _0x26d461() { var _0x3f77b6 = _0x5612de , _0x5e9ee6 = !(0x9d * -0x6 + 0x16b0 + -0x1301) , _0x499168 = -0x21e + -0x1b6a + 0x28 * 0xbd; try { document && document['createEvent'] && (document[_0x3f77b6(0x188)](_0x3f77b6(0x240)), _0x5e9ee6 = !(0x195e + -0x5 * -0x371 + -0x3f * 0xad)); } catch (_0x4b2baa) { } var _0x462d0a = _0x52f064(_0x9fb121, 0x6a * 0x2 + -0x1b * 0x16b + 0x2576) , _0x136148 = _0x52f064(_0x191fa5, -0x94c + -0xd * 0x51 + 0xd6e, !(0x1 * 0x7ed + -0xab4 + 0x2c7)) , _0x17d8e6 = -0x2 * 0xb95 + -0xda0 + 0x24cb; !_0x5e9ee6 && _0x2786f5 && (_0x17d8e6 |= 0x2468 + -0x1 * 0x2c2 + -0x2166, _0x499168 |= _0x216650[_0x3f77b6(0x2f2)]), -0x293 * -0x3 + 0x1 * 0xdad + -0x1566 === _0x9fb121['length'] ? (_0x17d8e6 |= -0x1cf3 * 0x1 + 0x12e1 + 0xa14, _0x499168 |= _0x216650[_0x3f77b6(0x2fe)]) : _0x462d0a[-0x17f8 + -0x1eae + 0x36a6] > 0xbab + -0x1fb9 + 0x3 * 0x6c0 && (_0x17d8e6 |= 0x20 * -0x5c + 0xb22 + 0x6e, _0x499168 |= _0x216650['kMoveFast']), 0x2297 * -0x1 + 0x57b * 0x3 + 0x65 * 0x2e === _0x207cc5['length'] && (_0x17d8e6 |= 0x15a5 + 0x557 + -0x1af8, _0x499168 |= _0x216650['kNoClickTouch']), 0x20bb + 0x1e5f + 0x1f8d * -0x2 === _0x191fa5[_0x3f77b6(0x259)] ? (_0x17d8e6 |= -0x1 * 0x1b73 + -0x43e + 0x1fb9, _0x499168 |= _0x216650['kNoKeyboardEvent']) : _0x136148[0x16ee + -0x1 * 0x1e99 + 0x7ab] > 0x1 * 0xb77 + -0x82 + -0xaf5 + 0.5 && (_0x17d8e6 |= 0x176a + 0x183 + 0x7 * -0x38b, _0x499168 |= _0x216650[_0x3f77b6(0x3bf)]), _0xe06992['ubcode'] = _0x499168; var _0x9340b9 = _0x17d8e6[_0x3f77b6(0x3ae)](-0x2018 + -0x543 * 0x7 + 0x450d); return 0xc8e * 0x1 + -0x3 * 0x3ca + -0x12f === _0x9340b9['length'] ? _0x9340b9 = '00' + _0x9340b9 : -0x117a + 0x19 * -0x57 + 0x8a9 * 0x3 === _0x9340b9[_0x3f77b6(0x259)] && (_0x9340b9 = '0' + _0x9340b9), _0x9340b9; } function _0x5047d8() { _0x450b73(-0x1b05 + 0x103 * 0x1c + -0x4 * 0x53); } function _0x4145f8(_0x30c511, _0x35c283) { var _0x46d631 = _0x5612de; for (var _0x3d95be = _0x35c283[_0x46d631(0x259)], _0x3c9714 = new ArrayBuffer(_0x3d95be + (-0x2ef * -0x2 + 0x1bbd + 0x2e * -0xbb)), _0x1054b9 = new Uint8Array(_0x3c9714), _0x5633c2 = -0x114 + -0x2096 + 0x21aa, _0x28d040 = 0x10a * -0x1a + 0x5 * 0x5cf + -0x207 * 0x1; _0x28d040 < _0x3d95be; _0x28d040++) _0x1054b9[_0x28d040] = _0x35c283[_0x28d040], _0x5633c2 ^= _0x35c283[_0x28d040]; _0x1054b9[_0x3d95be] = _0x5633c2; var _0x280d0b = -0xe * -0x98 + 0x1f * 0x9e + 0x6f * -0x3d & Math[_0x46d631(0x1cf)]((-0x1 * 0x13ed + -0x2 * 0x581 + 0x1fee) * Math[_0x46d631(0x18e)]()) , _0x418204 = String[_0x46d631(0x1e2)][_0x46d631(0x207)](null, _0x1054b9) , _0x250194 = _0x21db29(String[_0x46d631(0x1e2)](_0x280d0b), _0x418204) , _0x37237c = ''; return _0x37237c += String[_0x46d631(0x1e2)](_0x30c511), _0x37237c += String[_0x46d631(0x1e2)](_0x280d0b), _0x389396(_0x37237c += _0x250194, 's1'); } function _0x1633f2(_0x48f290, _0x504655, _0x4fa808, _0x1a5c57, _0x3a4737) { var _0x5a9e67 = _0x5612de; _0x5863d1(), _0x26d461(), void (0x247a + -0x3f2 + -0x2088) !== _0x1a5c57 && '' !== _0x1a5c57 && (_0x1a5c57 = ''); var _0x21ca02 = _0x5dd467(_0x1a5c57); _0x3a4737 || (_0x3a4737 = '00000000000000000000000000000000'); var _0x1fb174 = new ArrayBuffer(0x1cc4 + -0x1109 + -0x3e6 * 0x3) , _0x1ffaa7 = new Uint8Array(_0x1fb174) , _0xeefda2 = 0x22cb + 0x1 * -0x32c + 0x1f9f * -0x1 | _0x48f290 << -0x228e + 0x22f4 * 0x1 + -0x60 | _0x504655 << 0x1075 + -0x2c * -0x26 + -0x16f8 | (-0x261a + 0x1e95 + 0x786 & Math[_0x5a9e67(0x1cf)]((0x705 + 0x1069 + -0x3 * 0x7ae) * Math[_0x5a9e67(0x18e)]())) << 0x3f4 * 0x5 + 0x59a + -0x195a | -0x1 * -0x2033 + 0x284 * 0x5 + 0xeed * -0x3; _0x6caf[_0x5a9e67(0x326)]++; _0x6caf['envcode'] = 1 var _0x5eb39b = -0x55 + 0x8a3 + -0x80f & _0x6caf[_0x5a9e67(0x326)]; _0x1ffaa7[0x18e0 * 0x1 + 0xd8d + -0x266d] = _0x4fa808 << -0x1 * 0x1117 + -0x144f + 0x256c | _0x5eb39b, _0x1ffaa7[-0x177d + 0x3 * 0xb39 + -0xa2d] = _0x6caf['envcode'] >> -0x163b + 0x2c3 + 0x1380 & 0x15b3 + -0x1efd + 0xa49 * 0x1, _0x1ffaa7[-0x5e3 + -0x1 * 0x1535 + 0x1b1a * 0x1] = 0x64a + -0xc * 0xa3 + 0x259 & _0x6caf[_0x5a9e67(0x380)], _0x1ffaa7[0x81 * 0x11 + 0xa49 * 0x1 + -0x12d7] = _0xe06992[_0x5a9e67(0x382)]; var _0x13f487 = _0x42e709[_0x5a9e67(0x2b7)](_0x5dd467(_0x42e709[_0x5a9e67(0x2b7)](_0x21ca02))); _0x1ffaa7[-0xf6b * 0x2 + 0x1c4 + 0x1d16] = _0x13f487[-0x81a + -0x24ee + -0x18e * -0x1d], _0x1ffaa7[0x73d * -0x3 + 0x2335 + -0x1 * 0xd79] = _0x13f487[-0x16 * -0x4 + -0xfdd + 0x3e5 * 0x4]; var _0x136ce1 = _0x42e709['decode'](_0x5dd467(_0x42e709['decode'](_0x3a4737))); return _0x1ffaa7[0x17 * -0x35 + 0x220e + -0x1d45] = _0x136ce1[0xe7 * -0x11 + 0x1 * -0x17a1 + 0x2706], _0x1ffaa7[0x1b8 + -0x2 * -0x665 + 0x151 * -0xb] = _0x136ce1[-0x276 * 0x9 + -0x1335 + -0x39 * -0xba], _0x1ffaa7[0x1fa8 + 0x776 + 0x1 * -0x2716] = -0x23cd + -0x69d + 0x2b69 & Math[_0x5a9e67(0x1cf)]((0x9c + 0x77b * 0x4 + -0x1 * 0x1d89) * Math[_0x5a9e67(0x18e)]()), _0x4145f8(_0xeefda2, _0x1ffaa7); } function _0x34c70a(_0xf6b3d0, _0x289075, _0x2c48ed) { var _0x1fe583 = _0x5612de; return { 'X-Bogus': _0x1633f2(_0x2e10da[_0x1fe583(0x289)], _0x462335['initialized'], _0xf6b3d0, null, _0x2c48ed) }; } function _0x11233a(_0x34d47b, _0x1b3ba5, _0x55dcd4) { var _0x3e598d = _0x5612de; return { 'X-Bogus': _0x1633f2(_0x2e10da[_0x3e598d(0x1df)], _0x462335[_0x3e598d(0x1fc)], _0x34d47b, _0x1b3ba5, _0x55dcd4) }; } function _0x5c2014(_0x1fa689) { return w_0x5c3140('484e4f4a403f524300362d0a5f00233c0000000029b6a730000000630214000103001400020700001400030700011400041101031100031347000d11010311000313140001450023110103110004134700130211010011010311000413430114000145000607000214000102110101110002021100014303140005110005420003096b1e7e601e606766710c6b1e7e601e63726a7f7c7277200303030303030303030303030303030303030303030303030303030303030303', { get 0x0() { return _0x5dd467; }, get 0x1() { return _0x34c70a; }, 0x2: arguments, 0x3: _0x1fa689 }, this); } function _0x3c875d(_0x17e2b2, _0x1e7967) { var _0x36f0c4 = _0x5612de , _0x292251 = new Uint8Array(-0x1d76 + 0xa81 * -0x3 + 0x3cfc); return _0x292251[0xff * 0x25 + 0x1ee0 * -0x1 + -0x5fb] = _0x17e2b2 / (0x1126 * -0x2 + -0x1 * 0x20f6 + -0x2 * -0x2221), _0x292251[0x29 * -0xde + 0x25a6 + 0x6b * -0x5] = _0x17e2b2 % (-0x5 * 0x705 + 0x10bd + -0xb1 * -0x1c), _0x292251[-0x1 * -0x1fb5 + -0x1921 + -0x692] = _0x1e7967 % (0x1615 + -0xda0 + 0x1 * -0x775), String[_0x36f0c4(0x1e2)][_0x36f0c4(0x207)](null, _0x292251); } function _0x4b49f3(_0xe64465) { var _0x2fa114 = _0x5612de; return String[_0x2fa114(0x1e2)](_0xe64465); } function _0x26151b(_0x51896f, _0x3a647f, _0x2db6f6) { return _0x4b49f3(_0x51896f) + _0x4b49f3(_0x3a647f) + _0x2db6f6; } function _0xc38697(_0x5bf566, _0x20667e) { return _0x389396(_0x5bf566, _0x20667e); } function _0x538c80(_0x213380, _0x5bb06d, _0x2807a8, _0x2bbe8d, _0x43c759, _0x510d57, _0x5b433d, _0x30b81d, _0x392407, _0x124f15, _0x22fc4c, _0x56f654, _0x8ab411, _0x1c9de4, _0x24f978, _0x27f46c, _0x1bd4f9, _0x572854, _0x34f6a0) { var _0x2e1799 = _0x5612de , _0x432584 = new Uint8Array(0x22bb + 0xd * -0x2bd + 0x1 * 0xf1); return _0x432584[-0xc70 + 0x5e9 + 0x22d * 0x3] = _0x213380, _0x432584[-0x2 * -0x127c + -0x1 * 0x1f61 + -0x596] = _0x22fc4c, _0x432584[-0x1af * 0xb + 0x97b + 0xc1 * 0xc] = _0x5bb06d, _0x432584[-0xfc5 * -0x2 + -0x5e5 + -0x19a2] = _0x56f654, _0x432584[-0x35 * 0xa3 + -0x10c9 * -0x2 + 0x1 * 0x31] = _0x2807a8, _0x432584[0x1d1d + 0x124a + -0x1 * 0x2f62] = _0x8ab411, _0x432584[-0x1 * -0xb49 + -0x5d1 + -0x572] = _0x2bbe8d, _0x432584[-0x18e * -0xe + 0xff * 0x22 + -0x379b] = _0x1c9de4, _0x432584[-0x697 * 0x2 + 0xcc8 + 0x5 * 0x16] = _0x43c759, _0x432584[0x2 * 0x8bf + 0xcc4 + -0x1e39] = _0x24f978, _0x432584[0x63 * -0x43 + -0x242 + 0x3 * 0x967] = _0x510d57, _0x432584[0x1aa9 + 0xe38 + 0x1 * -0x28d6] = _0x27f46c, _0x432584[-0xdee + -0x1 * 0x105b + 0x1e55 * 0x1] = _0x5b433d, _0x432584[0x1 * -0x1543 + -0x1 * 0x1b5b + 0x1 * 0x30ab] = _0x1bd4f9, _0x432584[0xc86 * -0x1 + 0x1c9 + 0xacb] = _0x30b81d, _0x432584[0x1581 + -0x70c + -0xe66] = _0x572854, _0x432584[0x1dc1 + 0xd * 0xb5 + -0x26e2] = _0x392407, _0x432584[-0x36 * 0x4a + 0x268a + -0x16dd] = _0x34f6a0, _0x432584[0xf * -0x1ca + -0x16c2 + 0x31aa] = _0x124f15, String['fromCharCode'][_0x2e1799(0x207)](null, _0x432584); } var _0x3c4305 = !(-0xc6b + 0x270e * 0x1 + -0x1aa2); function _0x8edc3d(_0x22218f, _0x1e70b9) { var _0x364732 = _0x5612de; return w_0x5c3140(_0x364732(0x2b2), { get 0x0() { return _0x5dd467; }, get 0x1() { return _0x3c4305; }, set 0x1(_0x539e8c) { _0x3c4305 = _0x539e8c; }, get 0x2() { return _0x462335; }, get 0x3() { return _0x5863d1; }, get 0x4() { return _0x26d461; }, get 0x5() { return _0xe06992; }, get 0x6() { return _0x6caf; }, get 0x7() { return _0x42e709; }, 0x8: String, get 0x9() { return navigator; }, get 0xa() { return _0x3c875d; }, get 0xb() { return _0x21db29; }, get 0xc() { return _0xc38697; }, 0xd: Date, get 0xe() { return _0x18b4be; }, get 0xf() { return _0x538c80; }, get 0x10() { return _0x4b49f3; }, get 0x11() { return _0x26151b; }, get 0x12() { return _0x389396; }, 0x13: arguments, 0x14: _0x22218f, 0x15: _0x1e70b9, 0x16: RegExp }, this); } function _0x556182(_0x5e8c32) { var _0x4a1328 = _0x5612de; _0x4a2daf(_0x4a1328(0x28a), _0x5e8c32); } function _0x5141ac() { var _0x2770a6 = _0x3d13cf('xmst'); return _0x2770a6 || ''; } function _0x50686a(_0x193aca) { var _0x460363 = _0x5612de; return _0x460363(0x331) === Object[_0x460363(0x344)][_0x460363(0x3ae)][_0x460363(0x334)](_0x193aca); } function _0x2195cd(_0x383b5f, _0x4a7858) { var _0x20b490 = _0x5612de; if (_0x383b5f) { var _0x11f5aa = _0x383b5f[_0x4a7858]; if (_0x11f5aa) { var _0x4fcde1 = _0x1db123(_0x11f5aa); return _0x20b490(0x17d) === _0x4fcde1 || _0x20b490(0x1ee) === _0x4fcde1 ? -0x1 * -0x4e8 + 0x18eb + -0x1dd2 : 'string' === _0x4fcde1 ? _0x4fcde1[_0x20b490(0x259)] > -0x124c + 0x20c3 + -0xe77 ? 0x35 * 0x34 + 0x1ec8 + -0x298b : -0x8a * 0x31 + -0x18c7 + -0x1111 * -0x3 : _0x50686a(_0x11f5aa) ? -0x1 * -0xc83 + -0x3 * 0x466 + 0x4 * 0x2c : 0x1018 + -0x371 * -0x9 + 0x1 * -0x2f0f; } } return -0x155a + 0x4c * -0x6c + 0x4 * 0xd5b; } function _0xdc4d4c(_0x3c0aaa) { var _0x11952c = _0x5612de; try { var _0x57243b = Object[_0x11952c(0x344)][_0x11952c(0x3ae)][_0x11952c(0x334)](_0x3c0aaa); return _0x11952c(0x30e) === _0x57243b ? !(-0x1 * 0x7c5 + 0x1 * -0x853 + 0x338 * 0x5) === _0x3c0aaa ? -0xa1 * -0x23 + 0x2 * 0xaa9 + -0x2b54 : -0x1e71 * 0x1 + 0x1 * 0x1885 + 0x5ee : _0x11952c(0x1af) === _0x57243b ? -0xce * 0x25 + 0x1e1a + 0x3 * -0x1b : '[object\x20Undefined]' === _0x57243b ? -0x1 * 0x159a + -0x684 + 0x1c22 : _0x11952c(0x1ce) === _0x57243b ? -0x3a * -0x70 + 0x4b1 + 0xa04 * -0x3 : '[object\x20String]' === _0x57243b ? '' === _0x3c0aaa ? 0x12 * 0xad + 0x3b * 0x71 + -0x262e : 0x3 * 0xf8 + 0x22ec + -0x25cc : _0x11952c(0x331) === _0x57243b ? -0x1 * -0x874 + -0x1 * 0x2197 + 0x1923 === _0x3c0aaa['length'] ? -0xf6f + -0x1ee3 + 0x2e5b : -0x10c3 + -0x466 * -0x4 + 0x7 * -0x1d : _0x11952c(0x37b) === _0x57243b ? 0x1 * 0x22e6 + 0x1 * 0x2669 + 0x824 * -0x9 : _0x11952c(0x1fe) === _0x57243b ? 0x2392 + -0x15db + -0xdab : _0x11952c(0x17d) === _0x1db123(_0x3c0aaa) ? -0x3 * 0xd7 + -0x17b0 + 0x1a98 : -(-0x8e4 + 0x2 * 0xd1b + -0x1151); } catch (_0x320465) { return -(-0x62e * 0x1 + 0x3 * -0x3ec + 0x11f4); } } var _0x2d8bb4 = {}; function _0x241339() { var _0x5d55e6 = _0x5612de; return document[_0x5d55e6(0x373)] ? 'IE' : -0xc04 + -0xbe8 + 0x17ec; } function _0x5011f8() { var _0x52b3d6 = _0x5612de; return eval['toString']()[_0x52b3d6(0x259)]; } function _0x58210c(_0x4d43be, _0x3faa08, _0x3d51f5) { var _0x16b761 = _0x5612de; for (var _0x4738ee = {}, _0x793fa7 = 0x111 + -0x209 * 0x1 + 0x1f * 0x8; _0x793fa7 < _0x3faa08[_0x16b761(0x259)]; _0x793fa7++) { var _0x15231e = void (-0xf02 * -0x1 + 0xe35 + -0x1d37) , _0x2d01fa = void (-0x782 + 0x5 * -0x143 + 0xdd1) , _0x17bcc5 = _0x3faa08[_0x793fa7]; try { _0x4d43be && (_0x15231e = _0x4d43be[_0x17bcc5]); } catch (_0x1de94f) { } if ('string' === _0x3d51f5) _0x2d01fa = '' + _0x15231e; else { if (_0x16b761(0x28b) === _0x3d51f5) _0x2d01fa = _0x15231e ? Math[_0x16b761(0x1cf)](_0x15231e) : -(-0x19f0 + 0x1ada + 0xe9 * -0x1); else { if (_0x16b761(0x1ab) !== _0x3d51f5) throw Error(_0x16b761(0x256)); _0x2d01fa = _0xdc4d4c(_0x15231e); } } _0x4738ee[_0x17bcc5] = _0x2d01fa; } return _0x4738ee; } function _0x3a2b92() { var _0x53dacb = _0x5612de, _0x1c216f; Object[_0x53dacb(0x2a0)](_0x2d8bb4[_0x53dacb(0x270)], _0x58210c(navigator, [_0x53dacb(0x36b), _0x53dacb(0x350), _0x53dacb(0x184), 'appVersion', _0x53dacb(0x32d), 'doNotTrack', _0x53dacb(0x39a), _0x53dacb(0x38c), _0x53dacb(0x17a), _0x53dacb(0x246), 'productSub', 'cpuClass', _0x53dacb(0x212), _0x53dacb(0x1d5), _0x53dacb(0x1de), _0x53dacb(0x210), _0x53dacb(0x314), _0x53dacb(0x1c9), 'webdriver'], 'string')), Object['assign'](_0x2d8bb4[_0x53dacb(0x270)], _0x58210c(navigator, [_0x53dacb(0x1e1), 'vibrate', _0x53dacb(0x2e2), _0x53dacb(0x292), _0x53dacb(0x390), _0x53dacb(0x38f)], _0x53dacb(0x1ab))), Object[_0x53dacb(0x2a0)](_0x2d8bb4[_0x53dacb(0x270)], _0x58210c(navigator, [_0x53dacb(0x360), _0x53dacb(0x33b)], _0x53dacb(0x28b))), _0x2d8bb4['navigator'][_0x53dacb(0x398)] = '' + navigator[_0x53dacb(0x398)]; try { document[_0x53dacb(0x188)](_0x53dacb(0x240)), _0x1c216f = -0xa2 * -0x5 + 0x26 * -0xe8 + -0x11 * -0x1d7; } catch (_0x264380) { _0x1c216f = 0xbc0 + -0x26b + -0xd9 * 0xb; } _0x2d8bb4['navigator'][_0x53dacb(0x34b)] = _0x1c216f; var _0x5ac61e = _0x53dacb(0x1ea) in window ? 0x2424 + 0x1 * 0xa5c + -0x2e7f : -0x86b * 0x1 + -0x195 + 0xa02; _0x2d8bb4[_0x53dacb(0x270)][_0x53dacb(0x1d6)] = _0x5ac61e; } function _0x5abc93() { var _0x279235 = _0x5612de; Object['assign'](_0x2d8bb4['window'], _0x58210c(window, ['Image', _0x279235(0x309), _0x279235(0x29f), _0x279235(0x399), _0x279235(0x33a), 'external', 'mozRTCPeerConnection', 'postMessage', _0x279235(0x284), _0x279235(0x2de), _0x279235(0x2ff), _0x279235(0x1dc), _0x279235(0x3be), _0x279235(0x2e9)], _0x279235(0x1ab))), Object[_0x279235(0x2a0)](_0x2d8bb4[_0x279235(0x393)], _0x58210c(window, [_0x279235(0x3c0)], _0x279235(0x28b))), _0x2d8bb4[_0x279235(0x393)][_0x279235(0x3c2)] = window[_0x279235(0x3c2)][_0x279235(0x290)]; } function _0x5d3845() { var _0x4ceb6d = _0x5612de; try { var _0x55434c = document , _0x2f0012 = window[_0x4ceb6d(0x328)] , _0x5ac16c = window[_0x4ceb6d(0x306)] >>> 0x1d2c + -0x21 * 0xa7 + -0x1 * 0x7a5 , _0x44cace = window[_0x4ceb6d(0x222)] >>> -0xcd0 + -0x1f70 + 0x162 * 0x20 , _0x5953b9 = window[_0x4ceb6d(0x1aa)] >>> 0x5 * 0x2e3 + -0x1 * -0x1f7 + -0x1 * 0x1066 , _0x1dfce9 = window[_0x4ceb6d(0x26d)] >>> 0x1545 + -0x8d7 + -0xc6e , _0x29911c = Math[_0x4ceb6d(0x1cf)](window['screenX']) , _0x340972 = Math['floor'](window[_0x4ceb6d(0x366)]) , _0x4bc0b8 = window[_0x4ceb6d(0x258)] >>> -0x1 * -0x1548 + -0x21 * -0x7 + -0x162f , _0x39df7d = window[_0x4ceb6d(0x282)] >>> 0x1dc7 * 0x1 + -0x2113 + 0x34c , _0x13b30d = _0x2f0012['availWidth'] >>> 0xc84 + 0x1 * -0x644 + -0x640 , _0x3f047e = _0x2f0012[_0x4ceb6d(0x276)] >>> -0x23c + 0xc * 0x1c4 + -0x12f4 * 0x1 , _0x3e7595 = _0x2f0012['width'] >>> 0x61 * 0x4 + -0x2469 + 0x22e5 * 0x1 , _0xa6e264 = _0x2f0012[_0x4ceb6d(0x245)] >>> 0x882 + 0x2646 + -0x5d9 * 0x8; return { 'innerWidth': void (-0xa40 + 0x192e + -0x31 * 0x4e) !== _0x5ac16c ? _0x5ac16c : -(0x135c + -0xd * -0x115 + -0x45 * 0x7c), 'innerHeight': void (-0x2176 + -0x281 * -0xc + 0x17 * 0x26) !== _0x44cace ? _0x44cace : -(-0x17a1 + 0xe8 * 0x8 + 0x576 * 0x3), 'outerWidth': void (-0x167c + -0x6f * 0x49 + 0x3623) !== _0x5953b9 ? _0x5953b9 : -(0x3 * -0x795 + 0x4 * -0x53b + 0x2bac), 'outerHeight': void (-0x26 * -0x5f + -0xf71 + -0x1 * -0x157) !== _0x1dfce9 ? _0x1dfce9 : -(0xebd + -0x18cd + 0x3 * 0x35b), 'screenX': void (-0xf9 * -0x22 + 0xd3 * -0x2c + 0x332) !== _0x29911c ? _0x29911c : -(-0x188 + -0x24e3 + 0x266c), 'screenY': void (-0x5 * -0x105 + 0x2 * 0x11fe + -0x2915) !== _0x340972 ? _0x340972 : -(-0x3cc * -0x6 + 0x1561 * 0x1 + -0x2c28), 'pageXOffset': void (0xb15 + -0x64d * 0x5 + -0x2 * -0xa36) !== _0x4bc0b8 ? _0x4bc0b8 : -(0x647 + -0x1 * -0x1ced + -0x2333), 'pageYOffset': void (-0x1337 * 0x1 + -0x10c1 + -0x1 * -0x23f8) !== _0x39df7d ? _0x39df7d : -(0x3b * 0x5b + -0x7 * 0x3a6 + 0x492), 'availWidth': void (-0xd * 0x267 + 0x1a67 + -0x2 * -0x26a) !== _0x13b30d ? _0x13b30d : -(0xe48 + -0x7d * -0x23 + -0x1f5e), 'availHeight': void (-0x192a + 0x5 * -0x5b3 + 0xf1 * 0x39) !== _0x3f047e ? _0x3f047e : -(0x4 * 0x5b + -0x3 * -0x13 + -0x1a4), 'sizeWidth': void (-0x1a38 * 0x1 + -0x20cb + 0x1 * 0x3b03) !== _0x3e7595 ? _0x3e7595 : -(-0x663 + -0x2659 + 0x2cbd * 0x1), 'sizeHeight': void (0x857 * 0x3 + -0x65 * -0xa + -0x1cf7) !== _0xa6e264 ? _0xa6e264 : -(0xafa + 0x305 * 0x5 + -0x1a12), 'clientWidth': _0x55434c['body'] ? _0x55434c[_0x4ceb6d(0x189)][_0x4ceb6d(0x1a2)] >>> -0xe38 + 0x1839 + -0xa01 : -(0x1338 + 0x21c1 + 0x69f * -0x8), 'clientHeight': _0x55434c[_0x4ceb6d(0x189)] ? _0x55434c[_0x4ceb6d(0x189)][_0x4ceb6d(0x3bb)] >>> -0x1 * 0x156 + 0x31 * 0x43 + -0xb7d : -(0x1202 + -0x32b + -0xed6), 'colorDepth': _0x2f0012[_0x4ceb6d(0x17e)] >>> -0x1d * -0xcb + 0x197 * 0x11 + 0x26 * -0x151, 'pixelDepth': _0x2f0012[_0x4ceb6d(0x30a)] >>> -0x254c + 0x469 + 0x20e3 * 0x1 }; } catch (_0x35aa5a) { return {}; } } function _0x573065() { var _0x109cfa = _0x5612de; Object[_0x109cfa(0x2a0)](_0x2d8bb4[_0x109cfa(0x208)], _0x58210c(document, [_0x109cfa(0x335), _0x109cfa(0x253), 'documentMode'], _0x109cfa(0x33c))), Object['assign'](_0x2d8bb4[_0x109cfa(0x208)], _0x58210c(document, [_0x109cfa(0x325), _0x109cfa(0x183), 'images'], _0x109cfa(0x1ab))); } function _0x47f354() { var _0xad51a5 = _0x5612de , _0x5ad193 = {}; try { var _0x344158 = document[_0xad51a5(0x260)](_0xad51a5(0x24d))[_0xad51a5(0x2f9)](_0xad51a5(0x26b)) , _0x45ab53 = _0x344158[_0xad51a5(0x1d7)](_0xad51a5(0x375)) , _0x3724de = _0x344158['getParameter'](_0x45ab53['UNMASKED_VENDOR_WEBGL']) , _0x41fa79 = _0x344158[_0xad51a5(0x383)](_0x45ab53[_0xad51a5(0x39e)]); _0x5ad193['vendor'] = _0x3724de, _0x5ad193[_0xad51a5(0x2c2)] = _0x41fa79; } catch (_0x22684e) { } return _0x5ad193; } function _0x58bcf8() { var _0x3fa932 = _0x5612de , _0xf9ae75 = _0x11a1d6(); if (_0xf9ae75) { var _0x3c4406 = { 'antialias': _0xf9ae75[_0x3fa932(0x1c5)]()[_0x3fa932(0x1a5)] ? -0x1d2e + -0x4b1 + 0x21e0 : -0x1 * -0x87c + 0x4b * 0x49 + -0x5 * 0x5f9, 'blueBits': _0xf9ae75[_0x3fa932(0x383)](_0xf9ae75[_0x3fa932(0x27e)]), 'depthBits': _0xf9ae75['getParameter'](_0xf9ae75[_0x3fa932(0x252)]), 'greenBits': _0xf9ae75[_0x3fa932(0x383)](_0xf9ae75['GREEN_BITS']), 'maxAnisotropy': _0x39c3d8(_0xf9ae75), 'maxCombinedTextureImageUnits': _0xf9ae75[_0x3fa932(0x383)](_0xf9ae75['MAX_COMBINED_TEXTURE_IMAGE_UNITS']), 'maxCubeMapTextureSize': _0xf9ae75[_0x3fa932(0x383)](_0xf9ae75[_0x3fa932(0x300)]), 'maxFragmentUniformVectors': _0xf9ae75[_0x3fa932(0x383)](_0xf9ae75[_0x3fa932(0x286)]), 'maxRenderbufferSize': _0xf9ae75[_0x3fa932(0x383)](_0xf9ae75[_0x3fa932(0x2da)]), 'maxTextureImageUnits': _0xf9ae75[_0x3fa932(0x383)](_0xf9ae75['MAX_TEXTURE_IMAGE_UNITS']), 'maxTextureSize': _0xf9ae75[_0x3fa932(0x383)](_0xf9ae75[_0x3fa932(0x203)]), 'maxVaryingVectors': _0xf9ae75['getParameter'](_0xf9ae75[_0x3fa932(0x264)]), 'maxVertexAttribs': _0xf9ae75[_0x3fa932(0x383)](_0xf9ae75['MAX_VERTEX_ATTRIBS']), 'maxVertexTextureImageUnits': _0xf9ae75['getParameter'](_0xf9ae75[_0x3fa932(0x205)]), 'maxVertexUniformVectors': _0xf9ae75['getParameter'](_0xf9ae75[_0x3fa932(0x19d)]), 'shadingLanguageVersion': _0xf9ae75[_0x3fa932(0x383)](_0xf9ae75[_0x3fa932(0x179)]), 'stencilBits': _0xf9ae75['getParameter'](_0xf9ae75[_0x3fa932(0x2bb)]), 'version': _0xf9ae75[_0x3fa932(0x383)](_0xf9ae75[_0x3fa932(0x201)]) }; Object['assign'](_0x2d8bb4['webgl'], _0x3c4406); } Object[_0x3fa932(0x2a0)](_0x2d8bb4[_0x3fa932(0x26b)], _0x47f354()); } function _0x75957() { var _0x122393 = _0x5612de; if (window[_0x122393(0x29f)]) { for (var _0x2963d5 = -0x81a + -0xad * -0x1 + 0x76f; _0x2963d5 < -0x1629 + 0x1 * 0x31c + 0x1317; _0x2963d5++) try { return !!new window[(_0x122393(0x29f))](_0x122393(0x3b4) + _0x2963d5) && _0x2963d5[_0x122393(0x3ae)](); } catch (_0x2c5722) { } try { return !!new window[(_0x122393(0x29f))]('PDF.PdfCtrl.1') && '4'; } catch (_0x1be01e) { } try { return !!new window[(_0x122393(0x29f))](_0x122393(0x345)) && '7'; } catch (_0x19cffa) { } } return '0'; } function _0x1555d9() { return { 'plugin': _0x30412e(), 'pv': _0x75957() }; } function _0x1f01ce(_0x371dd1) { var _0x5c76bb = _0x5612de; try { var _0xf71d16 = window[_0x371dd1] , _0x28a4d1 = _0x5c76bb(0x1f9); return _0xf71d16[_0x5c76bb(0x1c4)](_0x28a4d1, _0x28a4d1), _0xf71d16[_0x5c76bb(0x2c3)](_0x28a4d1), !(0x1e7a + -0x1824 + 0x2 * -0x32b); } catch (_0x1dd803) { return !(0x6e5 + 0x599 * -0x2 + -0x1 * -0x44e); } } function _0x3ffe15() { return w_0x5c3140('484e4f4a403f5243003c20117d3adeac000000004e770f390000003a030014000102110100070000430147000b11000103012f170001354902110100070001430147000e110001030103012b2f17000135491100014200020c45464a48457a5d465b484e4c0e5a4c5a5a4046477a5d465b484e4c', { get 0x0() { return _0x1f01ce; }, 0x1: arguments }, this); } function _0x252788(_0x468bb4, _0x392758, _0x269720) { var _0x16947c = _0x5612de; for (var _0x4dea11 = 0x1 * 0x1705 + 0x29 * -0xbf + 0x72 * 0x11, _0x1f88e5 = 0x65 * -0x7 + 0xb50 + -0x88d; _0x1f88e5 < _0x392758['length']; _0x1f88e5++) { var _0x1dd5f9 = _0x2195cd(_0x468bb4, _0x392758[_0x1f88e5]); if (_0x1dd5f9 && (_0x4dea11 |= _0x1dd5f9 << _0x269720 + _0x1f88e5, _0x269720 + _0x1f88e5 >= 0xd5a + 0x7c + -0x1 * 0xdb6)) { console[_0x16947c(0x310)]('abort\x2032'); break; } } return _0x4dea11; } function _0x484054() { return w_0x5c3140('484e4f4a403f5243002c3b0a6f4f88290000000044c410000000011f1101001400010700000700010700020700030700040700050700060700070700080700090c000a14000207000a14000307000b14000407000a110101110004163e000414000a413d00d11100014a07000c1307000d43010300131400050c0000140006030014000711000711000207000e13274700691100014a07000f130700104301140008110002110007131400091100084a0700111307001207001311000918430249110004070014181100091807001518110008070016161100054a070017131100084301491100064a07001813110008430149170007214945ff891101011100041317000335490300170007354911000711000207000e132747001a1100054a0700191311000611000713430149170007214945ffd84111000342001a037e617e037e617d037e617c037e617b037e617a037e6179037e6178037e6177037e6176037d617f0014262b20213b242120382138272e3b263c3b27263c14282a3b0a232a222a213b3c0d361b2e28012e222a04272a2e2b06232a21283b270d2c3d2a2e3b2a0a232a222a213b063c2c3d263f3b0c3c2a3b0e3b3b3d262d3a3b2a08232e21283a2e282a0a052e392e1c2c3d263f3b02726d016d043b2a373b0b2e3f3f2a212b0c2726232b043f3a3c270b3d2a2220392a0c2726232b', { get 0x0() { return document; }, get 0x1() { return window; }, 0x2: arguments }, this); } _0x2d8bb4[_0x5612de(0x270)] = {}, _0x2d8bb4[_0x5612de(0x23e)] = {}, _0x2d8bb4[_0x5612de(0x393)] = {}, _0x2d8bb4[_0x5612de(0x26b)] = {}, _0x2d8bb4['document'] = {}, _0x2d8bb4[_0x5612de(0x328)] = {}, _0x2d8bb4[_0x5612de(0x1ba)] = {}, _0x2d8bb4['custom'] = {}; var _0x548676 = null; function _0x491716() { var _0x4f58c9 = _0x5612de; return w_0x5c3140(_0x4f58c9(0x174), { get 0x0() { return self; }, get 0x1() { return window; }, get 0x2() { return parent; }, 0x3: arguments }, this); } function _0x2d2578() { !(function () { var _0x236733 = w_0x25f3 , _0x460c07 = {} , _0x3606e1 = navigator[_0x236733(0x359)] || navigator[_0x236733(0x2f0)]; if (_0x3606e1) { try { _0x460c07[_0x236733(0x3ac)] = _0x3606e1[_0x236733(0x3ac)] ? 0x147a + -0x5 * 0x60f + 0x9d2 : -0x169c + 0x26d6 + -0x1038, _0x460c07[_0x236733(0x213)] = Math[_0x236733(0x369)]((0x31 * 0x75 + 0x14c7 + -0x2ac8) * _0x3606e1[_0x236733(0x213)]), _0x460c07[_0x236733(0x2b0)] = '' + _0x3606e1[_0x236733(0x2b0)], _0x460c07[_0x236733(0x191)] = '' + _0x3606e1['dischargingTime']; } catch (_0x2c301c) { } _0x2d8bb4[_0x236733(0x359)] = {}, Object[_0x236733(0x2a0)](_0x2d8bb4[_0x236733(0x359)], _0x460c07); } else { if (_0x236733(0x384) != typeof navigator && navigator[_0x236733(0x1b2)]) try { navigator[_0x236733(0x1b2)]()[_0x236733(0x1ed)](function (_0x369fc5) { var _0x34e3e9 = _0x236733; try { _0x460c07[_0x34e3e9(0x3ac)] = _0x369fc5[_0x34e3e9(0x3ac)] ? 0x20aa + -0x11 * -0x86 + 0x1 * -0x298f : 0xd51 + 0x152c + 0xd * -0x2a7, _0x460c07[_0x34e3e9(0x213)] = Math[_0x34e3e9(0x369)]((0x1205 + -0x41 * 0x32 + -0x3 * 0x1a5) * _0x369fc5[_0x34e3e9(0x213)]), _0x460c07[_0x34e3e9(0x2b0)] = '' + _0x369fc5[_0x34e3e9(0x2b0)], _0x460c07[_0x34e3e9(0x191)] = '' + _0x369fc5['dischargingTime']; } catch (_0x2f63d1) { } _0x2d8bb4['battery'] = {}, Object[_0x34e3e9(0x2a0)](_0x2d8bb4[_0x34e3e9(0x359)], _0x460c07); }); } catch (_0xfc012d) { } } }()), 'undefined' != typeof Promise && (_0x548676 = new Promise(function (_0x1df02e) { try { _0x5bbaf0()['then'](function (_0x27ab47) { var _0xbee19f = w_0x25f3; Object[_0xbee19f(0x2a0)](_0x2d8bb4['wID'], { 'rtcIP': _0x27ab47 }); }); } catch (_0x7bc12a) { } _0x1df02e(''); } )); } function _0x5c328e() { var _0x378ae2 = _0x5612de; return w_0x5c3140(_0x378ae2(0x1ad), { get 0x0() { return window; }, get 0x1() { return navigator; }, get 0x2() { var _0xa78dc5 = _0x378ae2; return _0xa78dc5(0x384) != typeof InstallTrigger ? InstallTrigger : void (0x26ae * 0x1 + -0xad * 0x2e + -0x6c * 0x12); }, 0x3: Object, get 0x4() { return _0x241339; }, get 0x5() { return _0x2d8bb4; }, get 0x6() { return document; }, 0x7: Promise, 0x8: Date, get 0x9() { return _0x252788; }, get 0xa() { return _0x5011f8; }, get 0xb() { return _0x4f323e; }, get 0xc() { return _0x5090f5; }, 0xd: Math, get 0xe() { return _0x3ffe15; }, get 0xf() { return _0x18b4be; }, get 0x10() { return _0x484054; }, get 0x11() { return _0x491716; }, get 0x12() { return _0x462335; }, get 0x13() { return _0x24dc34; }, get 0x14() { return _0x6caf; }, get 0x15() { return _0x2d2578; }, get 0x16() { return _0x3a2b92; }, get 0x17() { return _0x5abc93; }, get 0x18() { return _0x573065; }, get 0x19() { return _0x58bcf8; }, get 0x1a() { return _0x1555d9; }, get 0x1b() { return _0x5d3845; }, 0x1c: parseInt, get 0x1d() { return _0x3d13cf; }, get 0x1e() { return _0x4a2daf; }, get 0x1f() { return _0x641e3d; }, 0x20: JSON, get 0x21() { return _0x48a082; }, get 0x22() { return _0x4df596; }, get 0x23() { return _0x5dde58; }, get 0x24() { return _0x548676; }, get 0x25() { return _0x29a5ac; }, 0x26: arguments }, this); } function _0x25a792(_0x2f25f2) { var _0x329005 = _0x5612de; return _0x462335[_0x329005(0x185)] && _0x462335[_0x329005(0x185)][_0x329005(0x2dc)] && -(-0x4f2 * 0x5 + 0x254f + -0xc94) !== _0x2f25f2[_0x329005(0x2c5)](_0x462335[_0x329005(0x185)][_0x329005(0x2dc)]) ? _0x39693d['sec'] : _0x39693d[_0x329005(0x1b4)]; } function _0xd287a1(_0x35260d) { var _0x241816 = _0x5612de , _0x40ac17 = _0x462335[_0x241816(0x185)][_0x241816(0x2dc)]; return !(!_0x40ac17 || !_0x35260d || -(0x116 * -0x2 + -0x1af8 * -0x1 + -0x18cb) === _0x35260d[_0x241816(0x2c5)](_0x40ac17)); } function _0x2b13af(_0x52c947) { var _0x221375 = _0x5612de , _0x7eff84 = _0x52c947; decodeURIComponent(_0x52c947) === _0x52c947 && (_0x7eff84 = encodeURI(_0x52c947)); var _0x5e1d88 = _0x7eff84[_0x221375(0x2c5)]('?'); if (_0x5e1d88 > 0x1673 * 0x1 + 0x2150 + -0x37c3) { var _0xc7170 = _0x7eff84[_0x221375(0x3a7)](-0x80c + 0x71e * 0x2 + -0x630, _0x5e1d88 + (0x2 * 0x919 + 0x4 * -0x931 + 0x1293)) , _0x8fbad9 = _0x7eff84[_0x221375(0x3a7)](_0x5e1d88 + (-0x1de4 + -0x2 * -0x1163 + 0x4e1 * -0x1)); _0x7eff84 = _0xc7170 + _0x8fbad9[_0x221375(0x342)]('\x27')['join'](_0x221375(0x37e)); } return _0x7eff84; } function _0x1958a5(_0x42413b, _0x5e3de6) { var _0x406220 = _0x5612de; for (var _0x32045a = '', _0xbe63df = '', _0x5623ae = 0x5f6 + -0x220e + 0x1c18; _0x5623ae < _0x5e3de6[_0x406220(0x259)]; _0x5623ae++) _0x5623ae % (0x951 + 0xb15 + 0x105 * -0x14) == -0x10de + 0x15 * 0x185 + 0x1 * -0xf0b ? _0xbe63df = _0x5e3de6[_0x5623ae] : _0x32045a += '&' + _0xbe63df + '=' + _0x5e3de6[_0x5623ae]; var _0x4ddc33 = _0x42413b; if (_0x32045a[_0x406220(0x259)] > -0x135d + 0x8e1 + -0x2c * -0x3d) { var _0x50fb42 = -(0x2 * -0x1252 + 0x13 * 0x85 + 0x95 * 0x2e) === _0x42413b['indexOf']('?') ? '?' : '&'; _0x4ddc33 = _0x42413b + _0x50fb42 + _0x32045a[_0x406220(0x3a7)](0x324 + -0xf1 * -0x17 + 0x1 * -0x18ca); } return _0x4ddc33; } function _0x288415(_0x5a419e) { var _0x259f53 = _0x5612de , _0x4465ef = _0x5a419e['indexOf']('?'); return -(-0x78c + -0x1a4c + -0x6c5 * -0x5) !== _0x4465ef ? _0x5a419e[_0x259f53(0x3a7)](_0x4465ef + (0x907 + 0x137b * 0x1 + -0x1c81)) : ''; } function _0x6a7375(_0x2a9a57) { var _0x447596 = _0x5612de; for (var _0x1ca622 = -0x16a3 * -0x1 + 0x87b + -0x1f1e; _0x1ca622 < _0x462335['_enablePathListRegex']['length']; _0x1ca622++) if (_0x462335[_0x447596(0x1a9)][_0x1ca622]['test'](_0x2a9a57)) return !(-0x5ed + -0x3d * -0x41 + -0x990); return !(-0x1 * -0x26ff + -0x11c1 * 0x1 + -0x153d); } function _0x7d8404(_0x34967f) { var _0x5e578e = _0x5612de; return _0x5e578e(0x186) === _0x34967f || 'application/json' === _0x34967f; } function _0x3af1be() { var _0x1347f7 = _0x5612de; return w_0x5c3140(_0x1347f7(0x368), { get 0x0() { return window; }, get 0x1() { return _0x6a7375; }, get 0x2() { return _0x6caf; }, get 0x3() { return _0x1958a5; }, get 0x4() { return _0x2b13af; }, get 0x5() { return _0x288415; }, get 0x6() { return _0x8edc3d; }, get 0x7() { return _0x462335; }, get 0x8() { return _0x45e0e9; }, get 0x9() { return _0x7d8404; }, get 0xa() { return _0x1294ff; }, get 0xb() { return _0x20cbf3; }, get 0xc() { return _0x45b94b; }, get 0xd() { return _0x572e48; }, get 0xe() { return _0xd287a1; }, get 0xf() { return _0x25a792; }, get 0x10() { return _0x39693d; }, get 0x11() { return _0x1f42cb; }, get 0x12() { return _0x556182; }, get 0x13() { return setTimeout; }, get 0x14() { return _0x5c328e; }, 0x15: arguments, 0x16: RegExp }, this); } var _0x3c4266 = _0x5612de(0x384) != typeof URL && URL instanceof Object , _0x3311d7 = 'undefined' != typeof Request && Request instanceof Object , _0x4f1fa4 = 'undefined' != typeof Headers && Headers instanceof Object; function _0x415adb() { var _0x51ab32 = _0x5612de; return window[_0x51ab32(0x197)]; } function _0x1d82ac() { var _0x2340b7 = _0x5612de; return w_0x5c3140(_0x2340b7(0x234), { get 0x0() { return _0x415adb; }, get 0x1() { return window; }, get 0x2() { return _0xd287a1; }, get 0x3() { return _0x25a792; }, get 0x4() { return _0x39693d; }, get 0x5() { return _0x6caf; }, get 0x6() { return _0x1f42cb; }, get 0x7() { return _0x556182; }, get 0x8() { return setTimeout; }, get 0x9() { return _0x5c328e; }, get 0xa() { return _0x3311d7; }, get 0xb() { return Request; }, get 0xc() { return _0x3c4266; }, get 0xd() { return URL; }, get 0xe() { return _0x6a7375; }, get 0xf() { return _0x1958a5; }, get 0x10() { return _0x2b13af; }, get 0x11() { return _0x288415; }, get 0x12() { return _0x8edc3d; }, get 0x13() { return _0x462335; }, get 0x14() { return _0x45e0e9; }, get 0x15() { return _0xb48e77; }, get 0x16() { return _0x7d8404; }, get 0x17() { return _0x1294ff; }, get 0x18() { return _0x20cbf3; }, get 0x19() { return _0x45b94b; }, get 0x1a() { return _0x572e48; }, 0x1b: arguments }, this); } function _0xb48e77(_0x29caaf, _0xa4ab82) { var _0x1cf8d6 = _0x5612de , _0x314297 = ''; if (_0x3311d7 && _0x29caaf instanceof Request) { var _0x1f374d = _0x29caaf[_0x1cf8d6(0x35b)][_0x1cf8d6(0x32b)](_0x1cf8d6(0x228)); return _0x1f374d && (_0x314297 = _0x1f374d), _0x314297; } if (_0xa4ab82 && _0xa4ab82[_0x1cf8d6(0x35b)]) { if (_0x4f1fa4 && _0xa4ab82[_0x1cf8d6(0x35b)] instanceof Headers) { var _0x4dff82 = _0xa4ab82[_0x1cf8d6(0x35b)][_0x1cf8d6(0x32b)](_0x1cf8d6(0x228)); return _0x4dff82 && (_0x314297 = _0x4dff82), _0x314297; } if (_0xa4ab82['headers'] instanceof Array) { for (var _0x6ae47c = 0xd20 + 0x699 * -0x5 + -0x13dd * -0x1; _0x6ae47c < _0xa4ab82['headers'][_0x1cf8d6(0x259)]; _0x6ae47c++) if (_0x1cf8d6(0x228) == _0xa4ab82[_0x1cf8d6(0x35b)][_0x6ae47c][-0x12f6 + -0x207f + 0x1127 * 0x3]['toLowerCase']()) return _0xa4ab82[_0x1cf8d6(0x35b)][_0x6ae47c][-0xaa2 + -0xe0e + 0x18b1]; } if (_0xa4ab82[_0x1cf8d6(0x35b)] instanceof Object) { for (var _0x1506c8 = -0x1ce4 + 0x8dc + 0xa04 * 0x2, _0x32fecb = Object['keys'](_0xa4ab82['headers']); _0x1506c8 < _0x32fecb[_0x1cf8d6(0x259)]; _0x1506c8++) { var _0x228b3d = _0x32fecb[_0x1506c8]; if ('content-type' === _0x228b3d['toLowerCase']()) return _0xa4ab82[_0x1cf8d6(0x35b)][_0x228b3d]; } return _0x314297; } } } function _0x1294ff(_0x1475ff, _0x5700d1, _0x563b6f) { var _0x383b2a = _0x5612de; if (null == _0x563b6f || '' === _0x563b6f) return _0x1475ff; if (_0x563b6f = _0x563b6f[_0x383b2a(0x3ae)](), _0x383b2a(0x186) === _0x5700d1) { _0x1475ff[_0x383b2a(0x336)] = !(0xfc + -0x22d4 + 0x21d8); var _0x38ceac = _0x563b6f[_0x383b2a(0x342)]('&') , _0x1b01b6 = {}; if (_0x38ceac) { for (var _0x4c4745 = -0x29 * -0xd3 + 0x1 * 0xa01 + -0x2bcc; _0x4c4745 < _0x38ceac['length']; _0x4c4745++) _0x1b01b6[_0x38ceac[_0x4c4745][_0x383b2a(0x342)]('=')[0x2 * -0x391 + -0x1 * -0x99f + -0x27d]] = decodeURIComponent(_0x38ceac[_0x4c4745][_0x383b2a(0x342)]('=')[0x1e44 + -0x14e3 * -0x1 + -0x3326]); } _0x1475ff[_0x383b2a(0x189)] = _0x1b01b6; } else _0x1475ff[_0x383b2a(0x189)] = JSON['parse'](_0x563b6f); return _0x1475ff; } function _0x45e0e9(_0x2d3284, _0xb8d78) { var _0x521f3f = _0x5612de , _0x3496a3 = _0xb8d78; if (_0x462335[_0x521f3f(0x18d)][_0x521f3f(0x259)] > 0x1f8f * 0x1 + -0x1 * 0x21dd + 0xa * 0x3b) for (var _0x335ec5 = 0xb * -0xd3 + -0x1977 * 0x1 + -0x41 * -0x88; _0x335ec5 < _0x462335[_0x521f3f(0x18d)][_0x521f3f(0x259)]; _0x335ec5++) { var _0x3f8686 = _0x462335[_0x521f3f(0x18d)][_0x335ec5][-0x2023 + 0xcb * -0x10 + -0x1cb * -0x19]; if (_0x3f8686[_0x521f3f(0x22b)](_0xb8d78)) { _0x3496a3 = _0xb8d78[_0x521f3f(0x377)](_0x3f8686, _0x462335[_0x521f3f(0x18d)][_0x335ec5][0x1a12 * -0x1 + 0x2361 + -0x94e]), _0x2d3284 && _0x3d40ff[_0x521f3f(0x397)]['call'](_0x2d3284, _0x521f3f(0x243), _0x521f3f(0x223) + _0xb8d78 + '\x0aREWRITED:\x20' + _0x3496a3); break; } } return _0x3496a3 = _0x2b13af(_0x3496a3); } function _0x344a4d() { var _0x1b8e41 = _0x5612de; return w_0x5c3140(_0x1b8e41(0x20d), { get 0x0() { return window; }, get 0x1() { return _0x6a7375; }, get 0x2() { return _0x6caf; }, get 0x3() { return _0x1958a5; }, get 0x4() { return _0x2b13af; }, get 0x5() { return _0x288415; }, get 0x6() { return _0x8edc3d; }, 0x7: arguments }, this); } function _0x3f720d() { _0x3af1be(), _0x1d82ac(), _0x344a4d(); } function _0x3bfecb(_0x4b21ed) { var _0x3a0ee2 = _0x5612de; this[_0x3a0ee2(0x341)] = 'ConfigException', this[_0x3a0ee2(0x312)] = _0x4b21ed; } var _0x589057 = { 'cn': { 'host': _0x5612de(0x2c9) } }, _0x2bbf08 = [_0x5612de(0x30c)], _0x3d70a4; function _0x43f5a3(_0x5a985f) { var _0x20ecf9 = _0x5612de , _0x3c43cc = ''; return { 'host': _0x3c43cc = _0x5a985f[_0x20ecf9(0x1b6)] || _0x5a985f[_0x20ecf9(0x1ae)] ? _0x5a985f[_0x20ecf9(0x1b3)] : _0x589057[_0x5a985f[_0x20ecf9(0x224)]][_0x20ecf9(0x2dc)], 'pathList': _0x2bbf08, 'reportUrl': _0x3c43cc + _0x2bbf08[0x1d7 * 0x7 + 0x1073 + -0x1d54] }; } var _0x383bd7 = !(0x244 * 0xb + -0x16a * -0x4 + -0x1e93), _0xd39ee2, _0x9e520d; function _0x53ee31(_0x817028) { var _0x5d5e8d = _0x5612de; return w_0x5c3140(_0x5d5e8d(0x34f), { 0x0: Object, 0x1: Math, get 0x2() { return _0x3bfecb; }, get 0x3() { return _0x6caf; }, get 0x4() { return _0x462335; }, get 0x5() { return _0x43f5a3; }, get 0x6() { return setTimeout; }, get 0x7() { return _0x5c328e; }, get 0x8() { return _0x3d70a4; }, set 0x8(_0x5982f0) { _0x3d70a4 = _0x5982f0; }, get 0x9() { return clearInterval; }, get 0xa() { return setInterval; }, get 0xb() { return _0x450b73; }, get 0xc() { return _0x1a39c4; }, get 0xd() { return _0x3f720d; }, get 0xe() { return _0x59992f; }, get 0xf() { return _0x39d569; }, get 0x10() { return _0x1dbe74; }, get 0x11() { return _0x383bd7; }, set 0x11(_0x488bc8) { _0x383bd7 = _0x488bc8; }, get 0x12() { return _0x18707d; }, get 0x13() { return _0x1c3b6d; }, 0x14: arguments, 0x15: _0x817028 }, this); } function _0x3498af(_0x3e9bb9) { } function _0x59992f(_0x10dd97) { var _0x1beab7 = _0x5612de; for (var _0x52c469 = 0xb * -0x262 + -0x606 + 0x101e * 0x2; _0x52c469 < _0x10dd97[_0x1beab7(0x259)]; _0x52c469++) _0x10dd97[_0x52c469] && _0x462335['_enablePathListRegex']['push'](new RegExp(_0x10dd97[_0x52c469])); } function _0x39d569(_0x51ab06) { var _0x59d7a1 = _0x5612de; if (void (-0x11c8 * 0x1 + 0x1fa2 * 0x1 + -0xdda) !== _0x51ab06) { for (var _0x3e2076 = -0x3ab * -0x5 + -0x19d4 + 0x1b * 0x47; _0x3e2076 < _0x51ab06[_0x59d7a1(0x259)]; _0x3e2076++) _0x462335[_0x59d7a1(0x18d)][_0x59d7a1(0x36e)]([new RegExp(_0x51ab06[_0x3e2076][-0xfb * -0x1 + 0x9a7 * 0x4 + -0x5 * 0x7eb]), _0x51ab06[_0x3e2076][-0x57 * 0x55 + 0x1c99 * 0x1 + 0x4b]]); } } function _0x32e4a6() { var _0x494ee9 = _0x5612de; return window[_0x494ee9(0x318)] || ''; } function _0x1be1e1(_0x58db04) { var _0x32bdb2 = _0x5612de , _0x4558be = _0x6caf[_0x32bdb2(0x323)] , _0x4ab91a = -0x5d * 0x5e + -0xb06 + -0x47 * -0xa3; _0x32bdb2(0x2b6) === _0x58db04 && (_0x4ab91a = 0x1754 + 0xd0 * 0x4 + -0x1a93), _0x32bdb2(0x285) === _0x58db04 && (_0x4ab91a = -0x146 * -0x13 + 0x13 * 0xef + -0x29ed); var _0x1741bb = { 'ts': new Date()[_0x32bdb2(0x16d)](), 'v': _0x4ab91a }; _0x4558be[_0x32bdb2(0x36e)](_0x1741bb); } function _0x4de7ef() { var _0x45ce69 = _0x5612de, _0x5c1967, _0x586941; void (0xbc1 + 0x1 * 0x1309 + -0x1eca) !== document[_0x45ce69(0x285)] ? (_0x45ce69(0x285), _0x586941 = _0x45ce69(0x1a0), _0x5c1967 = _0x45ce69(0x1f6)) : void (0x2056 + 0x2306 + -0x435c) !== document[_0x45ce69(0x15f)] ? (_0x45ce69(0x15f), _0x586941 = _0x45ce69(0x1c8), _0x5c1967 = _0x45ce69(0x33f)) : void (0x68 * 0x53 + 0x3c7 * 0x3 + -0x2d0d) !== document[_0x45ce69(0x2c6)] ? (_0x45ce69(0x2c6), _0x586941 = _0x45ce69(0x2cc), _0x5c1967 = _0x45ce69(0x1a4)) : void (-0x1bd7 + 0xc9 * -0x2b + 0x3d9a) !== document[_0x45ce69(0x22a)] && (_0x45ce69(0x22a), _0x586941 = _0x45ce69(0x322), _0x5c1967 = _0x45ce69(0x166)), document[_0x45ce69(0x2f6)](_0x586941, function () { _0x1be1e1(document[_0x5c1967]); }, !(0x1088 + 0x18c2 + 0xdc3 * -0x3)), _0x1be1e1(document[_0x5c1967]); } function _0x3ff3f1() { _0x58c311(); } function _0x4c727e() { var _0x3f5094 = _0x5612de; function _0x115c6a(_0x2162b2) { var _0x4cc12e = w_0x25f3; _0x462335['triggerUnload'] || (_0x462335[_0x4cc12e(0x204)] = !(0x1 * -0x632 + 0x3 * -0xb71 + -0x1c3 * -0x17), _0x3ff3f1()); } window && window[_0x3f5094(0x2f6)] && (window[_0x3f5094(0x2f6)](_0x3f5094(0x37d), _0x115c6a), window[_0x3f5094(0x2f6)](_0x3f5094(0x29b), _0x115c6a)); } function _0x5b850b() { var _0x4d0be4 = _0x5612de; for (var _0x35a39e = document[_0x4d0be4(0x272)][_0x4d0be4(0x342)](';'), _0x3046a3 = [], _0x58914c = -0x923 + -0x66a * -0x5 + -0x16ef; _0x58914c < _0x35a39e[_0x4d0be4(0x259)]; _0x58914c++) if (_0x4d0be4(0x3b5) == (_0x3046a3 = _0x35a39e[_0x58914c][_0x4d0be4(0x342)]('='))[-0x7cd * -0x1 + -0x1678 + 0xeab]['trim']()) { _0x6caf[_0x4d0be4(0x3b5)] = _0x3046a3[0x200f * -0x1 + 0x926 + 0x16ea]; break; } } function _0x498349(_0x5efcd6) { return new _0x53ee31(_0x5efcd6); } function _0x475194(_0x56e1e3) { -0xa7f + -0xb * 0x18a + 0x1b6d === _0x56e1e3 // setTimeout(_0x5047d8, 0x1 * -0xb55 + -0x1031 + 0x1bea * 0x1) : -0xef1 + -0xfd3 * -0x1 + -0xe1 === _0x56e1e3 && setTimeout(_0x5c328e, 0x6 * 0x38d + -0x49 * -0x2b + -0x95 * 0x39); } function _0x4a4111(_0x2ba688, _0x1e135c) { var _0x18e9b1 = _0x5612de; -0xfab + 0x9 * -0x2a2 + 0x275e === _0x2ba688 && (_0x462335[_0x18e9b1(0x34a)] = Object['assign']({}, _0x462335[_0x18e9b1(0x34a)], _0x1e135c)); } function _0x271dea(_0x20563e) { void (-0x1aa2 + -0x2407 + 0x3 * 0x14e3) !== _0x20563e && '' != _0x20563e && (_0x6caf['ttwid'] = _0x20563e); } function _0x3a4a1a(_0x5b82b5) { var _0x30adbb = _0x5612de; void (0xb * -0x17d + -0x227d + 0x32dc) !== _0x5b82b5 && '' != _0x5b82b5 && (_0x6caf[_0x30adbb(0x316)] = _0x5b82b5); } function _0x3f0a66(_0x53e069) { var _0x38d5b6 = _0x5612de; void (-0x7eb * 0x3 + 0xc4 * 0x2 + -0x1 * -0x1639) !== _0x53e069 && '' != _0x53e069 && (_0x6caf[_0x38d5b6(0x339)] = _0x53e069); } _0x53ee31[_0x5612de(0x344)][_0x5612de(0x1a3)] = _0x5c2014, _0x53ee31[_0x5612de(0x344)]['getReferer'] = _0x32e4a6, _0x53ee31[_0x5612de(0x344)]['setUserMode'] = _0x3498af, _0xd39ee2 = _0x24dc34(_0x45b94b['refererKey']) || '', _0x2ecc5a(_0x45b94b['refererKey']), _0x5612de(0x2d6) === _0xd39ee2 ? _0xd39ee2 = '' : '' === _0xd39ee2 && (_0xd39ee2 = document[_0x5612de(0x2fa)]), _0xd39ee2 && (window[_0x5612de(0x318)] = _0xd39ee2), _0x9e520d = _0x5141ac(), _0x9e520d && (_0x6caf[_0x5612de(0x2b4)] = _0x9e520d, _0x6caf['msStatus'] = _0x39693d['asgw']), // setTimeout(function () { // _0x18a9f7(), // _0x1dbe74(), // _0x4de7ef(), // _0x4c727e(), // _0x21fa28(); // }, -0x1a83 + 0x15ee + -0xd * -0x141), _0x5b850b(), _0x59992f([_0x5612de(0x30c)]); var _0x1649bc = !(-0xc6b * -0x1 + -0x2315 + 0x16aa); crawler = _0x5c2014 _0x1d18f2['frontierSign'] = _0x5c2014, _0x1d18f2[_0x5612de(0x2e8)] = _0x32e4a6, _0x1d18f2[_0x5612de(0x3b9)] = _0x498349, _0x1d18f2[_0x5612de(0x1f8)] = _0x1649bc, _0x1d18f2['report'] = _0x475194, _0x1d18f2[_0x5612de(0x298)] = _0x4a4111, _0x1d18f2[_0x5612de(0x1a7)] = _0x3a4a1a, _0x1d18f2['setTTWebidV2'] = _0x3f0a66, _0x1d18f2[_0x5612de(0x244)] = _0x271dea, _0x1d18f2[_0x5612de(0x378)] = _0x3498af, Object['defineProperty'](_0x1d18f2, _0x5612de(0x3bd), { 'value': !(0x1 * 0x2069 + -0x1403 + -0xc66) }); }); } function get_sign(md5) { let data = { "X-MS-STUB": md5 } return crawler(data)["X-Bogus"]; } // console.log(get_sign("e103106903fe7124df5b89decb376942")) ================================================ FILE: simple_live_core/example/simple_live_core_example.dart ================================================ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:simple_live_core/src/model/tars/get_cdn_token_req.dart'; import 'package:simple_live_core/src/model/tars/get_cdn_token_resp.dart'; import 'package:tars_dart/tars/net/base_tars_http.dart'; import 'package:tars_dart/tars/tup/uni_packet.dart'; void main() async { // CoreLog.enableLog = true; // CoreLog.requestLogType = RequestLogType.short; // LiveSite site = BiliBiliSite(); // var danmaku = site.getDanmaku(); // danmaku.onMessage = (event) { // if (event.type == LiveMessageType.chat) { // print("[${event.color}]${event.userName}:${event.message}"); // } else if (event.type == LiveMessageType.online) { // print("-----人气:${event.data}-----"); // } else if (event.type == LiveMessageType.superChat) { // var scMessage = event.data as LiveSuperChatMessage; // print("[SC]${scMessage.userName}:${scMessage.message}"); // } // }; // danmaku.onClose = (event) { // print(event); // }; // //var search = await site.searchRooms("东方"); // //var categores = await site.getCategores(); // //print(categores.length); // var detail = await site.getRoomDetail(roomId: '7734200'); // // var playQualites = await site.getPlayQualites(detail: detail); // // print(playQualites); // // var playUrls = // // await site.getPlayUrls(detail: detail, quality: playQualites.first); // // for (var element in playUrls) { // // print(element); // // } // //print(detail); // danmaku.start(detail.danmakuData); // await Future.wait({}); sendReq(); } void testHuyaReq() async { var reqBytes = await File('demo/getCdnTokenInfoReq.bin').readAsBytes(); // RequestPacket req = RequestPacket(); // req.readFrom(TarsInputStream(reqBytes)); // print(req.iVersion); UniPacket uniPacket = UniPacket(); //uniPacket.readFrom(TarsInputStream(reqBytes)); uniPacket.decode(reqBytes); var value = uniPacket.get('tReq', GetCdnTokenReq()); // GetCdnTokenReq reqData = GetCdnTokenReq(); // reqData.readFrom(TarsInputStream(req.sBuffer)); print(value.toString()); } void sendReq() async { var req = GetCdnTokenReq(); req.cdnType = "HW"; req.streamName = "1199637826638-1199637826638-5763635889762729984-2399275776732-10057-A-0-1"; BaseTarsHttp http = BaseTarsHttp("http://wup.huya.com", "liveui"); var data = await http.tupRequest("getCdnTokenInfo", req, GetCdnTokenResp()); var url = 'http://hw.flv.huya.com/src/${data.streamName}.flv?${data.flvAntiCode}&codec=264'; print(url); await Dio().download( url, 'live-stream.flv', options: Options( responseType: ResponseType.bytes, headers: {"user-agent": "HYSDK(Windows, 20000308)"}, ), onReceiveProgress: (count, total) { var downBytes = count / 1024 / 1024; print('downloading: $downBytes MB'); }, ); } void testHuyaResp() async { var respBytes = await File('demo/getCdnTokenInfoResp.bin').readAsBytes(); UniPacket uniPacket = UniPacket(); uniPacket.decode(respBytes); var value = uniPacket.get('tRsp', GetCdnTokenResp()); print(value.toString()); } ================================================ FILE: simple_live_core/lib/simple_live_core.dart ================================================ library; export 'src/interface/live_site.dart'; export 'src/interface/live_danmaku.dart'; export 'src/platforms/huya/huya_site.dart'; export 'src/platforms/bilibili/bilibili_site.dart'; export 'src/platforms/douyu/douyu_site.dart'; export 'src/platforms/douyin/douyin_site.dart'; export 'src/platforms/twitch/twitch_site.dart'; export 'src/common/core_log.dart'; export 'src/model/live_message.dart'; export 'src/danmaku/bilibili_danmaku.dart'; export 'src/danmaku/douyu_danmaku.dart'; export 'src/danmaku/huya_danmaku.dart'; export 'src/danmaku/douyin_danmaku.dart'; export 'src/model/live_category_result.dart'; export 'src/model/live_category.dart'; export 'src/model/live_play_quality.dart'; export 'src/model/live_room_detail.dart'; export 'src/model/live_room_item.dart'; export 'src/model/live_search_result.dart'; export 'src/model/live_anchor_item.dart'; export 'src/model/live_play_url.dart'; ================================================ FILE: simple_live_core/lib/src/common/binary_writer.dart ================================================ import 'dart:typed_data'; class BinaryWriter { List buffer; int position = 0; BinaryWriter(this.buffer); int get length => buffer.length; void writeBytes(List list) { buffer.addAll(list); position += list.length; } void writeInt(int value, int len, {Endian endian = Endian.big}) { var b = Uint8List(len).buffer; var bytes = ByteData.view(b); if (len == 1) { //写入byte bytes.setUint8(0, value.toUnsigned(8)); } if (len == 2) { bytes.setInt16(0, value, endian); } if (len == 4) { bytes.setInt32(0, value, endian); } if (len == 8) { bytes.setInt64(0, value, endian); } buffer.addAll(bytes.buffer.asUint8List()); position += len; } void writeDouble(double value, int len, {Endian endian = Endian.big}) { var b = Uint8List(len).buffer; var bytes = ByteData.view(b); if (len == 4) { bytes.setFloat32(0, value, endian); } if (len == 8) { bytes.setFloat64(0, value, endian); } buffer.addAll(bytes.buffer.asUint8List()); position += len; } } class BinaryReader { Uint8List buffer; int position = 0; BinaryReader(this.buffer); int get length => buffer.length; /// 从当前流中读取下一个字节,并使流的当前位置提升 1 个字节 /// 返回下一个字节(0-255) int read() { var byte = buffer[position]; position += 1; return byte; } /// 从当前流中读取指定长度的字节整数,并使流的当前位置提升指定长度。 /// [len] 指定长度 /// len=1为int8,2为int16,4为int32,8为int64。dart中统一为int类型 /// 返回整数 int readInt(int len, {Endian endian = Endian.big}) { var result = 0; // if (len == 1) { // result = buffer[position]; // position += len; // return result; // } var bytes = Uint8List.fromList(buffer.getRange(position, position + len).toList()); var byteBuffer = bytes.buffer; var data = ByteData.view(byteBuffer); if (len == 1) { result = data.getUint8(0); } if (len == 2) { result = data.getInt16(0, endian); } if (len == 4) { result = data.getInt32(0, endian); } if (len == 8) { result = data.getInt64(0, endian); } position += len; return result; } /// 读取字节 /// int长度=1 int readByte({Endian endian = Endian.big}) { return readInt(1, endian: endian); } /// 读取 /// int长度=2 int readShort({Endian endian = Endian.big}) { return readInt(2, endian: endian); } /// 读取字节 /// int长度=4 int readInt32({Endian endian = Endian.big}) { return readInt(4, endian: endian); } /// 读取字节 /// int长度=8 int readLong({Endian endian = Endian.big}) { return readInt(8, endian: endian); } /// 从当前流中读取指定长度的字节数组,并使流的当前位置提升指定长度。 /// [len] 指定长度 /// 返回字节数组 Uint8List readBytes(int len) { var bytes = Uint8List.fromList(buffer.getRange(position, position + len).toList()); position += len; return bytes; } /// 从当前流中读取指定长度的字节浮点数,并使流的当前位置提升指定长度。 /// [len] 指定长度 /// len=4为float,8为double。dart中统一为double类型 /// 返回浮点数 double readFloat(int len, {Endian endian = Endian.big}) { var result = 0.0; var bytes = Uint8List.fromList(buffer.getRange(position, position + len).toList()); var byteBuffer = bytes.buffer; var data = ByteData.view(byteBuffer); if (len == 4) { result = data.getFloat32(0, endian); } if (len == 8) { result = data.getFloat64(0, endian); } position += len; return result; } } ================================================ FILE: simple_live_core/lib/src/common/convert_helper.dart ================================================ T? asT(dynamic value) { if (value is T) { return value; } return null; } ================================================ FILE: simple_live_core/lib/src/common/core_error.dart ================================================ class CoreError extends Error { /// 错误码 final int statusCode; /// 错误信息 final String message; CoreError( this.message, { this.statusCode = 0, }); @override String toString() { if (statusCode != 0) { return statusCodeToString(statusCode); } return message; } String statusCodeToString(int statusCode) { switch (statusCode) { case 400: return "错误的请求(400)"; case 401: return "无权限访问资源(401)"; case 403: return "无权限访问资源(403)"; case 404: return "服务器找不到请求的资源(404)"; case 500: return "服务器出现错误(500)"; case 502: return "服务器出现错误(502)"; case 503: return "服务器出现错误(503)"; default: return "连接服务器失败,请稍后再试($statusCode)"; } } } ================================================ FILE: simple_live_core/lib/src/common/core_log.dart ================================================ import 'package:logger/logger.dart'; enum RequestLogType { /// 输出所有请求信息 /// 包括请求的URL,请求的参数,请求的头,请求的体,响应的头,响应的内容,耗时 all, /// 简洁的输出 /// 仅输出请求的URL和响应的状态码 short, /// 不输出请求信息 none, } class CoreLog { /// 是否启用日志 static bool enableLog = true; /// 请求日志模式 static RequestLogType requestLogType = RequestLogType.all; static Function(Level, String)? onPrintLog; static Logger logger = Logger( printer: PrettyPrinter( methodCount: 0, errorMethodCount: 8, lineLength: 120, colors: true, printEmojis: true, ), ); static void d(String message) { if (!enableLog) { return; } onPrintLog?.call(Level.debug, message); if (onPrintLog == null) { logger.d("${DateTime.now().toString()}\n$message"); } } static void i(String message) { if (!enableLog) { return; } onPrintLog?.call(Level.info, message); if (onPrintLog == null) { logger.i("${DateTime.now().toString()}\n$message"); } } static void e(String message, StackTrace stackTrace) { if (!enableLog) { return; } onPrintLog?.call(Level.error, message); if (onPrintLog == null) { logger.e("${DateTime.now().toString()}\n$message", stackTrace: stackTrace); } } static void error(Object e) { if (!enableLog) { return; } onPrintLog?.call(Level.error, e.toString()); if (onPrintLog == null) { logger.e( "${DateTime.now().toString()}\n${e.toString()}", error: e, stackTrace: (e is Error) ? e.stackTrace : StackTrace.current, ); } } static void w(String message) { if (!enableLog) { return; } onPrintLog?.call(Level.warning, message); if (onPrintLog == null) { logger.w("${DateTime.now().toString()}\n$message"); } } } ================================================ FILE: simple_live_core/lib/src/common/custom_interceptor.dart ================================================ import 'package:dio/dio.dart'; import 'core_log.dart'; class CustomInterceptor extends Interceptor { @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { options.extra["ts"] = DateTime.now().millisecondsSinceEpoch; if (CoreLog.requestLogType == RequestLogType.all) { CoreLog.i( '''[HTTP Request] [${options.method}] Request URL:${options.uri} Request Query:${options.queryParameters} Request Data:${options.data} Request Headers:${options.headers}''', ); } else if (CoreLog.requestLogType == RequestLogType.short) { CoreLog.i("[HTTP Request] [${options.method}] ${options.uri}"); } super.onRequest(options, handler); } @override void onError(DioException err, ErrorInterceptorHandler handler) { var time = DateTime.now().millisecondsSinceEpoch - err.requestOptions.extra["ts"]; if (CoreLog.requestLogType == RequestLogType.all) { CoreLog.e('''[HTTP Error] [${err.type}] [Time:${time}ms] ${err.message} Request Method:${err.requestOptions.method} Response Code:${err.response?.statusCode} Request URL:${err.requestOptions.uri} Request Query:${err.requestOptions.queryParameters} Request Data:${err.requestOptions.data} Request Headers:${err.requestOptions.headers} Response Headers:${err.response?.headers.map} Response Data:${err.response?.data}''', err.stackTrace); } else { CoreLog.e( "[HTTP Error] [${err.type}] [Time:${time}ms]\n[${err.response?.statusCode}] ${err.requestOptions.uri}", err.stackTrace, ); } super.onError(err, handler); } @override void onResponse(Response response, ResponseInterceptorHandler handler) { var time = DateTime.now().millisecondsSinceEpoch - response.requestOptions.extra["ts"]; if (CoreLog.requestLogType == RequestLogType.all) { CoreLog.i( '''[HTTP Response] [time:${time}ms] Request Method:${response.requestOptions.method} Request Code:${response.statusCode} Request URL:${response.requestOptions.uri} Request Query:${response.requestOptions.queryParameters} Request Data:${response.requestOptions.data} Request Headers:${response.requestOptions.headers} Response Headers:${response.headers.map} Response Data:${response.data}''', ); } else if (CoreLog.requestLogType == RequestLogType.short) { CoreLog.i( "[HTTP Response] [time:${time}ms] [${response.statusCode}] ${response.requestOptions.uri}", ); } super.onResponse(response, handler); } } ================================================ FILE: simple_live_core/lib/src/common/douyin/abogus.dart ================================================ import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; import 'package:dart_sm/dart_sm.dart'; class StringProcessor { static String toCharStr(List codes) { return String.fromCharCodes(codes); } static List toOrdArray(String s) { return s.codeUnits; } static int jsShiftRight(int val, int n) { return (val & 0xFFFFFFFF) >>> n; } static String generateRandomBytes({int length = 3}) { List generateByteSequence() { final _rd = Random().nextInt(10000); return [ String.fromCharCode(((_rd & 255) & 170) | 1), String.fromCharCode(((_rd & 255) & 85) | 2), String.fromCharCode((jsShiftRight(_rd, 8) & 170) | 5), String.fromCharCode((jsShiftRight(_rd, 8) & 85) | 40), ]; } final result = []; for (var i = 0; i < length; i++) { result.addAll(generateByteSequence()); } return result.join(''); } } class CryptoUtility { final String salt; final List base64Alphabet; late List bigArray; CryptoUtility(this.salt, this.base64Alphabet) { // fmt: off bigArray = [ 121, 243, 55, 234, 103, 36, 47, 228, 30, 231, 106, 6, 115, 95, 78, 101, 250, 207, 198, 50, 139, 227, 220, 105, 97, 143, 34, 28, 194, 215, 18, 100, 159, 160, 43, 8, 169, 217, 180, 120, 247, 45, 90, 11, 27, 197, 46, 3, 84, 72, 5, 68, 62, 56, 221, 75, 144, 79, 73, 161, 178, 81, 64, 187, 134, 117, 186, 118, 16, 241, 130, 71, 89, 147, 122, 129, 65, 40, 88, 150, 110, 219, 199, 255, 181, 254, 48, 4, 195, 248, 208, 32, 116, 167, 69, 201, 17, 124, 125, 104, 96, 83, 80, 127, 236, 108, 154, 126, 204, 15, 20, 135, 112, 158, 13, 1, 188, 164, 210, 237, 222, 98, 212, 77, 253, 42, 170, 202, 26, 22, 29, 182, 251, 10, 173, 152, 58, 138, 54, 141, 185, 33, 157, 31, 252, 132, 233, 235, 102, 196, 191, 223, 240, 148, 39, 123, 92, 82, 128, 109, 57, 24, 38, 113, 209, 245, 2, 119, 153, 229, 189, 214, 230, 174, 232, 63, 52, 205, 86, 140, 66, 175, 111, 171, 246, 133, 238, 193, 99, 60, 74, 91, 225, 51, 76, 37, 145, 211, 166, 151, 213, 206, 0, 200, 244, 176, 218, 44, 184, 172, 49, 216, 93, 168, 53, 21, 183, 41, 67, 85, 224, 155, 226, 242, 87, 177, 146, 70, 190, 12, 162, 19, 137, 114, 25, 165, 163, 192, 23, 59, 9, 94, 179, 107, 35, 7, 142, 131, 239, 203, 149, 136, 61, 249, 14, 156 ]; // fmt: on } static List sm3ToArray(dynamic inputData) { Uint8List inputDataBytes; if (inputData is String) { inputDataBytes = utf8.encode(inputData); } else if (inputData is List) { inputDataBytes = Uint8List.fromList(inputData); } else { throw ArgumentError("Input data must be a String or List"); } final hexResult = SM3.hashBytes(inputDataBytes); final result = []; for (var i = 0; i < hexResult.length; i += 2) { result.add(int.parse(hexResult.substring(i, i + 2), radix: 16)); } return result; } String addSalt(String param) { return param + salt; } dynamic processParam(dynamic param, bool addSaltFlag) { if (param is String && addSaltFlag) { return addSalt(param); } return param; } List paramsToArray(dynamic param, {bool addSalt = true}) { final processedParam = processParam(param, addSalt); return sm3ToArray(processedParam); } String transformBytes(List bytesList) { final bytesStr = StringProcessor.toCharStr(bytesList); final resultStr = []; var indexB = bigArray[1]; var initialValue = 0; var valueE = 0; // To hold the value for the next iteration for (var index = 0; index < bytesStr.length; index++) { final char = bytesStr[index]; var sumInitial = 0; if (index == 0) { initialValue = bigArray[indexB]; sumInitial = indexB + initialValue; bigArray[1] = initialValue; bigArray[indexB] = indexB; } else { sumInitial = initialValue + valueE; } final charValue = char.codeUnitAt(0); sumInitial %= bigArray.length; final valueF = bigArray[sumInitial]; final encryptedChar = charValue ^ valueF; resultStr.add(String.fromCharCode(encryptedChar)); valueE = bigArray[(index + 2) % bigArray.length]; sumInitial = (indexB + valueE) % bigArray.length; initialValue = bigArray[sumInitial]; bigArray[sumInitial] = bigArray[(index + 2) % bigArray.length]; bigArray[(index + 2) % bigArray.length] = initialValue; indexB = sumInitial; } return resultStr.join(''); } String base64Encode(String inputString, {int selectedAlphabet = 0}) { var binaryString = inputString.codeUnits .map((c) => c.toRadixString(2).padLeft(8, '0')) .join(''); final paddingLength = (6 - binaryString.length % 6) % 6; binaryString += '0' * paddingLength; final base64Indices = []; for (var i = 0; i < binaryString.length; i += 6) { base64Indices.add(int.parse(binaryString.substring(i, i + 6), radix: 2)); } var outputString = base64Indices .map((index) => base64Alphabet[selectedAlphabet][index]) .join(''); outputString += '=' * (paddingLength ~/ 2); return outputString; } String abogusEncode(String abogusBytesStr, int selectedAlphabet) { final abogus = []; final alphabet = base64Alphabet[selectedAlphabet]; for (var i = 0; i < abogusBytesStr.length; i += 3) { int n; if (i + 2 < abogusBytesStr.length) { n = (abogusBytesStr.codeUnitAt(i) << 16) | (abogusBytesStr.codeUnitAt(i + 1) << 8) | abogusBytesStr.codeUnitAt(i + 2); } else if (i + 1 < abogusBytesStr.length) { n = (abogusBytesStr.codeUnitAt(i) << 16) | (abogusBytesStr.codeUnitAt(i + 1) << 8); } else { n = abogusBytesStr.codeUnitAt(i) << 16; } final shifts = [18, 12, 6, 0]; final masks = [0xFC0000, 0x03F000, 0x0FC0, 0x3F]; for (var j = 0; j < shifts.length; j++) { if (shifts[j] == 6 && i + 1 >= abogusBytesStr.length) break; if (shifts[j] == 0 && i + 2 >= abogusBytesStr.length) break; abogus.add(alphabet[(n & masks[j]) >> shifts[j]]); } } abogus.add('=' * ((4 - abogus.length % 4) % 4)); return abogus.join(''); } static Uint8List rc4Encrypt(Uint8List key, String plaintext) { final s = List.generate(256, (i) => i); var j = 0; for (var i = 0; i < 256; i++) { j = (j + s[i] + key[i % key.length]) % 256; final temp = s[i]; s[i] = s[j]; s[j] = temp; } var i = 0; j = 0; final ciphertext = []; for (final char in plaintext.codeUnits) { i = (i + 1) % 256; j = (j + s[i]) % 256; final temp = s[i]; s[i] = s[j]; s[j] = temp; final k = s[(s[i] + s[j]) % 256]; ciphertext.add(char ^ k); } return Uint8List.fromList(ciphertext); } } class BrowserFingerprintGenerator { static String generateFingerprint({String browserType = "Edge"}) { final browsers = { "Chrome": generateChromeFingerprint, "Firefox": generateFirefoxFingerprint, "Safari": generateSafariFingerprint, "Edge": generateEdgeFingerprint, }; return (browsers[browserType] ?? generateChromeFingerprint)(); } static String generateChromeFingerprint() => _generateFingerprint(platform: "Win32"); static String generateFirefoxFingerprint() => _generateFingerprint(platform: "Win32"); static String generateSafariFingerprint() => _generateFingerprint(platform: "MacIntel"); static String generateEdgeFingerprint() => _generateFingerprint(platform: "Win32"); static String _generateFingerprint({required String platform}) { final random = Random(); final innerWidth = 1024 + random.nextInt(1920 - 1024 + 1); final innerHeight = 768 + random.nextInt(1080 - 768 + 1); final outerWidth = innerWidth + (24 + random.nextInt(32 - 24 + 1)); final outerHeight = innerHeight + (75 + random.nextInt(90 - 75 + 1)); const screenX = 0; final screenY = [0, 30][random.nextInt(2)]; final sizeWidth = 1024 + random.nextInt(1920 - 1024 + 1); final sizeHeight = 768 + random.nextInt(1080 - 768 + 1); final availWidth = 1280 + random.nextInt(1920 - 1280 + 1); final availHeight = 800 + random.nextInt(1080 - 800 + 1); return "$innerWidth|$innerHeight|$outerWidth|$outerHeight|" "$screenX|$screenY|0|0|$sizeWidth|$sizeHeight|" "$availWidth|$availHeight|$innerWidth|$innerHeight|24|24|$platform"; } } class ABogus { final String userAgent; final String browserFp; final List options; final int aid = 6383; final int pageId = 0; final String salt = "cus"; final bool boe = false; final double ddrt = 8.5; final double ic = 8.5; final List paths = [ "^/webcast/", "^/aweme/v1/", "^/aweme/v2/", "/v1/message/send", "^/live/", "^/captcha/", "^/ecom/", ]; final Uint8List uaKey = Uint8List.fromList([0, 1, 14]); final String character = "Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe"; final String character2 = "ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe"; late final List characterList; late final CryptoUtility cryptoUtility; // fmt: off final List sortIndex = [ 18, 20, 52, 26, 30, 34, 58, 38, 40, 53, 42, 21, 27, 54, 55, 31, 35, 57, 39, 41, 43, 22, 28, 32, 60, 36, 23, 29, 33, 37, 44, 45, 59, 46, 47, 48, 49, 50, 24, 25, 65, 66, 70, 71 ]; final List sortIndex2 = [ 18, 20, 26, 30, 34, 38, 40, 42, 21, 27, 31, 35, 39, 41, 43, 22, 28, 32, 36, 23, 29, 33, 37, 44, 45, 46, 47, 48, 49, 50, 24, 25, 52, 53, 54, 55, 57, 58, 59, 60, 65, 66, 70, 71 ]; // fmt: on ABogus({String? fp, String? userAgent, List? options}) : userAgent = userAgent != null && userAgent.isNotEmpty ? userAgent : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0", browserFp = fp != null && fp.isNotEmpty ? fp : BrowserFingerprintGenerator.generateFingerprint(browserType: "Edge"), options = options ?? [0, 1, 14] { characterList = [character, character2]; cryptoUtility = CryptoUtility(salt, characterList); } String encodeData(String data, {int alphabetIndex = 0}) { return cryptoUtility.abogusEncode(data, alphabetIndex); } List generateAbogus(String params, {String body = ""}) { final abDir = { 8: 3, 15: { "aid": aid, "pageId": pageId, "boe": boe, "ddrt": ddrt, "paths": paths, "track": {"mode": 0, "delay": 300, "paths": []}, "dump": true, "rpU": "", }, 18: 44, 19: [1, 0, 1, 0, 1], 66: 0, 69: 0, 70: 0, 71: 0, }; final startEncryption = DateTime.now().millisecondsSinceEpoch; final array1 = cryptoUtility.paramsToArray(cryptoUtility.paramsToArray(params)); final array2 = cryptoUtility.paramsToArray(cryptoUtility.paramsToArray(body)); final array3 = cryptoUtility.paramsToArray( cryptoUtility.base64Encode( StringProcessor.toCharStr( CryptoUtility.rc4Encrypt(uaKey, userAgent), ), selectedAlphabet: 1, ), addSalt: false, ); final endEncryption = DateTime.now().millisecondsSinceEpoch; abDir[20] = (startEncryption >> 24) & 255; abDir[21] = (startEncryption >> 16) & 255; abDir[22] = (startEncryption >> 8) & 255; abDir[23] = startEncryption & 255; abDir[24] = StringProcessor.jsShiftRight(startEncryption, 32); abDir[25] = StringProcessor.jsShiftRight(startEncryption, 40); abDir[26] = (options[0] >> 24) & 255; abDir[27] = (options[0] >> 16) & 255; abDir[28] = (options[0] >> 8) & 255; abDir[29] = options[0] & 255; abDir[30] = (options[1] ~/ 256) & 255; abDir[31] = (options[1] % 256) & 255; abDir[32] = (options[1] >> 24) & 255; abDir[33] = (options[1] >> 16) & 255; abDir[34] = (options[2] >> 24) & 255; abDir[35] = (options[2] >> 16) & 255; abDir[36] = (options[2] >> 8) & 255; abDir[37] = options[2] & 255; abDir[38] = array1[21]; abDir[39] = array1[22]; abDir[40] = array2[21]; abDir[41] = array2[22]; abDir[42] = array3[23]; abDir[43] = array3[24]; abDir[44] = (endEncryption >> 24) & 255; abDir[45] = (endEncryption >> 16) & 255; abDir[46] = (endEncryption >> 8) & 255; abDir[47] = endEncryption & 255; abDir[48] = abDir[8]; abDir[49] = StringProcessor.jsShiftRight(endEncryption, 32); abDir[50] = StringProcessor.jsShiftRight(endEncryption, 40); abDir[51] = (pageId >> 24) & 255; abDir[52] = (pageId >> 16) & 255; abDir[53] = (pageId >> 8) & 255; abDir[54] = pageId & 255; abDir[55] = pageId; abDir[56] = aid; abDir[57] = aid & 255; abDir[58] = (aid >> 8) & 255; abDir[59] = (aid >> 16) & 255; abDir[60] = (aid >> 24) & 255; abDir[64] = browserFp.length; abDir[65] = browserFp.length; final sortedValues = sortIndex.map((i) => abDir[i] ?? 0).toList().cast(); final edgeFpArray = StringProcessor.toOrdArray(browserFp); var abXor = 0; for (var index = 0; index < sortIndex2.length; index++) { if (index == 0) { abXor = abDir[sortIndex2[index]] ?? 0; } else { abXor ^= (abDir[sortIndex2[index]] ?? 0); } } sortedValues.addAll(edgeFpArray); sortedValues.add(abXor); final abogusBytesStr = StringProcessor.generateRandomBytes() + cryptoUtility.transformBytes(sortedValues); final abogus = cryptoUtility.abogusEncode(abogusBytesStr, 0); final finalParams = "$params&a_bogus=$abogus"; return [finalParams, abogus, userAgent, body]; } } ================================================ FILE: simple_live_core/lib/src/common/douyin/douyinRequestParams.dart ================================================ mixin DouyinRequestParams { static const String kDefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0"; static const AID_VALUE = "6383"; static const VERSION_CODE_VALUE = "180800"; static const SDK_VERSION = "1.0.14-beta.0"; } ================================================ FILE: simple_live_core/lib/src/common/douyin/douyin_utils.dart ================================================ import 'dart:convert'; import 'dart:math'; import 'package:simple_live_core/simple_live_core.dart'; import 'package:simple_live_core/src/common/http_client.dart'; import 'abogus.dart'; import 'douyinRequestParams.dart'; class DouyinUtils { // 根据传入长度产生随机字符串 static String getMSToken({int randomLength = 184}) { var baseStr = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789='; var sb = StringBuffer(); for (var i = 0; i < randomLength; i++) { var index = Random().nextInt(baseStr.length); sb.write(baseStr[index]); } return sb.toString(); } static buildRequestUrl(String baseUrl, Map params) { var abogus = ABogus(userAgent: DouyinRequestParams.kDefaultUserAgent); var parsedUrl = Uri.parse(baseUrl); var exParams = params; exParams['aid'] = "6383"; exParams['compress'] = "gzip"; exParams['device_platform'] = "web"; exParams['browser_language'] = "zh-CN"; exParams['browser_platform'] = "Win32"; exParams['browser_name'] = "Edge"; exParams['browser_version'] = "125.0.0.0"; if (!exParams.containsKey('msToken')) { exParams['msToken'] = getMSToken(); } var newQueryStr = Uri(queryParameters: exParams).query; var signedQueryStr = abogus.generateAbogus(newQueryStr, body: "").first; final newUrl = parsedUrl.replace( query: signedQueryStr, ); return newUrl.toString(); } Future> get_ttwid_webid({required String req_url}) async { // 先请求以获取 ttwid 等 Cookie,再解析页面的 RENDER_DATA 获取 user_unique_id final headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", }; String? ttwid; String? webid; try { // 先用 HEAD 获取 Set-Cookie(包含 ttwid) final headResp = await HttpClient.instance.head( req_url, header: headers, ); final setCookies = headResp.headers["set-cookie"]; if (setCookies != null) { for (final cookieLine in setCookies) { final cookie = cookieLine.split(";").first; if (cookie.startsWith("ttwid=")) { ttwid = cookie.substring("ttwid=".length); break; } } } // 再用 GET 拉取页面 HTML,解析 RENDER_DATA final html = await HttpClient.instance.getText( req_url, header: headers, ); // 提取 RENDER_DATA 脚本块 final renderMatches = RegExp( r'