| // Copyright 2015 Google Inc. All Rights Reserved. |
| // |
| // 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. |
| |
| #include <memory> |
| #include <string> |
| |
| #include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/trace_event/trace_event.h" |
| |
| #include "cobalt/webdriver/screencast/screencast_module.h" |
| |
| namespace cobalt { |
| namespace webdriver { |
| namespace screencast { |
| |
| namespace { |
| const char kJpegContentType[] = "image/jpeg"; |
| // Add screencast frame rate as 30 fps. |
| const int kScreencastFramesPerSecond = 30; |
| } |
| |
| ScreencastModule::ScreencastModule( |
| int server_port, const std::string& listen_ip, |
| const GetScreenshotFunction& screenshot_function) |
| : screenshot_dispatcher_(new WebDriverDispatcher()), |
| screenshot_thread_("ScreencstDrvThd"), |
| incoming_requests_(), |
| last_served_request_(-1), |
| screenshot_function_(screenshot_function), |
| no_screenshots_pending_(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED), |
| num_screenshots_processing_(0) { |
| DETACH_FROM_THREAD(thread_checker_); |
| |
| screenshot_dispatcher_->RegisterCommand( |
| WebDriverServer::kGet, "/screenshot/:id", |
| base::Bind(&ScreencastModule::PutRequestInQueue, base::Unretained(this))); |
| // Start the thread and create the HTTP server on that thread. |
| screenshot_thread_.StartWithOptions( |
| base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); |
| screenshot_thread_.message_loop()->task_runner()->PostTask( |
| FROM_HERE, base::Bind(&ScreencastModule::StartServer, |
| base::Unretained(this), server_port, listen_ip)); |
| } |
| |
| ScreencastModule::~ScreencastModule() { |
| TRACE_EVENT0("cobalt::Screencast", "ScreencastModule::~ScreencastModule()"); |
| screenshot_thread_.message_loop()->task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&ScreencastModule::StopTimer, base::Unretained(this))); |
| |
| no_screenshots_pending_.Wait(); |
| |
| screenshot_thread_.message_loop()->task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&ScreencastModule::StopServer, base::Unretained(this))); |
| screenshot_thread_.Stop(); |
| } |
| |
| void ScreencastModule::StartServer(int server_port, |
| const std::string& listen_ip) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| // Create a new WebDriverServer and pass in the Dispatcher. |
| |
| screenshot_server_.reset(new WebDriverServer( |
| server_port, listen_ip, |
| base::Bind(&WebDriverDispatcher::HandleWebDriverServerRequest, |
| base::Unretained(screenshot_dispatcher_.get())), |
| "Cobalt.Server.Screencast")); |
| |
| screenshot_timer_.reset(new base::RepeatingTimer()); |
| |
| const base::Closure screenshot_event = |
| base::Bind(&ScreencastModule::TakeScreenshot, base::Unretained(this)); |
| screenshot_timer_->Start(FROM_HERE, |
| base::TimeDelta::FromMilliseconds( |
| 1000.0f / kScreencastFramesPerSecond), |
| screenshot_event); |
| } |
| |
| void ScreencastModule::StopTimer() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| screenshot_timer_.reset(); |
| |
| if (num_screenshots_processing_ < 1) { |
| no_screenshots_pending_.Signal(); |
| } |
| } |
| |
| void ScreencastModule::StopServer() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| // Clear out queue of requests. |
| while (!incoming_requests_.empty()) { |
| scoped_refptr<WaitingRequest> next_request = incoming_requests_.front(); |
| incoming_requests_.pop(); |
| std::unique_ptr<base::Value> message = std::unique_ptr<base::Value>(); |
| // Send rejection to request with invalid ID. |
| next_request->result_handler->SendResult( |
| base::nullopt, protocol::Response::kUnknownError, std::move(message)); |
| } |
| screenshot_server_.reset(); |
| } |
| |
| void ScreencastModule::PutRequestInQueue( |
| const base::Value* parameters, |
| const WebDriverDispatcher::PathVariableMap* path_variables, |
| std::unique_ptr<WebDriverDispatcher::CommandResultHandler> result_handler) { |
| TRACE_EVENT0("cobalt::Screencast", "ScreencastModule::PutRequestInQueue()"); |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| scoped_refptr<WaitingRequest> current_request = new WaitingRequest; |
| current_request->result_handler = std::move(result_handler); |
| // The id of request, e.g. screencast/2 would be 2. |
| if (base::StringToInt(path_variables->GetVariable(":id"), |
| &(current_request->request_id))) { |
| incoming_requests_.push(current_request); |
| } else { |
| // Send rejection to request with invalid ID. |
| std::unique_ptr<base::Value> message = std::unique_ptr<base::Value>(); |
| result_handler->SendResult(base::nullopt, protocol::Response::kUnknownError, |
| std::move(message)); |
| } |
| } |
| |
| void ScreencastModule::SendScreenshotToNextInQueue( |
| const scoped_refptr<loader::image::EncodedStaticImage>& screenshot) { |
| TRACE_EVENT0("cobalt::Screencast", |
| "ScreencastModule::SendScreenshotToNextInQueue()"); |
| |
| if (base::MessageLoop::current() != screenshot_thread_.message_loop()) { |
| screenshot_thread_.message_loop()->task_runner()->PostTask( |
| FROM_HERE, base::Bind(&ScreencastModule::SendScreenshotToNextInQueue, |
| base::Unretained(this), screenshot)); |
| return; |
| } |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| num_screenshots_processing_--; |
| // If the timer is off we can check if it's ready to be shutdown. |
| if (screenshot_timer_.get() == nullptr && num_screenshots_processing_ < 1) { |
| no_screenshots_pending_.Signal(); |
| } |
| |
| while (!incoming_requests_.empty()) { |
| scoped_refptr<WaitingRequest> next_request = incoming_requests_.front(); |
| incoming_requests_.pop(); |
| std::unique_ptr<base::Value> message = std::unique_ptr<base::Value>(); |
| // Check if request is valid. |
| if (next_request->request_id > last_served_request_) { |
| // Send screenshot. |
| last_served_request_ = next_request->request_id; |
| next_request->result_handler->SendResultWithContentType( |
| protocol::Response::kSuccess, kJpegContentType, |
| reinterpret_cast<char*>(screenshot->GetMemory()), |
| screenshot->GetEstimatedSizeInBytes()); |
| return; |
| } else { |
| // Send rejection to request with invalid ID. |
| next_request->result_handler->SendResult( |
| base::nullopt, protocol::Response::kUnknownError, std::move(message)); |
| } |
| } |
| } |
| |
| void ScreencastModule::TakeScreenshot() { |
| TRACE_EVENT0("cobalt::Screencast", "ScreencastModule::TakeScreenshot()"); |
| if (num_screenshots_processing_ < max_num_screenshots_processing_) { |
| num_screenshots_processing_++; |
| screenshot_function_.Run( |
| loader::image::EncodedStaticImage::ImageFormat::kJPEG, |
| /*clip_rect=*/base::nullopt, |
| base::Bind(&ScreencastModule::SendScreenshotToNextInQueue, |
| base::Unretained(this))); |
| } |
| } |
| |
| } // namespace screencast |
| } // namespace webdriver |
| } // namespace cobalt |