| // 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 "chrome/browser/ui/ash/projector/screencast_manager.h" |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "ash/webui/projector_app/public/cpp/projector_app_constants.h" |
| #include "ash/webui/projector_app/public/mojom/projector_types.mojom.h" |
| #include "base/check.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/location.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/bind_post_task.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "chrome/browser/ash/drive/drive_integration_service.h" |
| #include "chrome/browser/ash/extensions/file_manager/scoped_suppress_drive_notifications_for_path.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/ash/projector/projector_drivefs_provider.h" |
| #include "chrome/browser/ui/ash/projector/projector_utils.h" |
| #include "chrome/services/media_gallery_util/public/cpp/local_media_data_source_factory.h" |
| #include "chrome/services/media_gallery_util/public/cpp/safe_media_metadata_parser.h" |
| #include "chrome/services/media_gallery_util/public/mojom/media_parser.mojom.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| constexpr int kOneSecondInMillisecond = 1000; |
| |
| void OnMediaMetadataParsed( |
| projector::mojom::VideoInfoPtr video, |
| ProjectorAppClient::OnGetVideoCallback callback, |
| const base::FilePath& video_path, |
| std::unique_ptr<SafeMediaMetadataParser> parser_keep_alive, |
| bool parse_success, |
| chrome::mojom::MediaMetadataPtr metadata, |
| std::unique_ptr<std::vector<metadata::AttachedImage>> attached_images) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| if (!parse_success || !metadata || !metadata->duration) { |
| // TODO(b/242748137): Add test to cover this error message. |
| std::move(callback).Run( |
| projector::mojom::GetVideoResult::NewErrorMessage(base::StringPrintf( |
| "Failed get media metadata or duration with video file id=%s", |
| video->file_id.c_str()))); |
| return; |
| } |
| |
| if (metadata->duration < 0) { |
| // The video file might be malformed if duration is -1. |
| std::move(callback).Run(projector::mojom::GetVideoResult::NewErrorMessage( |
| base::StringPrintf("Media might be malformed with video file id=%s", |
| video->file_id.c_str()))); |
| return; |
| } |
| |
| video->duration_millis = metadata->duration * kOneSecondInMillisecond; |
| // Launches app on UI thread when duration is valid. |
| |
| // Even though the video file id is not a file path, we need to pass it to the |
| // launch event to match up with the original request. |
| base::FilePath video_id_as_path(video->file_id); |
| SendFilesToProjectorApp({video_id_as_path, video_path}); |
| std::move(callback).Run( |
| projector::mojom::GetVideoResult::NewVideo(std::move(video))); |
| } |
| |
| // Caller of this method requires a sequenced context. Gets video metadata for |
| // `video_path`, triggers the flow to set `duration_millis` in the given video |
| // object and triggers the callback. Should not be called on UI thread. |
| void GetVideoMetadata(const base::FilePath& video_path, |
| projector::mojom::VideoInfoPtr video, |
| ProjectorAppClient::OnGetVideoCallback callback) { |
| DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| int64_t size_in_byte; |
| if (!base::PathExists(video_path) || |
| !base::GetFileSize(video_path, &size_in_byte)) { |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| std::move(callback), |
| projector::mojom::GetVideoResult::NewErrorMessage( |
| base::StringPrintf( |
| "Path does not exist or cannot read video size with " |
| "video file id=%s", |
| video->file_id.c_str())))); |
| return; |
| } |
| |
| auto parser = std::make_unique<SafeMediaMetadataParser>( |
| size_in_byte, kProjectorMediaMimeType, |
| /*get_attached_images=*/false, |
| std::make_unique<LocalMediaDataSourceFactory>( |
| video_path, base::SingleThreadTaskRunner::GetCurrentDefault())); |
| |
| // Uses raw pointer since the `parser` is moved before calling Start(). |
| SafeMediaMetadataParser* parser_ptr = parser.get(); |
| parser_ptr->Start(base::BindPostTask( |
| content::GetUIThreadTaskRunner({}), |
| base::BindOnce(&OnMediaMetadataParsed, std::move(video), |
| std::move(callback), video_path, std::move(parser)))); |
| } |
| |
| } // namespace |
| |
| // CreateSingleThreadTaskRunner for `video_metadata_task_runner` since |
| // LocalMediaDataSource requires a single thread context. |
| ScreencastManager::ScreencastManager() |
| : video_metadata_task_runner_( |
| base::ThreadPool::CreateSingleThreadTaskRunner({base::MayBlock()})) {} |
| ScreencastManager::~ScreencastManager() = default; |
| |
| void ScreencastManager::GetVideo( |
| const std::string& video_file_id, |
| const absl::optional<std::string>& resource_key, |
| ProjectorAppClient::OnGetVideoCallback callback) const { |
| // TODO(b/237089852): Handle the resource key once LocateFilesByItemIds() |
| // supports it. |
| |
| drive::DriveIntegrationService* integration_service = |
| ProjectorDriveFsProvider::GetActiveDriveIntegrationService(); |
| integration_service->LocateFilesByItemIds( |
| {video_file_id}, |
| base::BindOnce(&ScreencastManager::OnVideoFilePathLocated, |
| weak_ptr_factory_.GetMutableWeakPtr(), video_file_id, |
| std::move(callback))); |
| } |
| |
| void ScreencastManager::ResetScopeSuppressDriveNotifications() { |
| suppress_drive_notifications_for_path_.reset(); |
| } |
| |
| void ScreencastManager::OnVideoFilePathLocated( |
| const std::string& video_id, |
| ProjectorAppClient::OnGetVideoCallback callback, |
| absl::optional<std::vector<drivefs::mojom::FilePathOrErrorPtr>> paths) { |
| if (!paths || paths.value().size() != 1u) { |
| std::move(callback).Run(projector::mojom::GetVideoResult::NewErrorMessage( |
| base::StringPrintf("Failed to find DriveFS path with video file id=%s", |
| video_id.c_str()))); |
| |
| return; |
| } |
| |
| const auto& path_or_error = paths.value()[0]; |
| if (path_or_error->is_error() || !path_or_error->is_path()) { |
| std::move(callback).Run(projector::mojom::GetVideoResult::NewErrorMessage( |
| base::StringPrintf("Failed to fetch DriveFS file with video file id=%s " |
| "and error code=%d", |
| video_id.c_str(), path_or_error->get_error()))); |
| return; |
| } |
| |
| const base::FilePath& relative_drivefs_path = path_or_error->get_path(); |
| if (!relative_drivefs_path.MatchesExtension(kProjectorMediaFileExtension)) { |
| std::move(callback).Run(projector::mojom::GetVideoResult::NewErrorMessage( |
| base::StringPrintf("Failed to fetch video file with video file id=%s", |
| video_id.c_str()))); |
| return; |
| } |
| |
| const base::FilePath& mounted_path = |
| ProjectorDriveFsProvider::GetDriveFsMountPointPath(); |
| const base::FilePath& video_path = mounted_path.Append(relative_drivefs_path); |
| |
| // Suppresses the notification before calling GetVideoMetadata, which might |
| // trigger file syncing event that triggers Drive notifications. |
| suppress_drive_notifications_for_path_ = |
| std::make_unique<file_manager::ScopedSuppressDriveNotificationsForPath>( |
| ProfileManager::GetActiveUserProfile(), |
| base::FilePath("/").Append(relative_drivefs_path)); |
| |
| // Post task to: |
| // 1. Fill the video duration which also serves the purpose of video file |
| // validation. |
| // 2. If the duration is valid, trigger LaunchProjectorAppWithFiles(). |
| // 3. Trigger `callback`. |
| auto video = projector::mojom::VideoInfo::New(); |
| video->file_id = video_id; |
| video_metadata_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&GetVideoMetadata, video_path, std::move(video), |
| std::move(callback))); |
| } |
| |
| } // namespace ash |