| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fuchsia/element/cpp/fidl.h> |
| #include <fuchsia/io/cpp/fidl.h> |
| #include <fuchsia/web/cpp/fidl.h> |
| #include <lib/sys/cpp/component_context.h> |
| #include <lib/sys/cpp/service_directory.h> |
| |
| #include <optional> |
| |
| #include "base/base_paths.h" |
| #include "base/check.h" |
| #include "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/fuchsia/file_utils.h" |
| #include "base/fuchsia/fuchsia_logging.h" |
| #include "base/fuchsia/mem_buffer_util.h" |
| #include "base/fuchsia/process_context.h" |
| #include "base/logging.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/task/single_thread_task_executor.h" |
| #include "base/test/test_future.h" |
| #include "base/time/time.h" |
| #include "components/cast/message_port/fuchsia/create_web_message.h" |
| #include "components/cast/message_port/platform_message_port.h" |
| #include "components/cast_streaming/test/cast_streaming_test_sender.h" |
| #include "components/fuchsia_component_support/annotations_manager.h" |
| #include "fuchsia_web/cast_streaming/cast_streaming.h" |
| #include "fuchsia_web/common/init_logging.h" |
| #include "fuchsia_web/common/test/fit_adapter.h" |
| #include "fuchsia_web/common/test/frame_test_util.h" |
| #include "fuchsia_web/shell/present_frame.h" |
| #include "fuchsia_web/shell/remote_debugging_port.h" |
| #include "fuchsia_web/shell/shell_relauncher.h" |
| #include "fuchsia_web/webengine/switches.h" |
| #include "fuchsia_web/webinstance_host/web_instance_host.h" |
| #include "media/base/media_util.h" |
| #include "media/gpu/test/video_test_helpers.h" |
| |
| namespace { |
| |
| // Identifier for JavaScript to be injected, only relevant if injecting multiple |
| // JavaScripts. |
| constexpr int kAddBeforeLoadJavaScriptID = 0; |
| |
| void PrintUsage() { |
| std::cerr << "Usage: " |
| << base::CommandLine::ForCurrentProcess()->GetProgram().BaseName() |
| << " [--" << kRemoteDebuggingPortSwitch |
| << "=<port>] [-- [--{extra_flag1}] [--{extra_flag2}]]\n" |
| << "Setting " << kRemoteDebuggingPortSwitch << "=0" |
| << " will automatically choose an available port.\n" |
| << "Extra flags will be passed to WebEngine to be processed.\n"; |
| } |
| |
| media::VideoDecoderConfig GetDefaultVideoConfig() { |
| constexpr gfx::Size kVideoSize = {1280, 720}; |
| constexpr gfx::Rect kVideoRect(kVideoSize); |
| |
| return media::VideoDecoderConfig( |
| media::VideoCodec::kVP8, media::VideoCodecProfile::VP8PROFILE_MIN, |
| media::VideoDecoderConfig::AlphaMode::kIsOpaque, media::VideoColorSpace(), |
| media::VideoTransformation(), kVideoSize, kVideoRect, kVideoSize, |
| media::EmptyExtraData(), media::EncryptionScheme::kUnencrypted); |
| } |
| |
| // Set up content directory and context params. |
| fuchsia::web::CreateContextParams GetCreateContextParams( |
| uint16_t remote_debugging_port) { |
| // Configure the fuchsia-dir://cast-streaming/ directory. |
| fuchsia::web::CreateContextParams create_context_params; |
| ApplyCastStreamingContextParams(&create_context_params); |
| |
| // Enable other WebEngine features. |
| fuchsia::web::ContextFeatureFlags features = |
| fuchsia::web::ContextFeatureFlags::AUDIO | |
| fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER | |
| fuchsia::web::ContextFeatureFlags::NETWORK | |
| fuchsia::web::ContextFeatureFlags::VULKAN; |
| create_context_params.set_features(features); |
| |
| create_context_params.set_remote_debugging_port(remote_debugging_port); |
| |
| return create_context_params; |
| } |
| |
| // Set autoplay, enable all logging, and present fullscreen view of `frame`. |
| std::optional<fuchsia::element::GraphicalPresenterPtr> ConfigureFrame( |
| fuchsia::web::Frame* frame, |
| fidl::InterfaceHandle<fuchsia::element::AnnotationController> |
| annotation_controller) { |
| fuchsia::web::ContentAreaSettings settings; |
| settings.set_autoplay_policy(fuchsia::web::AutoplayPolicy::ALLOW); |
| frame->SetContentAreaSettings(std::move(settings)); |
| frame->SetJavaScriptLogLevel(fuchsia::web::ConsoleLogLevel::DEBUG); |
| return PresentFrame(frame, std::move(annotation_controller)); |
| } |
| |
| } // namespace |
| |
| int main(int argc, char** argv) { |
| base::CommandLine::Init(argc, argv); |
| |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| CHECK(InitLoggingFromCommandLine(*command_line)); |
| |
| base::SingleThreadTaskExecutor executor(base::MessagePumpType::IO); |
| |
| if (auto optional_exit_code = RelaunchForWebInstanceHostIfParent( |
| "#meta/cast_streaming_shell_for_web_instance_host.cm", *command_line); |
| optional_exit_code.has_value()) { |
| return optional_exit_code.value(); |
| } |
| |
| std::optional<uint16_t> remote_debugging_port = |
| GetRemoteDebuggingPort(*command_line); |
| if (!remote_debugging_port) { |
| PrintUsage(); |
| return 1; |
| } |
| |
| // Instantiate Web Instance Host. |
| WebInstanceHostWithServicesFromThisComponent web_instance_host( |
| *base::ComponentContextForProcess()->outgoing(), |
| /*is_web_instance_component_in_same_package=*/false); |
| fidl::InterfaceRequest<fuchsia::io::Directory> services_request; |
| auto services = sys::ServiceDirectory::CreateWithRequest(&services_request); |
| base::CommandLine child_command_line = |
| base::CommandLine(command_line->GetArgs()); |
| child_command_line.AppendSwitch(switches::kEnableCastStreamingReceiver); |
| zx_status_t result = web_instance_host.CreateInstanceForContextWithCopiedArgs( |
| GetCreateContextParams(remote_debugging_port.value()), |
| std::move(services_request), child_command_line); |
| if (result != ZX_OK) { |
| ZX_LOG(ERROR, result) << "CreateInstanceForContextWithCopiedArgs failed"; |
| return 2; |
| } |
| |
| base::ComponentContextForProcess()->outgoing()->ServeFromStartupInfo(); |
| |
| // Create the browser `context`. |
| fuchsia::web::ContextPtr context; |
| services->Connect(context.NewRequest()); |
| |
| base::RunLoop run_loop; |
| |
| context.set_error_handler( |
| [quit_run_loop = run_loop.QuitClosure()](zx_status_t status) { |
| ZX_LOG(ERROR, status) << "Context connection lost:"; |
| quit_run_loop.Run(); |
| }); |
| |
| // Create the browser `frame`. |
| fuchsia::web::CreateFrameParams frame_params; |
| frame_params.set_enable_remote_debugging(true); |
| |
| fuchsia::web::FramePtr frame; |
| context->CreateFrameWithParams(std::move(frame_params), frame.NewRequest()); |
| frame.set_error_handler( |
| [quit_run_loop = run_loop.QuitClosure()](zx_status_t status) { |
| ZX_LOG(ERROR, status) << "Frame connection lost:"; |
| quit_run_loop.Run(); |
| }); |
| |
| // The underlying PresentView call expects an AnnotationController and will |
| // return PresentViewError.INVALID_ARGS without one. The AnnotationController |
| // should serve WatchAnnotations, but it doesn't need to do anything. |
| // TODO(b/264899156): Remove this when AnnotationController becomes |
| // optional. |
| auto annotations_manager = |
| std::make_unique<fuchsia_component_support::AnnotationsManager>(); |
| fuchsia::element::AnnotationControllerPtr annotation_controller; |
| annotations_manager->Connect(annotation_controller.NewRequest()); |
| auto presenter = |
| ConfigureFrame(frame.get(), std::move(annotation_controller)); |
| |
| // Register the MessagePort for the Cast Streaming Receiver. |
| std::unique_ptr<cast_api_bindings::MessagePort> sender_message_port; |
| std::unique_ptr<cast_api_bindings::MessagePort> receiver_message_port; |
| cast_api_bindings::CreatePlatformMessagePortPair(&sender_message_port, |
| &receiver_message_port); |
| |
| constexpr char kCastStreamingMessagePortOrigin[] = "cast-streaming:receiver"; |
| base::test::TestFuture<fuchsia::web::Frame_PostMessage_Result> |
| post_message_result; |
| frame->PostMessage(kCastStreamingMessagePortOrigin, |
| CreateWebMessage("", std::move(receiver_message_port)), |
| CallbackToFitFunction(post_message_result.GetCallback())); |
| if (!post_message_result.Wait()) { |
| LOG(ERROR) << "PostMessage timed out."; |
| return 1; |
| } |
| if (post_message_result.Get().is_err()) { |
| LOG(ERROR) << "PostMessage failed."; |
| return 1; |
| } |
| |
| // Inject JavaScript test harness into receiver.html. |
| base::test::TestFuture<fuchsia::web::Frame_AddBeforeLoadJavaScript_Result> |
| add_before_load_javascript_result; |
| base::FilePath pkg_path; |
| CHECK(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &pkg_path)); |
| base::FilePath test_harness_js_path(pkg_path.AppendASCII( |
| "fuchsia_web/shell/cast_streaming_shell_data/injector.js")); |
| std::string test_harness_string; |
| CHECK(base::ReadFileToString(test_harness_js_path, &test_harness_string)); |
| frame->AddBeforeLoadJavaScript( |
| kAddBeforeLoadJavaScriptID, {"*"}, |
| base::MemBufferFromString(std::move(test_harness_string), |
| "test-harness-js"), |
| CallbackToFitFunction(add_before_load_javascript_result.GetCallback())); |
| if (!add_before_load_javascript_result.Wait()) { |
| LOG(ERROR) << "AddBeforeLoadJavaScript timed out."; |
| return 1; |
| } |
| if (add_before_load_javascript_result.Get().is_err()) { |
| LOG(ERROR) << "AddBeforeLoadJavaScript failed."; |
| return 1; |
| } |
| |
| // Send `sender_message_port` to a Sender and start it. |
| cast_streaming::CastStreamingTestSender sender; |
| sender.Start(std::move(sender_message_port), net::IPAddress::IPv6Localhost(), |
| std::nullopt, GetDefaultVideoConfig()); |
| |
| // Navigate `frame` to `receiver.html`. |
| fuchsia::web::LoadUrlParams load_params; |
| load_params.set_type(fuchsia::web::LoadUrlReason::TYPED); |
| load_params.set_was_user_activated(true); |
| fuchsia::web::NavigationControllerPtr nav_controller; |
| frame->GetNavigationController(nav_controller.NewRequest()); |
| if (!LoadUrlAndExpectResponse(nav_controller, std::move(load_params), |
| kCastStreamingWebUrl)) { |
| LOG(ERROR) << "LoadUrl failed."; |
| return 1; |
| } |
| |
| // Log the debugging port, if debugging is requested. |
| base::test::TestFuture<fuchsia::web::Context_GetRemoteDebuggingPort_Result> |
| get_remote_debugging_port_result; |
| context->GetRemoteDebuggingPort( |
| CallbackToFitFunction(get_remote_debugging_port_result.GetCallback())); |
| if (!get_remote_debugging_port_result.Wait()) { |
| LOG(ERROR) << "Remote debugging service timed out."; |
| return 1; |
| } |
| if (get_remote_debugging_port_result.Get().is_err()) { |
| LOG(ERROR) << "Remote debugging service was not opened."; |
| return 1; |
| } |
| LOG(INFO) << "Remote debugging port: " |
| << get_remote_debugging_port_result.Get().response().port; |
| |
| if (!sender.RunUntilActive()) { |
| LOG(ERROR) << "RunUntilActive failed."; |
| return 1; |
| } |
| |
| // Load video. |
| base::FilePath video_file( |
| pkg_path.AppendASCII("media/test/data/bear-1280x720.ivf")); |
| std::optional<std::vector<uint8_t>> video_stream = |
| base::ReadFileToBytes(video_file); |
| CHECK(video_stream.has_value()); |
| media::test::EncodedDataHelper video_helper(video_stream.value(), |
| media::VideoCodec::kVP8); |
| |
| // Send first key frame. |
| scoped_refptr<media::DecoderBuffer> video_decoder_buffer = |
| video_helper.GetNextBuffer(); |
| video_decoder_buffer->set_timestamp(base::TimeDelta()); |
| video_decoder_buffer->set_is_key_frame(true); |
| sender.SendVideoBuffer(video_decoder_buffer); |
| |
| run_loop.Run(); |
| |
| return 0; |
| } |