blob: eea5e654a05eb456fa7293e183081c8d80993a6b [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/media_router/common/media_source.h"
#include <array>
#include <cstdio>
#include <ostream>
#include <string>
#include <string_view>
#include "base/ranges/algorithm.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "media/audio/audio_features.h"
#include "media/base/audio_codecs.h"
#include "media/base/media_switches.h"
#include "media/base/video_codecs.h"
#include "net/base/url_util.h"
#include "third_party/blink/public/platform/modules/remoteplayback/remote_playback_source.h"
#include "url/gurl.h"
namespace media_router {
namespace {
// Prefixes used to format and detect various protocols' media source URNs.
// See: https://www.ietf.org/rfc/rfc3406.txt
constexpr char kAnyTabMediaUrn[] = "urn:x-org.chromium.media:source:tab:*";
constexpr char kTabMediaUrnFormat[] = "urn:x-org.chromium.media:source:tab:%d";
constexpr std::string_view kDesktopMediaUrnPrefix =
"urn:x-org.chromium.media:source:desktop:";
// WARNING: If more desktop URN parameters are added in the future, the parsing
// code will have to be smarter!
constexpr std::string_view kDesktopMediaUrnAudioParam = "?with_audio=true";
constexpr std::string_view kUnchosenDesktopMediaUrn =
"urn:x-org.chromium.media:source:desktop";
constexpr std::string_view kUnchosenDesktopWithAudioMediaUrn =
"urn:x-org.chromium.media:source:desktop:?with_audio=true";
// List of non-http(s) schemes that are allowed in a Presentation URL.
constexpr std::array<const char* const, 5> kAllowedSchemes{
{kCastPresentationUrlScheme, kCastDialPresentationUrlScheme,
kDialPresentationUrlScheme, blink::kRemotePlaybackPresentationUrlScheme,
"test"}};
bool IsSchemeAllowed(const GURL& url) {
return url.SchemeIsHTTPOrHTTPS() ||
base::ranges::any_of(
kAllowedSchemes,
[&url](const char* const scheme) { return url.SchemeIs(scheme); });
}
bool IsSystemAudioCaptureSupported() {
if (!media::IsSystemLoopbackCaptureSupported()) {
return false;
}
#if BUILDFLAG(IS_LINUX)
return base::FeatureList::IsEnabled(media::kPulseaudioLoopbackForCast);
#else
return true;
#endif // BUILDFLAG(IS_LINUX)
}
} // namespace
bool IsLegacyCastPresentationUrl(const GURL& url) {
return base::StartsWith(url.spec(), kLegacyCastPresentationUrlPrefix,
base::CompareCase::INSENSITIVE_ASCII);
}
bool IsValidPresentationUrl(const GURL& url) {
return url.is_valid() && IsSchemeAllowed(url);
}
bool IsValidStandardPresentationSource(const std::string& media_source) {
const GURL source_url(media_source);
return source_url.is_valid() && source_url.SchemeIsHTTPOrHTTPS() &&
!base::StartsWith(source_url.spec(), kLegacyCastPresentationUrlPrefix,
base::CompareCase::INSENSITIVE_ASCII);
}
bool IsAutoJoinPresentationId(const std::string& presentation_id) {
return presentation_id == kAutoJoinPresentationId;
}
MediaSource::MediaSource(const MediaSource::Id& source_id) : id_(source_id) {
GURL url(source_id);
if (IsValidPresentationUrl(url))
url_ = url;
}
MediaSource::MediaSource(const GURL& presentation_url)
: id_(presentation_url.spec()), url_(presentation_url) {}
MediaSource::~MediaSource() = default;
// static
MediaSource MediaSource::ForAnyTab() {
return MediaSource(std::string(kAnyTabMediaUrn));
}
// static
MediaSource MediaSource::ForTab(int tab_id) {
// Ideally we shouldn't allow -1 as a tab ID, but in unit tests, a tab ID of
// -1 can show up when this function is called from
// CastHandler::StartObservingForSinks() because SessionTabHelper::IdForTab
// can return -1.
DCHECK_GE(tab_id, -1);
return MediaSource(base::StringPrintf(kTabMediaUrnFormat, tab_id));
}
// static
MediaSource MediaSource::ForPresentationUrl(const GURL& presentation_url) {
return MediaSource(presentation_url);
}
// static
MediaSource MediaSource::ForRemotePlayback(int tab_id,
media::VideoCodec video_codec,
media::AudioCodec audio_codec) {
return MediaSource(
base::StringPrintf(blink::kRemotePlaybackDesktopUrlFormat,
media::GetCodecName(video_codec).c_str(),
media::GetCodecName(audio_codec).c_str(), tab_id));
}
// static
MediaSource MediaSource::ForDesktop(const std::string& desktop_media_id,
bool with_audio) {
DCHECK(!desktop_media_id.empty());
std::string id = std::string(kDesktopMediaUrnPrefix) + desktop_media_id;
if (with_audio) {
id += std::string(kDesktopMediaUrnAudioParam);
}
return MediaSource(id);
}
// static
MediaSource MediaSource::ForUnchosenDesktop() {
return IsSystemAudioCaptureSupported() &&
base::FeatureList::IsEnabled(
media::kCastLoopbackAudioToAudioReceivers)
? MediaSource(std::string(kUnchosenDesktopWithAudioMediaUrn))
: MediaSource(std::string(kUnchosenDesktopMediaUrn));
}
bool MediaSource::IsTabMirroringSource() const {
return id() == kAnyTabMediaUrn || TabId().has_value();
}
bool MediaSource::IsDesktopMirroringSource() const {
return id() == kUnchosenDesktopMediaUrn ||
id() == kUnchosenDesktopWithAudioMediaUrn ||
base::StartsWith(id(), kDesktopMediaUrnPrefix,
base::CompareCase::SENSITIVE);
}
bool MediaSource::IsCastPresentationUrl() const {
return url_.SchemeIs(kCastPresentationUrlScheme) ||
IsLegacyCastPresentationUrl(url_);
}
bool MediaSource::IsRemotePlaybackSource() const {
return url_.SchemeIs(kRemotePlaybackPresentationUrlScheme);
}
std::optional<int> MediaSource::TabId() const {
int tab_id;
if (sscanf(id_.c_str(), kTabMediaUrnFormat, &tab_id) != 1) {
return std::nullopt;
}
return tab_id;
}
std::optional<int> MediaSource::TabIdFromRemotePlaybackSource() const {
if (!IsRemotePlaybackSource()) {
return std::nullopt;
}
std::string tab_id_str;
if (!net::GetValueForKeyInQuery(url(), "tab_id", &tab_id_str)) {
return std::nullopt;
}
int tab_id;
if (!base::StringToInt(tab_id_str, &tab_id)) {
return std::nullopt;
}
return tab_id;
}
std::optional<std::string> MediaSource::DesktopStreamId() const {
if (base::StartsWith(id_, kDesktopMediaUrnPrefix,
base::CompareCase::SENSITIVE)) {
const auto begin = id_.begin() + kDesktopMediaUrnPrefix.size();
auto end = id_.end();
if (base::EndsWith(id_, kDesktopMediaUrnAudioParam,
base::CompareCase::SENSITIVE)) {
end -= kDesktopMediaUrnAudioParam.size();
}
return std::string(begin, end);
}
return std::nullopt;
}
bool MediaSource::IsDesktopSourceWithAudio() const {
return base::StartsWith(id_, kDesktopMediaUrnPrefix,
base::CompareCase::SENSITIVE) &&
base::EndsWith(id_, kDesktopMediaUrnAudioParam,
base::CompareCase::SENSITIVE);
}
bool MediaSource::IsDialSource() const {
return url_.SchemeIs(kCastDialPresentationUrlScheme);
}
std::string MediaSource::AppNameFromDialSource() const {
return IsDialSource() ? url_.path() : "";
}
std::string MediaSource::TruncateForLogging(size_t max_length) const {
const std::string origin = url_.DeprecatedGetOriginAsURL().spec();
if (!origin.empty()) {
return origin.substr(0, max_length);
}
// TODO(takumif): Keep the query string by redacting PII. The query string
// may contain info useful for debugging such as the required capabilities.
const size_t query_start_index = id_.find("?");
const size_t length =
query_start_index == std::string::npos ? max_length : query_start_index;
return id_.substr(0, length);
}
void MediaSource::AppendTabIdToRemotePlaybackUrlQuery(int tab_id) {
if (url_.is_empty()) {
return;
}
GURL::Replacements replacements;
std::string tab_id_query = base::StringPrintf("tab_id=%d", tab_id);
std::string new_query =
(url_.has_query() ? url_.query() + "&" : "") + tab_id_query;
replacements.SetQueryStr(new_query);
url_ = url_.ReplaceComponents(replacements);
id_ = url_.spec();
}
} // namespace media_router