blob: a24cc3172a293679ab889bc372864cd161c1ff74 [file] [log] [blame]
// Copyright 2018 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 "chrome/browser/media/router/providers/cast/cast_session_tracker.h"
#include "base/stl_util.h"
#include "chrome/browser/media/router/providers/cast/chrome_cast_message_handler.h"
#include "chrome/browser/media/router/providers/cast/dual_media_sink_service.h"
#include "components/cast_channel/cast_socket_service.h"
namespace media_router {
CastSessionTracker::Observer::~Observer() = default;
// static
CastSessionTracker* CastSessionTracker::GetInstance() {
if (instance_for_test_)
return instance_for_test_;
static CastSessionTracker* instance = new CastSessionTracker(
DualMediaSinkService::GetInstance()->GetCastMediaSinkServiceImpl(),
GetCastMessageHandler(),
cast_channel::CastSocketService::GetInstance()->task_runner());
return instance;
}
void CastSessionTracker::AddObserver(CastSessionTracker::Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observers_.AddObserver(observer);
}
void CastSessionTracker::RemoveObserver(
CastSessionTracker::Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observers_.RemoveObserver(observer);
}
const CastSessionTracker::SessionMap& CastSessionTracker::sessions_by_sink_id()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return sessions_by_sink_id_;
}
CastSession* CastSessionTracker::GetSessionById(
const std::string& session_id) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it =
std::find_if(sessions_by_sink_id_.begin(), sessions_by_sink_id_.end(),
[&session_id](const auto& entry) {
return entry.second->session_id() == session_id;
});
return it != sessions_by_sink_id_.end() ? it->second.get() : nullptr;
}
CastSessionTracker::CastSessionTracker(
MediaSinkServiceBase* media_sink_service,
cast_channel::CastMessageHandler* message_handler,
const scoped_refptr<base::SequencedTaskRunner>& task_runner)
: media_sink_service_(media_sink_service),
message_handler_(message_handler) {
DETACH_FROM_SEQUENCE(sequence_checker_);
// This is safe because |this| will never be destroyed (except in unit tests).
task_runner->PostTask(FROM_HERE,
base::BindOnce(&CastSessionTracker::InitOnIoThread,
base::Unretained(this)));
}
CastSessionTracker::~CastSessionTracker() = default;
// This method needs to be separate from the constructor because the constructor
// needs to be called from the UI thread, but observers can only be added in an
// IO thread.
void CastSessionTracker::InitOnIoThread() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
media_sink_service_->AddObserver(this);
message_handler_->AddObserver(this);
}
void CastSessionTracker::HandleReceiverStatusMessage(
const MediaSinkInternal& sink,
const base::Value& message) {
const base::Value* status =
message.FindKeyOfType("status", base::Value::Type::DICTIONARY);
auto session = status ? CastSession::From(sink, *status) : nullptr;
const MediaSink::Id& sink_id = sink.sink().id();
if (!session) {
if (sessions_by_sink_id_.erase(sink_id)) {
for (auto& observer : observers_)
observer.OnSessionRemoved(sink);
}
return;
}
auto it = sessions_by_sink_id_.find(sink_id);
if (it == sessions_by_sink_id_.end()) {
it = sessions_by_sink_id_.emplace(sink_id, std::move(session)).first;
} else {
it->second->UpdateSession(std::move(session));
}
for (auto& observer : observers_)
observer.OnSessionAddedOrUpdated(sink, *it->second);
}
const MediaSinkInternal* CastSessionTracker::GetSinkByChannelId(
int channel_id) const {
for (const auto& sink : media_sink_service_->GetSinks()) {
if (sink.second.cast_data().cast_channel_id == channel_id)
return &sink.second;
}
return nullptr;
}
void CastSessionTracker::OnSinkAddedOrUpdated(const MediaSinkInternal& sink) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
message_handler_->RequestReceiverStatus(sink.cast_data().cast_channel_id);
}
void CastSessionTracker::OnSinkRemoved(const MediaSinkInternal& sink) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (sessions_by_sink_id_.erase(sink.sink().id())) {
for (auto& observer : observers_)
observer.OnSessionRemoved(sink);
}
}
void CastSessionTracker::OnInternalMessage(
int channel_id,
const cast_channel::InternalMessage& message) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// It's possible for a session to have been started/discovered on a different
// channel_id than the one we got the latest RECEIVER_STATUS on, but this
// should be okay, since we are mapping everything back to the sink_id, which
// should be constant.
const MediaSinkInternal* sink = GetSinkByChannelId(channel_id);
if (!sink) {
DVLOG(2) << "Received message from channel without sink: " << channel_id;
return;
}
if (message.type == cast_channel::CastMessageType::kReceiverStatus)
HandleReceiverStatusMessage(*sink, message.message);
}
// static
void CastSessionTracker::SetInstanceForTest(
CastSessionTracker* session_tracker) {
instance_for_test_ = session_tracker;
}
void CastSessionTracker::SetSessionForTest(
const MediaSink::Id& sink_id,
std::unique_ptr<CastSession> session) {
DCHECK(session);
sessions_by_sink_id_[sink_id] = std::move(session);
}
// static
CastSessionTracker* CastSessionTracker::instance_for_test_ = nullptr;
} // namespace media_router