From 267593067f71fceb09ed55850ad149a4f84f0705 Mon Sep 17 00:00:00 2001 From: btolsch Date: Fri, 7 Feb 2020 15:02:08 -0800 Subject: Add Cast app availability tracker This change ports the CastAppAvailabilityTracker from Chromium in preparation for the app discovery service. Bug: openscreen:60 Change-Id: Id450f6f616a35ae1c1959fa3326593370daf072c Reviewed-on: https://chromium-review.googlesource.com/c/openscreen/+/2029249 Commit-Queue: Brandon Tolsch Reviewed-by: Takumi Fujimoto --- cast/common/channel/message_util.cc | 16 ++ cast/common/channel/message_util.h | 8 + cast/sender/BUILD.gn | 19 +++ cast/sender/DEPS | 6 +- cast/sender/cast_app_availability_tracker.cc | 165 +++++++++++++++++++++ cast/sender/cast_app_availability_tracker.h | 124 ++++++++++++++++ .../cast_app_availability_tracker_unittest.cc | 159 ++++++++++++++++++++ cast/sender/public/DEPS | 7 +- cast/sender/public/README.md | 2 +- cast/sender/public/cast_media_source.cc | 45 ++++++ cast/sender/public/cast_media_source.h | 42 ++++++ 11 files changed, 583 insertions(+), 10 deletions(-) create mode 100644 cast/sender/cast_app_availability_tracker.cc create mode 100644 cast/sender/cast_app_availability_tracker.h create mode 100644 cast/sender/cast_app_availability_tracker_unittest.cc create mode 100644 cast/sender/public/cast_media_source.cc create mode 100644 cast/sender/public/cast_media_source.h (limited to 'cast') diff --git a/cast/common/channel/message_util.cc b/cast/common/channel/message_util.cc index b4a7f9f6..ee9a91f1 100644 --- a/cast/common/channel/message_util.cc +++ b/cast/common/channel/message_util.cc @@ -4,6 +4,8 @@ #include "cast/common/channel/message_util.h" +#include "util/logging.h" + namespace openscreen { namespace cast { namespace { @@ -22,6 +24,20 @@ CastMessage MakeConnectionMessage(const std::string& source_id, } // namespace +std::string ToString(AppAvailabilityResult availability) { + switch (availability) { + case AppAvailabilityResult::kAvailable: + return "Available"; + case AppAvailabilityResult::kUnavailable: + return "Unavailable"; + case AppAvailabilityResult::kUnknown: + return "Unknown"; + default: + OSP_NOTREACHED(); + return "bad value"; + } +} + CastMessage MakeSimpleUTF8Message(const std::string& namespace_, std::string payload) { CastMessage message; diff --git a/cast/common/channel/message_util.h b/cast/common/channel/message_util.h index b9177e87..82153962 100644 --- a/cast/common/channel/message_util.h +++ b/cast/common/channel/message_util.h @@ -52,6 +52,14 @@ static constexpr char kMessageTypeConnect[] = "CONNECT"; static constexpr char kMessageTypeClose[] = "CLOSE"; static constexpr char kMessageTypeConnected[] = "CONNECTED"; +enum class AppAvailabilityResult { + kAvailable, + kUnavailable, + kUnknown, +}; + +std::string ToString(AppAvailabilityResult availability); + inline bool IsAuthMessage(const ::cast::channel::CastMessage& message) { return message.namespace_() == kAuthNamespace; } diff --git a/cast/sender/BUILD.gn b/cast/sender/BUILD.gn index ae36bf27..fd295b92 100644 --- a/cast/sender/BUILD.gn +++ b/cast/sender/BUILD.gn @@ -27,16 +27,35 @@ source_set("channel") { ] } +source_set("sender") { + sources = [ + "cast_app_availability_tracker.cc", + "cast_app_availability_tracker.h", + "public/cast_media_source.cc", + "public/cast_media_source.h", + ] + + public_deps = [ + ":channel", + "../../platform", + "../../util", + ] +} + source_set("unittests") { testonly = true sources = [ + "cast_app_availability_tracker_unittest.cc", "channel/cast_auth_util_unittest.cc", ] deps = [ ":channel", + ":sender", "../../platform", + "../../platform:test", "../../testing/util", + "../../third_party/googletest:gmock", "../../third_party/googletest:gtest", "../common/certificate/proto:certificate_proto", "../common/certificate/proto:certificate_unittest_proto", diff --git a/cast/sender/DEPS b/cast/sender/DEPS index 7ab7a51a..48f4daee 100644 --- a/cast/sender/DEPS +++ b/cast/sender/DEPS @@ -1,7 +1,7 @@ # -*- Mode: Python; -*- include_rules = [ - # libcast sender code must not depend on the receiver. - '+cast/common', - '+cast/sender' + # libcast sender code must not depend on the receiver. + '+cast/common', + '+cast/sender', ] diff --git a/cast/sender/cast_app_availability_tracker.cc b/cast/sender/cast_app_availability_tracker.cc new file mode 100644 index 00000000..6c21c799 --- /dev/null +++ b/cast/sender/cast_app_availability_tracker.cc @@ -0,0 +1,165 @@ +// Copyright 2020 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 "cast/sender/cast_app_availability_tracker.h" + +#include "util/logging.h" + +namespace openscreen { +namespace cast { + +CastAppAvailabilityTracker::CastAppAvailabilityTracker() = default; +CastAppAvailabilityTracker::~CastAppAvailabilityTracker() = default; + +std::vector CastAppAvailabilityTracker::RegisterSource( + const CastMediaSource& source) { + if (registered_sources_.find(source.source_id()) != + registered_sources_.end()) { + return {}; + } + + registered_sources_.emplace(source.source_id(), source); + + std::vector new_app_ids; + for (const std::string& app_id : source.app_ids()) { + if (++registration_count_by_app_id_[app_id] == 1) { + new_app_ids.push_back(app_id); + } + } + return new_app_ids; +} + +void CastAppAvailabilityTracker::UnregisterSource( + const CastMediaSource& source) { + UnregisterSource(source.source_id()); +} + +void CastAppAvailabilityTracker::UnregisterSource( + const std::string& source_id) { + auto it = registered_sources_.find(source_id); + if (it == registered_sources_.end()) { + return; + } + + for (const std::string& app_id : it->second.app_ids()) { + auto count_it = registration_count_by_app_id_.find(app_id); + OSP_DCHECK(count_it != registration_count_by_app_id_.end()); + if (--(count_it->second) == 0) { + registration_count_by_app_id_.erase(count_it); + } + } + + registered_sources_.erase(it); +} + +std::vector CastAppAvailabilityTracker::UpdateAppAvailability( + const std::string& device_id, + const std::string& app_id, + AppAvailability availability) { + auto& availabilities = app_availabilities_[device_id]; + auto it = availabilities.find(app_id); + + AppAvailabilityResult old_availability = it == availabilities.end() + ? AppAvailabilityResult::kUnknown + : it->second.availability; + AppAvailabilityResult new_availability = availability.availability; + + // Updated if status changes from/to kAvailable. + bool updated = (old_availability == AppAvailabilityResult::kAvailable || + new_availability == AppAvailabilityResult::kAvailable) && + old_availability != new_availability; + availabilities[app_id] = availability; + + if (!updated) { + return {}; + } + + std::vector affected_sources; + for (const auto& source : registered_sources_) { + if (source.second.ContainsAppId(app_id)) { + affected_sources.push_back(source.second); + } + } + return affected_sources; +} + +std::vector CastAppAvailabilityTracker::RemoveResultsForDevice( + const std::string& device_id) { + auto affected_sources = GetSupportedSources(device_id); + app_availabilities_.erase(device_id); + return affected_sources; +} + +std::vector CastAppAvailabilityTracker::GetSupportedSources( + const std::string& device_id) const { + auto it = app_availabilities_.find(device_id); + if (it == app_availabilities_.end()) { + return std::vector(); + } + + // Find all app IDs that are available on the device. + std::vector supported_app_ids; + for (const auto& availability : it->second) { + if (availability.second.availability == AppAvailabilityResult::kAvailable) { + supported_app_ids.push_back(availability.first); + } + } + + // Find all registered sources whose query results contain the device ID. + std::vector sources; + for (const auto& source : registered_sources_) { + if (source.second.ContainsAnyAppIdFrom(supported_app_ids)) { + sources.push_back(source.second); + } + } + return sources; +} + +CastAppAvailabilityTracker::AppAvailability +CastAppAvailabilityTracker::GetAvailability(const std::string& device_id, + const std::string& app_id) const { + auto availabilities_it = app_availabilities_.find(device_id); + if (availabilities_it == app_availabilities_.end()) { + return {AppAvailabilityResult::kUnknown, Clock::time_point{}}; + } + + const auto& availability_map = availabilities_it->second; + auto availability_it = availability_map.find(app_id); + if (availability_it == availability_map.end()) { + return {AppAvailabilityResult::kUnknown, Clock::time_point{}}; + } + + return availability_it->second; +} + +std::vector CastAppAvailabilityTracker::GetRegisteredApps() const { + std::vector registered_apps; + for (const auto& app_ids_and_count : registration_count_by_app_id_) { + registered_apps.push_back(app_ids_and_count.first); + } + + return registered_apps; +} + +std::vector CastAppAvailabilityTracker::GetAvailableDevices( + const CastMediaSource& source) const { + std::vector device_ids; + // For each device, check if there is at least one available app in |source|. + for (const auto& availabilities : app_availabilities_) { + for (const std::string& app_id : source.app_ids()) { + const auto& availabilities_map = availabilities.second; + auto availability_it = availabilities_map.find(app_id); + if (availability_it != availabilities_map.end() && + availability_it->second.availability == + AppAvailabilityResult::kAvailable) { + device_ids.push_back(availabilities.first); + break; + } + } + } + return device_ids; +} + +} // namespace cast +} // namespace openscreen diff --git a/cast/sender/cast_app_availability_tracker.h b/cast/sender/cast_app_availability_tracker.h new file mode 100644 index 00000000..bced8a50 --- /dev/null +++ b/cast/sender/cast_app_availability_tracker.h @@ -0,0 +1,124 @@ +// Copyright 2020 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. + +#ifndef CAST_SENDER_CAST_APP_AVAILABILITY_TRACKER_H_ +#define CAST_SENDER_CAST_APP_AVAILABILITY_TRACKER_H_ + +#include +#include +#include + +#include "cast/sender/channel/message_util.h" +#include "cast/sender/public/cast_media_source.h" +#include "platform/api/time.h" + +namespace openscreen { +namespace cast { + +// Tracks device queries and their extracted Cast app IDs and their +// availabilities on discovered devices. +// Example usage: +/// +// (1) A page is interested in a Cast URL (e.g. by creating a +// PresentationRequest with the URL) like "cast:foo". To register the source to +// be tracked: +// CastAppAvailabilityTracker tracker; +// auto source = CastMediaSource::From("cast:foo"); +// auto new_app_ids = tracker.RegisterSource(source.value()); +// +// (2) The set of app IDs returned by the tracker can then be used by the caller +// to send an app availability request to each of the discovered devices. +// +// (3) Once the caller knows the availability value for a (device, app) pair, it +// may inform the tracker to update its results: +// auto affected_sources = +// tracker.UpdateAppAvailability(device_id, app_id, {availability, now}); +// +// (4) The tracker returns a subset of discovered sources that were affected by +// the update. The caller can then call |GetAvailableDevices()| to get the +// updated results for each affected source. +// +// (5a): At any time, the caller may call |RemoveResultsForDevice()| to remove +// cached results pertaining to the device, when it detects that a device is +// removed or no longer valid. +// +// (5b): At any time, the caller may call |GetAvailableDevices()| (even before +// the source is registered) to determine if there are cached results available. +class CastAppAvailabilityTracker { + public: + // The result of an app availability request and the time when it is obtained. + struct AppAvailability { + AppAvailabilityResult availability; + Clock::time_point time; + }; + + CastAppAvailabilityTracker(); + ~CastAppAvailabilityTracker(); + + CastAppAvailabilityTracker(const CastAppAvailabilityTracker&) = delete; + CastAppAvailabilityTracker& operator=(const CastAppAvailabilityTracker&) = + delete; + + // Registers |source| with the tracker. Returns a list of new app IDs that + // were previously not known to the tracker. + std::vector RegisterSource(const CastMediaSource& source); + + // Unregisters the source given by |source| or |source_id| with the tracker. + void UnregisterSource(const std::string& source_id); + void UnregisterSource(const CastMediaSource& source); + + // Updates the availability of |app_id| on |device_id| to |availability|. + // Returns a list of registered CastMediaSources for which the set of + // available devices might have been updated by this call. The caller should + // call |GetAvailableDevices| with the returned CastMediaSources to get the + // updated lists. + std::vector UpdateAppAvailability( + const std::string& device_id, + const std::string& app_id, + AppAvailability availability); + + // Removes all results associated with |device_id|, i.e. when the device + // becomes invalid. Returns a list of registered CastMediaSources for which + // the set of available devices might have been updated by this call. The + // caller should call |GetAvailableDevices| with the returned CastMediaSources + // to get the updated lists. + std::vector RemoveResultsForDevice( + const std::string& device_id); + + // Returns a list of registered CastMediaSources supported by |device_id|. + std::vector GetSupportedSources( + const std::string& device_id) const; + + // Returns the availability for |app_id| on |device_id| and the time at which + // the availability was determined. If availability is kUnknown, then the time + // may be null (e.g. if an availability request was never sent). + AppAvailability GetAvailability(const std::string& device_id, + const std::string& app_id) const; + + // Returns a list of registered app IDs. + std::vector GetRegisteredApps() const; + + // Returns a list of device IDs compatible with |source|, using the current + // availability info. + std::vector GetAvailableDevices( + const CastMediaSource& source) const; + + private: + // App ID to availability. + using AppAvailabilityMap = std::map; + + // Registered sources and corresponding CastMediaSources. + std::map registered_sources_; + + // App IDs tracked and the number of registered sources containing them. + std::map registration_count_by_app_id_; + + // IDs and app availabilities of known devices. + std::map app_availabilities_; +}; + +} // namespace cast +} // namespace openscreen + +#endif // CAST_SENDER_CAST_APP_AVAILABILITY_TRACKER_H_ diff --git a/cast/sender/cast_app_availability_tracker_unittest.cc b/cast/sender/cast_app_availability_tracker_unittest.cc new file mode 100644 index 00000000..b45d3563 --- /dev/null +++ b/cast/sender/cast_app_availability_tracker_unittest.cc @@ -0,0 +1,159 @@ +// Copyright 2020 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 "cast/sender/cast_app_availability_tracker.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "platform/test/fake_clock.h" + +namespace openscreen { +namespace cast { +namespace { + +using ::testing::UnorderedElementsAreArray; + +MATCHER_P(CastMediaSourcesEqual, expected, "") { + if (expected.size() != arg.size()) + return false; + return std::equal( + expected.begin(), expected.end(), arg.begin(), + [](const CastMediaSource& source1, const CastMediaSource& source2) { + return source1.source_id() == source2.source_id(); + }); +} + +} // namespace + +class CastAppAvailabilityTrackerTest : public ::testing::Test { + public: + CastAppAvailabilityTrackerTest() : clock_(Clock::now()) {} + ~CastAppAvailabilityTrackerTest() override = default; + + Clock::time_point Now() const { return clock_.now(); } + + protected: + FakeClock clock_; + CastAppAvailabilityTracker tracker_; +}; + +TEST_F(CastAppAvailabilityTrackerTest, RegisterSource) { + CastMediaSource source1("cast:AAA?clientId=1", {"AAA"}); + CastMediaSource source2("cast:AAA?clientId=2", {"AAA"}); + + std::vector expected_app_ids = {"AAA"}; + EXPECT_EQ(expected_app_ids, tracker_.RegisterSource(source1)); + + EXPECT_EQ(std::vector{}, tracker_.RegisterSource(source1)); + EXPECT_EQ(std::vector{}, tracker_.RegisterSource(source2)); + + tracker_.UnregisterSource(source1); + tracker_.UnregisterSource(source2); + + EXPECT_EQ(expected_app_ids, tracker_.RegisterSource(source1)); + EXPECT_EQ(expected_app_ids, tracker_.GetRegisteredApps()); +} + +TEST_F(CastAppAvailabilityTrackerTest, RegisterSourceReturnsMultipleAppIds) { + CastMediaSource source1("urn:x-org.chromium.media:source:tab:1", + {"0F5096E8", "85CDB22F"}); + + // Mirorring app ids. + std::vector expected_app_ids = {"0F5096E8", "85CDB22F"}; + EXPECT_THAT(tracker_.RegisterSource(source1), + UnorderedElementsAreArray(expected_app_ids)); + EXPECT_THAT(tracker_.GetRegisteredApps(), + UnorderedElementsAreArray(expected_app_ids)); +} + +TEST_F(CastAppAvailabilityTrackerTest, MultipleAppIdsAlreadyTrackingOne) { + // One of the mirroring app IDs. + CastMediaSource source1("cast:0F5096E8?clientId=123", {"0F5096E8"}); + + std::vector new_app_ids = {"0F5096E8"}; + std::vector registered_app_ids = {"0F5096E8"}; + EXPECT_EQ(new_app_ids, tracker_.RegisterSource(source1)); + EXPECT_EQ(registered_app_ids, tracker_.GetRegisteredApps()); + + CastMediaSource source2("urn:x-org.chromium.media:source:tab:1", + {"0F5096E8", "85CDB22F"}); + + new_app_ids = {"85CDB22F"}; + registered_app_ids = {"0F5096E8", "85CDB22F"}; + + EXPECT_EQ(new_app_ids, tracker_.RegisterSource(source2)); + EXPECT_THAT(tracker_.GetRegisteredApps(), + UnorderedElementsAreArray(registered_app_ids)); +} + +TEST_F(CastAppAvailabilityTrackerTest, UpdateAppAvailability) { + CastMediaSource source1("cast:AAA?clientId=1", {"AAA"}); + CastMediaSource source2("cast:AAA?clientId=2", {"AAA"}); + CastMediaSource source3("cast:BBB?clientId=3", {"BBB"}); + + tracker_.RegisterSource(source3); + + // |source3| not affected. + EXPECT_THAT( + tracker_.UpdateAppAvailability( + "deviceId1", "AAA", {AppAvailabilityResult::kAvailable, Now()}), + CastMediaSourcesEqual(std::vector())); + + std::vector devices_1 = {"deviceId1"}; + std::vector devices_1_2 = {"deviceId1", "deviceId2"}; + std::vector sources_1 = {source1}; + std::vector sources_1_2 = {source1, source2}; + + // Tracker returns available devices even though sources aren't registered. + EXPECT_EQ(devices_1, tracker_.GetAvailableDevices(source1)); + EXPECT_EQ(devices_1, tracker_.GetAvailableDevices(source2)); + EXPECT_TRUE(tracker_.GetAvailableDevices(source3).empty()); + + tracker_.RegisterSource(source1); + // Only |source1| is registered for this app. + EXPECT_THAT( + tracker_.UpdateAppAvailability( + "deviceId2", "AAA", {AppAvailabilityResult::kAvailable, Now()}), + CastMediaSourcesEqual(sources_1)); + EXPECT_THAT(tracker_.GetAvailableDevices(source1), + UnorderedElementsAreArray(devices_1_2)); + EXPECT_THAT(tracker_.GetAvailableDevices(source2), + UnorderedElementsAreArray(devices_1_2)); + EXPECT_TRUE(tracker_.GetAvailableDevices(source3).empty()); + + tracker_.RegisterSource(source2); + EXPECT_THAT( + tracker_.UpdateAppAvailability( + "deviceId2", "AAA", {AppAvailabilityResult::kUnavailable, Now()}), + CastMediaSourcesEqual(sources_1_2)); + EXPECT_EQ(devices_1, tracker_.GetAvailableDevices(source1)); + EXPECT_EQ(devices_1, tracker_.GetAvailableDevices(source2)); + EXPECT_TRUE(tracker_.GetAvailableDevices(source3).empty()); +} + +TEST_F(CastAppAvailabilityTrackerTest, RemoveResultsForDevice) { + CastMediaSource source1("cast:AAA?clientId=1", {"AAA"}); + + tracker_.UpdateAppAvailability("deviceId1", "AAA", + {AppAvailabilityResult::kAvailable, Now()}); + EXPECT_EQ(AppAvailabilityResult::kAvailable, + tracker_.GetAvailability("deviceId1", "AAA").availability); + + std::vector expected_device_ids = {"deviceId1"}; + EXPECT_EQ(expected_device_ids, tracker_.GetAvailableDevices(source1)); + + // Unrelated device ID. + tracker_.RemoveResultsForDevice("deviceId2"); + EXPECT_EQ(AppAvailabilityResult::kAvailable, + tracker_.GetAvailability("deviceId1", "AAA").availability); + EXPECT_EQ(expected_device_ids, tracker_.GetAvailableDevices(source1)); + + tracker_.RemoveResultsForDevice("deviceId1"); + EXPECT_EQ(AppAvailabilityResult::kUnknown, + tracker_.GetAvailability("deviceId1", "AAA").availability); + EXPECT_EQ(std::vector{}, tracker_.GetAvailableDevices(source1)); +} + +} // namespace cast +} // namespace openscreen diff --git a/cast/sender/public/DEPS b/cast/sender/public/DEPS index 44de6584..3cfae918 100644 --- a/cast/sender/public/DEPS +++ b/cast/sender/public/DEPS @@ -1,12 +1,7 @@ # -*- Mode: Python; -*- include_rules = [ - # By default, openscreen implementation libraries should not be exposed - # through public APIs. - '-base', - '-platform', - # Dependencies on the implementation are not allowed in public/. '-cast/sender', - '+cast/sender/public' + '+cast/sender/public', ] diff --git a/cast/sender/public/README.md b/cast/sender/public/README.md index ace86163..b670d110 100644 --- a/cast/sender/public/README.md +++ b/cast/sender/public/README.md @@ -1,4 +1,4 @@ -# cast/receiver/public +# cast/sender/public This module contains an implementation of the Cast "sender", i.e. the client that discovers Cast devices on the LAN and launches apps on them. diff --git a/cast/sender/public/cast_media_source.cc b/cast/sender/public/cast_media_source.cc new file mode 100644 index 00000000..ebc9a3fe --- /dev/null +++ b/cast/sender/public/cast_media_source.cc @@ -0,0 +1,45 @@ +// Copyright 2020 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 "cast/sender/public/cast_media_source.h" + +#include + +#include "util/logging.h" + +namespace openscreen { +namespace cast { + +// static +ErrorOr CastMediaSource::From(const std::string& source) { + // TODO(btolsch): Implement when we have URL parsing. + OSP_UNIMPLEMENTED(); + return Error::Code::kUnknownError; +} + +CastMediaSource::CastMediaSource(std::string source, + std::vector app_ids) + : source_id_(std::move(source)), app_ids_(std::move(app_ids)) {} + +CastMediaSource::CastMediaSource(const CastMediaSource& other) = default; +CastMediaSource::CastMediaSource(CastMediaSource&& other) = default; + +CastMediaSource::~CastMediaSource() = default; + +CastMediaSource& CastMediaSource::operator=(const CastMediaSource& other) = + default; +CastMediaSource& CastMediaSource::operator=(CastMediaSource&& other) = default; + +bool CastMediaSource::ContainsAppId(const std::string& app_id) const { + return std::find(app_ids_.begin(), app_ids_.end(), app_id) != app_ids_.end(); +} + +bool CastMediaSource::ContainsAnyAppIdFrom( + const std::vector& app_ids) const { + return std::find_first_of(app_ids_.begin(), app_ids_.end(), app_ids.begin(), + app_ids.end()) != app_ids_.end(); +} + +} // namespace cast +} // namespace openscreen diff --git a/cast/sender/public/cast_media_source.h b/cast/sender/public/cast_media_source.h new file mode 100644 index 00000000..18af80f8 --- /dev/null +++ b/cast/sender/public/cast_media_source.h @@ -0,0 +1,42 @@ +// Copyright 2020 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. + +#ifndef CAST_SENDER_PUBLIC_CAST_MEDIA_SOURCE_H_ +#define CAST_SENDER_PUBLIC_CAST_MEDIA_SOURCE_H_ + +#include +#include + +#include "platform/base/error.h" + +namespace openscreen { +namespace cast { + +class CastMediaSource { + public: + static ErrorOr From(const std::string& source); + + CastMediaSource(std::string source, std::vector app_ids); + CastMediaSource(const CastMediaSource& other); + CastMediaSource(CastMediaSource&& other); + ~CastMediaSource(); + + CastMediaSource& operator=(const CastMediaSource& other); + CastMediaSource& operator=(CastMediaSource&& other); + + bool ContainsAppId(const std::string& app_id) const; + bool ContainsAnyAppIdFrom(const std::vector& app_ids) const; + + const std::string& source_id() const { return source_id_; } + const std::vector& app_ids() const { return app_ids_; } + + private: + std::string source_id_; + std::vector app_ids_; +}; + +} // namespace cast +} // namespace openscreen + +#endif // CAST_SENDER_PUBLIC_CAST_MEDIA_SOURCE_H_ -- cgit v1.2.3