blob: 8b8d5f6466a049551a8b8aa3f7d46ff96ea33e62 [file] [log] [blame]
// Copyright 2015 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/dom/navigator.h"
#include <memory>
#include <vector>
#include "base/optional.h"
#include "cobalt/dom/captions/system_caption_settings.h"
#include "cobalt/dom/dom_exception.h"
#include "cobalt/dom/dom_settings.h"
#include "cobalt/dom/eme/media_key_system_access.h"
#include "cobalt/media_capture/media_devices.h"
#include "cobalt/media_session/media_session_client.h"
#include "cobalt/script/script_value_factory.h"
#include "starboard/configuration_constants.h"
#include "starboard/file.h"
#include "starboard/media.h"
using cobalt::media_session::MediaSession;
namespace cobalt {
namespace dom {
namespace {
const char kLicensesRelativePath[] = "/licenses/licenses_cobalt.txt";
#if !defined(COBALT_BUILD_TYPE_GOLD)
std::string ToString(const std::string& str, int indent_level);
std::string ToString(const eme::MediaKeySystemMediaCapability& capability,
int indent_level);
std::string ToString(const eme::MediaKeySystemConfiguration& configuration,
int indent_level);
std::string GetIndent(int indent_level) {
std::string indent;
while (indent_level > 0) {
indent += " ";
--indent_level;
}
return indent;
}
template <typename T>
std::string ToString(const script::Sequence<T>& sequence, int indent_level) {
std::stringstream ss;
ss << "{\n";
for (auto iter = sequence.begin(); iter != sequence.end(); ++iter) {
ss << ToString(*iter, indent_level + 1) << ",\n";
}
ss << GetIndent(indent_level) << "}";
return ss.str();
}
std::string ToString(const std::string& str, int indent_level) {
return GetIndent(indent_level) + str;
}
std::string ToString(const eme::MediaKeySystemMediaCapability& capability,
int indent_level) {
return GetIndent(indent_level) + '\'' + capability.content_type() + "'/'" +
capability.encryption_scheme().value_or("(null)") + '\'';
}
std::string ToString(const eme::MediaKeySystemConfiguration& configuration,
int indent_level) {
DCHECK(configuration.has_label());
std::stringstream ss;
ss << GetIndent(indent_level) << "label:'" << configuration.label()
<< "': {\n";
if (configuration.has_init_data_types()) {
ss << GetIndent(indent_level + 1) << "init_data_types: "
<< ToString(configuration.init_data_types(), indent_level + 1) << ",\n";
}
if (configuration.has_audio_capabilities()) {
ss << GetIndent(indent_level + 1) << "audio_capabilities: "
<< ToString(configuration.audio_capabilities(), indent_level + 1)
<< ",\n";
}
if (configuration.has_video_capabilities()) {
ss << GetIndent(indent_level + 1) << "video_capabilities: "
<< ToString(configuration.video_capabilities(), indent_level + 1)
<< ",\n";
}
ss << GetIndent(indent_level) << "}";
return ss.str();
}
#endif // !defined(COBALT_BUILD_TYPE_GOLD)
// This function is used when the underlying SbMediaCanPlayMimeAndKeySystem()
// implementation doesn't support extra attributes on |key_system|, it makes
// decision based on the key system itself.
bool IsEncryptionSchemeSupportedByDefault(
const std::string& key_system, const std::string& encryption_scheme) {
// 1. Playready only supports "cenc".
if (key_system.find("playready") != key_system.npos) {
return encryption_scheme == "cenc";
}
// 2. Fairplay only supports "cbcs" and "cbcs-1-9".
if (key_system.find("fairplay") != key_system.npos) {
return encryption_scheme == "cbcs" || encryption_scheme == "cbcs-1-9";
}
// 3. Widevine only supports "cenc", "cbcs" and "cbcs-1-9".
if (key_system.find("widevine") != key_system.npos) {
return encryption_scheme == "cenc" || encryption_scheme == "cbcs" ||
encryption_scheme == "cbcs-1-9";
}
// The key system is unknown, assume only "cenc" is supported.
return encryption_scheme == "cenc";
}
bool CanPlay(const media::CanPlayTypeHandler& can_play_type_handler,
const std::string& content_type, const std::string& key_system) {
auto can_play_result = can_play_type_handler.CanPlayAdaptive(
content_type.c_str(), key_system.c_str());
LOG_IF(INFO, can_play_result == kSbMediaSupportTypeMaybe)
<< "CanPlayAdaptive() returns \"maybe\".";
return can_play_result == kSbMediaSupportTypeProbably ||
can_play_result == kSbMediaSupportTypeMaybe;
}
} // namespace
Navigator::Navigator(
script::EnvironmentSettings* settings, const std::string& user_agent,
const std::string& language,
scoped_refptr<cobalt::dom::captions::SystemCaptionSettings> captions,
script::ScriptValueFactory* script_value_factory)
: user_agent_(user_agent),
language_(language),
mime_types_(new MimeTypeArray()),
plugins_(new PluginArray()),
media_devices_(
new media_capture::MediaDevices(settings, script_value_factory)),
system_caption_settings_(captions),
script_value_factory_(script_value_factory) {}
const std::string& Navigator::language() const { return language_; }
base::Optional<std::string> GetFilenameForLicenses() {
const size_t kBufferSize = kSbFileMaxPath + 1;
std::vector<char> buffer(kBufferSize, 0);
bool got_path = SbSystemGetPath(kSbSystemPathContentDirectory, buffer.data(),
static_cast<int>(kBufferSize));
if (!got_path) {
SB_DLOG(ERROR) << "Cannot get content path for licenses files.";
return base::Optional<std::string>();
}
return std::string(buffer.data()).append(kLicensesRelativePath);
}
const std::string Navigator::licenses() const {
base::Optional<std::string> filename = GetFilenameForLicenses();
if (!filename) {
return std::string();
}
SbFile file = SbFileOpen(filename->c_str(), kSbFileOpenOnly | kSbFileRead,
nullptr, nullptr);
if (file == kSbFileInvalid) {
SB_DLOG(WARNING) << "Cannot open licenses file: " << *filename;
return std::string();
}
SbFileInfo info;
bool success = SbFileGetInfo(file, &info);
if (!success) {
SB_DLOG(WARNING) << "Cannot get information for licenses file.";
SbFileClose(file);
return std::string();
}
// SbFileReadAll expects an int for the size argument. Assume that the file
// is smaller than 2^32.
int file_size = static_cast<int>(info.size);
std::unique_ptr<char[]> buffer(new char[file_size]);
SbFileReadAll(file, buffer.get(), file_size);
const std::string file_contents = std::string(buffer.get(), file_size);
SbFileClose(file);
return file_contents;
}
const std::string& Navigator::user_agent() const { return user_agent_; }
bool Navigator::java_enabled() const { return false; }
bool Navigator::cookie_enabled() const { return false; }
scoped_refptr<media_capture::MediaDevices> Navigator::media_devices() {
return media_devices_;
}
const scoped_refptr<MimeTypeArray>& Navigator::mime_types() const {
return mime_types_;
}
const scoped_refptr<PluginArray>& Navigator::plugins() const {
return plugins_;
}
const scoped_refptr<media_session::MediaSession>& Navigator::media_session() {
if (media_session_ == nullptr) {
media_session_ =
scoped_refptr<media_session::MediaSession>(new MediaSession());
if (media_player_factory_ != nullptr) {
media_session_->EnsureMediaSessionClient();
DCHECK(media_session_->media_session_client());
media_session_->media_session_client()
->SetMaybeFreezeCallback(maybe_freeze_callback_);
media_session_->media_session_client()
->SetMediaPlayerFactory(media_player_factory_);
}
}
return media_session_;
}
// TODO: Move the following two functions to the bottom of the file, in sync
// with the order of declaration.
// See
// https://www.w3.org/TR/encrypted-media/#get-supported-capabilities-for-audio-video-type.
base::Optional<script::Sequence<MediaKeySystemMediaCapability>>
Navigator::TryGetSupportedCapabilities(
const media::CanPlayTypeHandler& can_play_type_handler,
const std::string& key_system,
const script::Sequence<MediaKeySystemMediaCapability>&
requested_media_capabilities) {
// 2. Let supported media capabilities be an empty sequence of
// MediaKeySystemMediaCapability dictionaries.
script::Sequence<MediaKeySystemMediaCapability> supported_media_capabilities;
// 3. For each requested media capability in requested media capabilities:
for (std::size_t media_capability_index = 0;
media_capability_index < requested_media_capabilities.size();
++media_capability_index) {
const MediaKeySystemMediaCapability& requested_media_capability =
requested_media_capabilities.at(media_capability_index);
// 3.1. Let content type be requested media capability's contentType member.
const std::string& content_type = requested_media_capability.content_type();
// 3.3. If content type is the empty string, return null.
if (content_type.empty()) {
return base::nullopt;
}
// 3.13. If the user agent and [CDM] implementation definitely support
// playback of encrypted media data for the combination of container,
// media types [...]:
if (CanPlayWithCapability(can_play_type_handler, key_system,
requested_media_capability)) {
// 3.13.1. Add requested media capability to supported media capabilities.
supported_media_capabilities.push_back(requested_media_capability);
}
}
// 4. If supported media capabilities is empty, return null.
if (supported_media_capabilities.empty()) {
return base::nullopt;
}
// 5. Return supported media capabilities.
return supported_media_capabilities;
}
// Technically, a user agent is supposed to implement "3.1.1.1 Get Supported
// Configuration" which requests the user consent until it's given. But since
// Cobalt never interacts with the user directly, we'll assume that the consent
// is always given and go straight to "3.1.1.2 Get Supported Configuration and
// Consent". See
// https://www.w3.org/TR/encrypted-media/#get-supported-configuration-and-consent.
base::Optional<eme::MediaKeySystemConfiguration>
Navigator::TryGetSupportedConfiguration(
const media::CanPlayTypeHandler& can_play_type_handler,
const std::string& key_system,
const eme::MediaKeySystemConfiguration& candidate_configuration) {
// 1. Let accumulated configuration be a new MediaKeySystemConfiguration
// dictionary.
eme::MediaKeySystemConfiguration accumulated_configuration;
// 2. Set the label member of accumulated configuration to equal the label
// member of candidate configuration.
accumulated_configuration.set_label(candidate_configuration.label());
// For now, copy initialization data types into accumulated configuration.
//
// TODO: Implement step 3 after introducing a Starboard API for detecting
// supported initialization data type.
// TODO: Checking has_init_data_types() won't be needed once Cobalt supports
// default values for IDL sequences.
accumulated_configuration.set_init_data_types(
candidate_configuration.has_init_data_types()
? candidate_configuration.init_data_types()
: script::Sequence<std::string>());
// TODO: Reject distinctive identifiers, persistent state, and persistent
// sessions.
// 15. If the videoCapabilities and audioCapabilities members in candidate
// configuration are both empty, return NotSupported.
//
// TODO: Checking has_video_capabilities() and has_audio_capabilities() won't
// be needed once Cobalt supports default values for IDL sequences.
if ((!candidate_configuration.has_video_capabilities() ||
candidate_configuration.video_capabilities().empty()) &&
(!candidate_configuration.has_audio_capabilities() ||
candidate_configuration.audio_capabilities().empty())) {
return base::nullopt;
}
// 16. If the videoCapabilities member in candidate configuration is
// non-empty:
if (candidate_configuration.has_video_capabilities() &&
!candidate_configuration.video_capabilities().empty()) {
// 16.1. Let video capabilities be the result of executing the "Get
// Supported Capabilities for Audio/Video Type" algorithm.
base::Optional<script::Sequence<MediaKeySystemMediaCapability>>
maybe_video_capabilities = TryGetSupportedCapabilities(
can_play_type_handler, key_system,
candidate_configuration.video_capabilities());
// 16.2. If video capabilities is null, return NotSupported.
if (!maybe_video_capabilities) {
return base::nullopt;
}
// 16.3. Set the videoCapabilities member of accumulated configuration to
// video capabilities.
accumulated_configuration.set_video_capabilities(*maybe_video_capabilities);
} else {
// Otherwise: set the videoCapabilities member of accumulated configuration
// to an empty sequence.
accumulated_configuration.set_video_capabilities(
script::Sequence<MediaKeySystemMediaCapability>());
}
// 17. If the audioCapabilities member in candidate configuration is
// non-empty:
if (candidate_configuration.has_audio_capabilities() &&
!candidate_configuration.audio_capabilities().empty()) {
// 17.1. Let audio capabilities be the result of executing the "Get
// Supported Capabilities for Audio/Video Type" algorithm.
base::Optional<script::Sequence<MediaKeySystemMediaCapability>>
maybe_audio_capabilities = TryGetSupportedCapabilities(
can_play_type_handler, key_system,
candidate_configuration.audio_capabilities());
// 17.2. If audio capabilities is null, return NotSupported.
if (!maybe_audio_capabilities) {
return base::nullopt;
}
// 17.3. Set the audioCapabilities member of accumulated configuration to
// audio capabilities.
accumulated_configuration.set_audio_capabilities(*maybe_audio_capabilities);
} else {
// Otherwise: set the audioCapabilities member of accumulated configuration
// to an empty sequence.
accumulated_configuration.set_audio_capabilities(
script::Sequence<MediaKeySystemMediaCapability>());
}
// 23. Return accumulated configuration.
return accumulated_configuration;
}
// See
// https://www.w3.org/TR/encrypted-media/#dom-navigator-requestmediakeysystemaccess.
script::Handle<Navigator::InterfacePromise>
Navigator::RequestMediaKeySystemAccess(
script::EnvironmentSettings* settings, const std::string& key_system,
const script::Sequence<eme::MediaKeySystemConfiguration>&
supported_configurations) {
DCHECK(settings);
DOMSettings* dom_settings =
base::polymorphic_downcast<DOMSettings*>(settings);
DCHECK(dom_settings->can_play_type_handler());
script::Handle<InterfacePromise> promise =
script_value_factory_
->CreateInterfacePromise<scoped_refptr<eme::MediaKeySystemAccess>>();
#if !defined(COBALT_BUILD_TYPE_GOLD)
LOG(INFO) << "Navigator.RequestMediaKeySystemAccess() called with '"
<< key_system << "', and\n"
<< ToString(supported_configurations, 0);
#endif // !defined(COBALT_BUILD_TYPE_GOLD)
// 1. If |keySystem| is the empty string, return a promise rejected
// with a newly created TypeError.
// 2. If |supportedConfigurations| is empty, return a promise rejected
// with a newly created TypeError.
if (key_system.empty() || supported_configurations.empty()) {
promise->Reject(script::kTypeError);
return promise;
}
// 6.3. For each value in |supportedConfigurations|:
for (std::size_t configuration_index = 0;
configuration_index < supported_configurations.size();
++configuration_index) {
// 6.3.3. If supported configuration is not NotSupported:
base::Optional<eme::MediaKeySystemConfiguration>
maybe_supported_configuration = TryGetSupportedConfiguration(
*dom_settings->can_play_type_handler(), key_system,
supported_configurations.at(configuration_index));
if (maybe_supported_configuration) {
// 6.3.3.1. Let access be a new MediaKeySystemAccess object.
scoped_refptr<eme::MediaKeySystemAccess> media_key_system_access(
new eme::MediaKeySystemAccess(key_system,
*maybe_supported_configuration,
script_value_factory_));
#if !defined(COBALT_BUILD_TYPE_GOLD)
LOG(INFO) << "Navigator.RequestMediaKeySystemAccess() resolved with '"
<< media_key_system_access->key_system() << "', and\n"
<< ToString(media_key_system_access->GetConfiguration(), 0);
#endif // !defined(COBALT_BUILD_TYPE_GOLD)
// 6.3.3.2. Resolve promise.
promise->Resolve(media_key_system_access);
return promise;
}
}
// 6.4. Reject promise with a NotSupportedError.
promise->Reject(new DOMException(DOMException::kNotSupportedErr));
return promise;
}
const scoped_refptr<cobalt::dom::captions::SystemCaptionSettings>&
Navigator::system_caption_settings() const {
return system_caption_settings_;
}
void Navigator::TraceMembers(script::Tracer* tracer) {
tracer->Trace(mime_types_);
tracer->Trace(plugins_);
tracer->Trace(media_session_);
tracer->Trace(media_devices_);
tracer->Trace(system_caption_settings_);
}
bool Navigator::CanPlayWithCapability(
const media::CanPlayTypeHandler& can_play_type_handler,
const std::string& key_system,
const MediaKeySystemMediaCapability& media_capability) {
const std::string& content_type = media_capability.content_type();
// There is no encryption scheme specified, check directly.
if (!media_capability.encryption_scheme().has_value()) {
if (CanPlay(can_play_type_handler, content_type, key_system)) {
LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type
<< ", " << key_system << ") -> supported";
return true;
}
LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type
<< ", " << key_system << ") -> not supported";
return false;
}
if (!key_system_with_attributes_supported_.has_value()) {
if (!CanPlay(can_play_type_handler, content_type, key_system)) {
// If the check on the basic key system fails, we don't even bother to
// check if it supports key system with attributes.
LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type
<< ", " << key_system << ") -> not supported";
return false;
}
auto key_system_with_invalid_attribute =
std::string(key_system) + "; invalid_attributes=\"value\"";
// If an implementation supports attributes, it should ignore unknown
// attributes and return true, as the key system has been verified to be
// supported above.
key_system_with_attributes_supported_ = CanPlay(
can_play_type_handler, content_type, key_system_with_invalid_attribute);
if (key_system_with_attributes_supported_.value()) {
LOG(INFO) << "Navigator::RequestMediaKeySystemAccess() will use key"
<< " system with attributes.";
} else {
LOG(INFO) << "Navigator::RequestMediaKeySystemAccess() won't use key"
<< " system with attributes.";
}
}
DCHECK(key_system_with_attributes_supported_.has_value());
// As a key system with attributes support is optional, and the logic to
// determine whether the encryptionScheme is supported can be quite different
// depending on whether this is supported, we encapsulate the logic into two
// different functions.
if (key_system_with_attributes_supported_.value()) {
return CanPlayWithAttributes(can_play_type_handler, content_type,
key_system,
media_capability.encryption_scheme().value());
}
return CanPlayWithoutAttributes(can_play_type_handler, content_type,
key_system,
media_capability.encryption_scheme().value());
}
bool Navigator::CanPlayWithoutAttributes(
const media::CanPlayTypeHandler& can_play_type_handler,
const std::string& content_type, const std::string& key_system,
const std::string& encryption_scheme) {
DCHECK(key_system_with_attributes_supported_.has_value());
DCHECK(!key_system_with_attributes_supported_.value());
if (!IsEncryptionSchemeSupportedByDefault(key_system, encryption_scheme)) {
LOG(INFO) << "Navigator::RequestMediaKeySystemAccess() rejects "
<< key_system << " because encryptionScheme \""
<< encryption_scheme << "\" is not supported.";
return false;
}
if (CanPlay(can_play_type_handler, content_type, key_system)) {
LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type
<< ", " << key_system << ") with encryptionScheme \""
<< encryption_scheme << "\" -> supported";
return true;
}
LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type << ", "
<< key_system << ") with encryptionScheme \"" << encryption_scheme
<< "\" -> not supported";
return false;
}
bool Navigator::CanPlayWithAttributes(
const media::CanPlayTypeHandler& can_play_type_handler,
const std::string& content_type, const std::string& key_system,
const std::string& encryption_scheme) {
DCHECK(key_system_with_attributes_supported_.has_value());
DCHECK(key_system_with_attributes_supported_.value());
auto key_system_with_attributes =
key_system + "; encryptionscheme=\"" + encryption_scheme + '"';
if (CanPlay(can_play_type_handler, content_type,
key_system_with_attributes)) {
LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type
<< ", " << key_system << ") with encryptionScheme \""
<< encryption_scheme << "\" -> supported";
return true;
}
LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type << ", "
<< key_system << ") with encryptionScheme \"" << encryption_scheme
<< "\" -> not supported";
return false;
}
} // namespace dom
} // namespace cobalt