blob: f6cfb1eb1be037948b292fd0e7e39a3a854337dc [file] [log] [blame]
// Copyright 2017 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/media_capture/media_devices.h"
#include <memory>
#include <string>
#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/dom/dom_exception.h"
#include "cobalt/media_capture/media_device_info.h"
#include "cobalt/media_stream/media_stream.h"
#include "cobalt/media_stream/media_track_settings.h"
#include "cobalt/media_stream/microphone_audio_source.h"
#include "cobalt/speech/microphone.h"
#include "cobalt/speech/microphone_fake.h"
#include "cobalt/speech/microphone_starboard.h"
#include "starboard/common/string.h"
namespace cobalt {
namespace media_capture {
#if SB_USE_SB_MICROPHONE && !defined(DISABLE_MICROPHONE_IDL)
#define ENABLE_MICROPHONE_IDL
#endif
namespace {
using speech::Microphone;
std::unique_ptr<Microphone> CreateMicrophone(
const Microphone::Options& options) {
#if defined(ENABLE_FAKE_MICROPHONE)
if (options.enable_fake_microphone) {
return std::unique_ptr<speech::Microphone>(
new speech::MicrophoneFake(options));
}
#else
#endif // defined(ENABLE_FAKE_MICROPHONE)
std::unique_ptr<Microphone> mic;
#if defined(ENABLE_MICROPHONE_IDL)
mic.reset(new speech::MicrophoneStarboard(
speech::MicrophoneStarboard::kDefaultSampleRate,
/* Buffer for one second. */
speech::MicrophoneStarboard::kDefaultSampleRate *
speech::MicrophoneStarboard::kSbMicrophoneSampleSizeInBytes));
#endif // defined(ENABLE_MICROPHONE_IDL)
return mic;
}
} // namespace.
MediaDevices::MediaDevices(script::EnvironmentSettings* settings,
script::ScriptValueFactory* script_value_factory)
: dom::EventTarget(settings),
settings_(base::polymorphic_downcast<dom::DOMSettings*>(settings)),
script_value_factory_(script_value_factory),
javascript_message_loop_(base::MessageLoop::current()),
ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
weak_this_(weak_ptr_factory_.GetWeakPtr()) {}
script::Handle<MediaDevices::MediaInfoSequencePromise>
MediaDevices::EnumerateDevices() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(settings_);
DCHECK(script_value_factory_);
script::Handle<MediaInfoSequencePromise> promise =
script_value_factory_->CreateBasicPromise<MediaInfoSequence>();
script::Sequence<scoped_refptr<Wrappable>> output;
std::unique_ptr<speech::Microphone> microphone =
CreateMicrophone(settings_->microphone_options());
if (microphone && microphone->IsValid()) {
scoped_refptr<Wrappable> media_device(
new MediaDeviceInfo(kMediaDeviceKindAudioinput, microphone->Label()));
output.push_back(media_device);
}
promise->Resolve(output);
return promise;
}
script::Handle<MediaDevices::MediaStreamPromise> MediaDevices::GetUserMedia() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(script_value_factory_);
script::Handle<MediaDevices::MediaStreamPromise> promise =
script_value_factory_->CreateInterfacePromise<
script::ScriptValueFactory::WrappablePromise>();
// Per specification at
// https://w3c.github.io/mediacapture-main/#dom-mediadevices-getusermedia
//
// Step 3: If requestedMediaTypes is the empty set, return a promise rejected
// with a TypeError. The word "optional" occurs in the WebIDL due to WebIDL
// rules, but the argument must be supplied in order for the call to succeed.
promise->Reject(script::kTypeError);
return promise;
}
script::Handle<MediaDevices::MediaStreamPromise> MediaDevices::GetUserMedia(
const media_stream::MediaStreamConstraints& constraints) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
script::Handle<MediaDevices::MediaStreamPromise> promise =
script_value_factory_->CreateInterfacePromise<
script::ScriptValueFactory::WrappablePromise>();
if (!constraints.audio()) {
// Step 3: If requestedMediaTypes is the empty set, return a promise
// rejected with a TypeError. The word "optional" occurs in the WebIDL due
// to WebIDL rules, but the argument must be supplied in order for the call
// to succeed.
DLOG(INFO) << "Audio constraint must be true.";
promise->Reject(script::kTypeError);
return promise;
}
// Steps 4-7 are not needed for cobalt.
// Step 8 is to create a promise (which is already done).
// Step 9: Construct a list of MediaStreamTracks that we have permission.
// Access Microphone Device
// Create Audio Source (attach a device to it), source will periodically read
// from the device, and push out data to ALL of the tracks
if (!audio_source_) {
audio_source_ = new media_stream::MicrophoneAudioSource(
settings_->microphone_options(),
base::Bind(&MediaDevices::OnMicrophoneSuccess, weak_this_),
base::Closure(), // TODO: remove this redundant callback.
base::Bind(&MediaDevices::OnMicrophoneError, weak_this_));
audio_source_->SetStopCallback(
base::Bind(&MediaDevices::OnMicrophoneStopped, weak_this_));
}
std::unique_ptr<MediaStreamPromiseValue::Reference> promise_reference(
new MediaStreamPromiseValue::Reference(this, promise));
pending_microphone_promises_.push_back(std::move(promise_reference));
if (!pending_microphone_track_) {
pending_microphone_track_ =
new media_stream::MediaStreamAudioTrack(settings_);
// Starts the source, if needed. Also calls start on the audio track.
audio_source_->ConnectToTrack(
base::polymorphic_downcast<media_stream::MediaStreamAudioTrack*>(
pending_microphone_track_.get()));
}
// Step 10, return promise.
return promise;
}
void MediaDevices::OnMicrophoneError(
speech::MicrophoneManager::MicrophoneError error, std::string message) {
DLOG(INFO) << "MediaDevices::OnMicrophoneError " << message;
// No special error handling logic besides logging the message above, so just
// delegate to the OnMicrophoneStopped() functionality.
OnMicrophoneStopped();
}
void MediaDevices::OnMicrophoneStopped() {
if (javascript_message_loop_->task_runner() !=
base::MessageLoop::current()->task_runner()) {
javascript_message_loop_->task_runner()->PostTask(
FROM_HERE, base::Bind(&MediaDevices::OnMicrophoneStopped, weak_this_));
return;
}
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DLOG(INFO) << "MediaDevices::OnMicrophoneStopped()";
pending_microphone_track_ = nullptr;
audio_source_ = nullptr;
for (auto& promise : pending_microphone_promises_) {
promise->value().Reject(
new dom::DOMException(dom::DOMException::kNotAllowedErr));
}
pending_microphone_promises_.clear();
}
void MediaDevices::OnMicrophoneSuccess() {
if (javascript_message_loop_->task_runner() !=
base::MessageLoop::current()->task_runner()) {
javascript_message_loop_->task_runner()->PostTask(
FROM_HERE, base::Bind(&MediaDevices::OnMicrophoneSuccess, this));
return;
}
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
using media_stream::MediaStream;
MediaStream::TrackSequences audio_tracks;
pending_microphone_track_->SetMediaTrackSettings(
audio_source_->GetMediaTrackSettings());
audio_tracks.push_back(pending_microphone_track_);
pending_microphone_track_ = nullptr;
for (auto& promise : pending_microphone_promises_) {
promise->value().Resolve(
base::WrapRefCounted(new MediaStream(settings_, audio_tracks)));
}
pending_microphone_promises_.clear();
}
} // namespace media_capture
} // namespace cobalt