blob: 863ad756ed99645b9a157f7e5bbfa319e7f002c8 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/services/libassistant/media_controller.h"
#include "base/strings/string_util.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "chromeos/assistant/internal/util_headers.h"
#include "chromeos/services/assistant/public/shared/utils.h"
#include "libassistant/shared/internal_api/assistant_manager_internal.h"
#include "libassistant/shared/public/assistant_manager.h"
#include "libassistant/shared/public/media_manager.h"
namespace chromeos {
namespace libassistant {
namespace {
constexpr char kNextTrackClientOp[] = "media.NEXT";
constexpr char kPauseTrackClientOp[] = "media.PAUSE";
constexpr char kPlayMediaClientOp[] = "media.PLAY_MEDIA";
constexpr char kPrevTrackClientOp[] = "media.PREVIOUS";
constexpr char kResumeTrackClientOp[] = "media.RESUME";
constexpr char kStopTrackClientOp[] = "media.STOP";
constexpr char kIntentActionView[] = "android.intent.action.VIEW";
constexpr char kWebUrlPrefix[] = "http";
using chromeos::assistant::AndroidAppInfo;
using chromeos::assistant::shared::PlayMediaArgs;
// A macro which ensures we are running on the mojom thread.
#define ENSURE_MOJOM_THREAD(method, ...) \
if (!mojom_task_runner_->RunsTasksInCurrentSequence()) { \
mojom_task_runner_->PostTask( \
FROM_HERE, \
base::BindOnce(method, weak_factory_.GetWeakPtr(), ##__VA_ARGS__)); \
return; \
}
assistant_client::MediaStatus::PlaybackState ToPlaybackState(
mojom::PlaybackState input) {
switch (input) {
case mojom::PlaybackState::kError:
return assistant_client::MediaStatus::PlaybackState::ERROR;
case mojom::PlaybackState::kIdle:
return assistant_client::MediaStatus::PlaybackState::IDLE;
case mojom::PlaybackState::kNewTrack:
return assistant_client::MediaStatus::PlaybackState::NEW_TRACK;
case mojom::PlaybackState::kPaused:
return assistant_client::MediaStatus::PlaybackState::PAUSED;
case mojom::PlaybackState::kPlaying:
return assistant_client::MediaStatus::PlaybackState::PLAYING;
}
}
assistant_client::MediaStatus ToMediaStatus(const mojom::MediaState& input) {
assistant_client::MediaStatus result;
if (input.metadata) {
result.metadata.album = input.metadata->album;
result.metadata.title = input.metadata->title;
result.metadata.artist = input.metadata->artist;
}
result.playback_state = ToPlaybackState(input.playback_state);
return result;
}
mojom::PlaybackState ToPlaybackState(
assistant_client::MediaStatus::PlaybackState input) {
switch (input) {
case assistant_client::MediaStatus::PlaybackState::ERROR:
return mojom::PlaybackState::kError;
case assistant_client::MediaStatus::PlaybackState::IDLE:
return mojom::PlaybackState::kIdle;
case assistant_client::MediaStatus::PlaybackState::NEW_TRACK:
return mojom::PlaybackState::kNewTrack;
case assistant_client::MediaStatus::PlaybackState::PAUSED:
return mojom::PlaybackState::kPaused;
case assistant_client::MediaStatus::PlaybackState::PLAYING:
return mojom::PlaybackState::kPlaying;
}
}
mojom::MediaStatePtr ToMediaStatePtr(
const assistant_client::MediaStatus& input) {
mojom::MediaStatePtr result = mojom::MediaState::New();
if (!input.metadata.album.empty() || !input.metadata.title.empty() ||
!input.metadata.artist.empty()) {
result->metadata = mojom::MediaMetadata::New();
result->metadata->album = input.metadata.album;
result->metadata->title = input.metadata.title;
result->metadata->artist = input.metadata.artist;
}
result->playback_state = ToPlaybackState(input.playback_state);
return result;
}
std::string GetAndroidIntentUrlFromMediaArgs(
const std::string& play_media_args_proto) {
PlayMediaArgs play_media_args;
if (play_media_args.ParseFromString(play_media_args_proto)) {
for (auto media_item : play_media_args.media_item()) {
if (!media_item.has_uri())
continue;
return media_item.uri();
}
}
return std::string();
}
absl::optional<AndroidAppInfo> GetAppInfoFromMediaArgs(
const std::string& play_media_args_proto) {
PlayMediaArgs play_media_args;
if (play_media_args.ParseFromString(play_media_args_proto)) {
for (auto& media_item : play_media_args.media_item()) {
if (media_item.has_provider() &&
media_item.provider().has_android_app_info()) {
auto& app_info = media_item.provider().android_app_info();
AndroidAppInfo result;
result.package_name = app_info.package_name();
result.version = app_info.app_version();
result.localized_app_name = app_info.localized_app_name();
result.intent = app_info.android_intent();
return result;
}
}
}
return absl::nullopt;
}
std::string GetWebUrlFromMediaArgs(const std::string& play_media_args_proto) {
PlayMediaArgs play_media_args;
if (play_media_args.ParseFromString(play_media_args_proto)) {
for (auto media_item : play_media_args.media_item()) {
if (!media_item.has_uri())
continue;
// For web url in browser.
if (base::StartsWith(media_item.uri(), kWebUrlPrefix))
return media_item.uri();
}
}
return std::string();
}
} // namespace
class MediaController::LibassistantMediaManagerListener
: public assistant_client::MediaManager::Listener {
public:
explicit LibassistantMediaManagerListener(MediaController* parent)
: parent_(parent),
mojom_task_runner_(base::SequencedTaskRunnerHandle::Get()) {}
LibassistantMediaManagerListener(const LibassistantMediaManagerListener&) =
delete;
LibassistantMediaManagerListener& operator=(
const LibassistantMediaManagerListener&) = delete;
~LibassistantMediaManagerListener() override = default;
// assistant_client::MediaManager::Listener implementation:
// Called from the Libassistant thread.
void OnPlaybackStateChange(
const assistant_client::MediaStatus& media_status) override {
ENSURE_MOJOM_THREAD(
&LibassistantMediaManagerListener::OnPlaybackStateChange, media_status);
VLOG(1) << "Sending playback state update";
delegate().OnPlaybackStateChanged(ToMediaStatePtr(media_status));
}
private:
mojom::MediaDelegate& delegate() { return *parent_->delegate_; }
MediaController* const parent_;
scoped_refptr<base::SequencedTaskRunner> mojom_task_runner_;
base::WeakPtrFactory<LibassistantMediaManagerListener> weak_factory_{this};
};
class MediaController::LibassistantMediaHandler {
public:
LibassistantMediaHandler(
MediaController* parent,
assistant_client::AssistantManagerInternal* assistant_manager_internal)
: parent_(parent),
mojom_task_runner_(base::SequencedTaskRunnerHandle::Get()) {
// Register handler for media actions.
assistant_manager_internal->RegisterFallbackMediaHandler(
[this](std::string action_name, std::string media_action_args_proto) {
HandleMediaAction(action_name, media_action_args_proto);
});
}
LibassistantMediaHandler(const LibassistantMediaHandler&) = delete;
LibassistantMediaHandler& operator=(const LibassistantMediaHandler&) = delete;
~LibassistantMediaHandler() = default;
private:
// Called from the Libassistant thread.
void HandleMediaAction(const std::string& action_name,
const std::string& media_action_args_proto) {
ENSURE_MOJOM_THREAD(&LibassistantMediaHandler::HandleMediaAction,
action_name, media_action_args_proto);
if (action_name == kPlayMediaClientOp)
OnPlayMedia(media_action_args_proto);
else
OnMediaControlAction(action_name, media_action_args_proto);
}
void OnPlayMedia(const std::string& play_media_args_proto) {
absl::optional<AndroidAppInfo> app_info =
GetAppInfoFromMediaArgs(play_media_args_proto);
if (app_info) {
OnOpenMediaAndroidIntent(play_media_args_proto,
std::move(app_info.value()));
} else {
OnOpenUrl(play_media_args_proto);
}
}
// Handle android media playback intent.
void OnOpenMediaAndroidIntent(const std::string& play_media_args_proto,
AndroidAppInfo app_info) {
app_info.action = kIntentActionView;
if (app_info.intent.empty()) {
std::string url = GetAndroidIntentUrlFromMediaArgs(play_media_args_proto);
if (!url.empty())
app_info.intent = url;
}
VLOG(1) << "Playing android media";
delegate().PlayAndroidMedia(std::move(app_info));
}
void OnOpenUrl(const std::string& play_media_args_proto) {
std::string url = GetWebUrlFromMediaArgs(play_media_args_proto);
// Fallback to web URL.
if (!url.empty()) {
VLOG(1) << "Playing web media";
delegate().PlayWebMedia(url);
}
}
void OnMediaControlAction(const std::string& action_name,
const std::string& media_action_args_proto) {
if (action_name == kPauseTrackClientOp) {
VLOG(1) << "Pausing media playback";
delegate().Pause();
return;
}
if (action_name == kResumeTrackClientOp) {
VLOG(1) << "Resuming media playback";
delegate().Resume();
return;
}
if (action_name == kNextTrackClientOp) {
VLOG(1) << "Playing next track";
delegate().NextTrack();
return;
}
if (action_name == kPrevTrackClientOp) {
VLOG(1) << "Playing previous track";
delegate().PreviousTrack();
return;
}
if (action_name == kStopTrackClientOp) {
VLOG(1) << "Stop media playback";
delegate().Stop();
return;
}
}
mojom::MediaDelegate& delegate() { return *parent_->delegate_; }
MediaController* const parent_;
scoped_refptr<base::SequencedTaskRunner> mojom_task_runner_;
base::WeakPtrFactory<LibassistantMediaHandler> weak_factory_{this};
};
MediaController::MediaController()
: listener_(std::make_unique<LibassistantMediaManagerListener>(this)) {}
MediaController::~MediaController() = default;
void MediaController::Bind(
mojo::PendingReceiver<mojom::MediaController> receiver,
mojo::PendingRemote<mojom::MediaDelegate> delegate) {
receiver_.Bind(std::move(receiver));
delegate_.Bind(std::move(delegate));
}
void MediaController::ResumeInternalMediaPlayer() {
VLOG(1) << "Resume internal media player";
if (media_manager())
media_manager()->Resume();
}
void MediaController::PauseInternalMediaPlayer() {
VLOG(1) << "Pause internal media player";
if (media_manager())
media_manager()->Pause();
}
void MediaController::SetExternalPlaybackState(mojom::MediaStatePtr state) {
DCHECK(!state.is_null());
VLOG(1) << "Update external playback state to " << state->playback_state;
if (media_manager())
media_manager()->SetExternalPlaybackState(ToMediaStatus(*state));
}
void MediaController::OnAssistantManagerRunning(
assistant_client::AssistantManager* assistant_manager,
assistant_client::AssistantManagerInternal* assistant_manager_internal) {
assistant_manager_ = assistant_manager;
// Media manager should be created when Libassistant signals it is running.
DCHECK(media_manager());
handler_ = std::make_unique<LibassistantMediaHandler>(
this, assistant_manager_internal);
media_manager()->AddListener(listener_.get());
}
void MediaController::OnDestroyingAssistantManager(
assistant_client::AssistantManager* assistant_manager,
assistant_client::AssistantManagerInternal* assistant_manager_internal) {
assistant_manager_ = nullptr;
}
void MediaController::OnAssistantManagerDestroyed() {
// Handler can only be unset after the |AssistantManagerInternal| has been
// destroyed, as |AssistantManagerInternal| will call the handler.
handler_ = nullptr;
}
assistant_client::MediaManager* MediaController::media_manager() {
return assistant_manager_ ? assistant_manager_->GetMediaManager() : nullptr;
}
} // namespace libassistant
} // namespace chromeos