| // 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 { |
| const char kLicensesRelativePath[] = "/licenses/licenses_cobalt.txt"; |
| } // namespace |
| |
| namespace cobalt { |
| namespace dom { |
| |
| Navigator::Navigator( |
| script::EnvironmentSettings* settings, const std::string& user_agent, |
| const std::string& language, scoped_refptr<MediaSession> media_session, |
| 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_session_(media_session), |
| 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() |
| const { |
| return media_session_; |
| } |
| |
| namespace { |
| |
| // See |
| // https://www.w3.org/TR/encrypted-media/#get-supported-capabilities-for-audio-video-type. |
| base::Optional<script::Sequence<MediaKeySystemMediaCapability>> |
| TryGetSupportedCapabilities( |
| const std::string& key_system, |
| const script::Sequence<MediaKeySystemMediaCapability>& |
| requested_media_capabilities, |
| const media::CanPlayTypeHandler* can_play_type_handler) { |
| DCHECK(can_play_type_handler); |
| // 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 [...]: |
| const bool kIsProgressive = false; |
| if (can_play_type_handler->CanPlayType( |
| content_type.c_str(), key_system.c_str(), kIsProgressive) == |
| kSbMediaSupportTypeProbably) { |
| LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type |
| << ", " << key_system << ") -> supported"; |
| // 3.13.1. Add requested media capability to supported media capabilities. |
| supported_media_capabilities.push_back(requested_media_capability); |
| } else { |
| LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type |
| << ", " << key_system << ") -> not supported"; |
| } |
| } |
| // 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> TryGetSupportedConfiguration( |
| const std::string& key_system, |
| const eme::MediaKeySystemConfiguration& candidate_configuration, |
| const media::CanPlayTypeHandler* can_play_type_handler) { |
| // 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( |
| key_system, candidate_configuration.video_capabilities(), |
| can_play_type_handler); |
| // 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( |
| key_system, candidate_configuration.audio_capabilities(), |
| can_play_type_handler); |
| // 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; |
| } |
| |
| } // namespace |
| |
| // 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>>(); |
| |
| // 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( |
| key_system, supported_configurations.at(configuration_index), |
| dom_settings->can_play_type_handler()); |
| 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_)); |
| // 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_); |
| } |
| |
| } // namespace dom |
| } // namespace cobalt |