blob: 46f9ad4c433d985c94115d2b6f930dca9dbc9711 [file] [log] [blame]
// Copyright 2015 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 "third_party/blink/renderer/modules/mediacapturefromelement/html_media_element_capture.h"
#include "base/memory/ptr_util.h"
#include "third_party/blink/public/mojom/mediastream/media_devices.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/web/modules/mediastream/media_stream_video_source.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/html/media/html_media_element.h"
#include "third_party/blink/renderer/core/html/track/audio_track_list.h"
#include "third_party/blink/renderer/core/html/track/video_track_list.h"
#include "third_party/blink/renderer/modules/encryptedmedia/html_media_element_encrypted_media.h"
#include "third_party/blink/renderer/modules/encryptedmedia/media_keys.h"
#include "third_party/blink/renderer/modules/mediacapturefromelement/html_audio_element_capturer_source.h"
#include "third_party/blink/renderer/modules/mediacapturefromelement/html_video_element_capturer_source.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_constraints_util.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_utils.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_video_track.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_audio_source.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_source.h"
#include "third_party/blink/renderer/platform/wtf/uuid.h"
namespace blink {
namespace {
// This method creates a MediaStreamSource with the provided video
// capturer source. A new MediaStreamComponent + MediaStreamTrack pair is
// created, connected to the source and is plugged into the
// MediaStreamDescriptor (|descriptor|).
// |is_remote| should be true if the source of the data is not a local device.
// |is_readonly| should be true if the format of the data cannot be changed by
// MediaTrackConstraints.
bool AddVideoTrackToMediaStream(
LocalFrame* frame,
std::unique_ptr<media::VideoCapturerSource> video_source,
bool is_remote,
MediaStreamDescriptor* descriptor) {
DCHECK(video_source.get());
if (!descriptor) {
DLOG(ERROR) << "MediaStreamDescriptor is null";
return false;
}
media::VideoCaptureFormats preferred_formats =
video_source->GetPreferredFormats();
auto media_stream_video_source =
std::make_unique<MediaStreamVideoCapturerSource>(
frame, WebPlatformMediaStreamSource::SourceStoppedCallback(),
std::move(video_source));
auto* media_stream_video_source_ptr = media_stream_video_source.get();
const String track_id(WTF::CreateCanonicalUUIDString());
auto* media_stream_source = MakeGarbageCollected<MediaStreamSource>(
track_id, MediaStreamSource::kTypeVideo, track_id, is_remote);
media_stream_source->SetPlatformSource(std::move(media_stream_video_source));
media_stream_source->SetCapabilities(ComputeCapabilitiesForVideoSource(
track_id, preferred_formats, mojom::blink::FacingMode::NONE,
false /* is_device_capture */));
descriptor->AddRemoteTrack(MediaStreamVideoTrack::CreateVideoTrack(
media_stream_video_source_ptr,
MediaStreamVideoSource::ConstraintsOnceCallback(), true));
return true;
}
// Fills in the MediaStreamDescriptor to capture from the WebMediaPlayer
// identified by the second parameter.
void CreateHTMLVideoElementCapturer(
LocalFrame* frame,
MediaStreamDescriptor* descriptor,
WebMediaPlayer* web_media_player,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
DCHECK(descriptor);
DCHECK(web_media_player);
AddVideoTrackToMediaStream(
frame,
HtmlVideoElementCapturerSource::CreateFromWebMediaPlayerImpl(
web_media_player, Platform::Current()->GetIOTaskRunner(),
std::move(task_runner)),
false, // is_remote
descriptor);
}
// Fills in the MediaStreamDescriptor to capture from the WebMediaPlayer
// identified by the second parameter.
void CreateHTMLAudioElementCapturer(
LocalFrame*,
MediaStreamDescriptor* descriptor,
WebMediaPlayer* web_media_player,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
DCHECK(descriptor);
DCHECK(web_media_player);
const String track_id = WTF::CreateCanonicalUUIDString();
auto* media_stream_source = MakeGarbageCollected<MediaStreamSource>(
track_id, MediaStreamSource::StreamType::kTypeAudio, track_id,
false /* is_remote */);
auto* media_stream_component =
MakeGarbageCollected<MediaStreamComponent>(media_stream_source);
MediaStreamAudioSource* const media_stream_audio_source =
HtmlAudioElementCapturerSource::CreateFromWebMediaPlayerImpl(
web_media_player, std::move(task_runner));
// |media_stream_source| takes ownership of |media_stream_audio_source|.
media_stream_source->SetPlatformSource(
base::WrapUnique(media_stream_audio_source));
MediaStreamSource::Capabilities capabilities;
capabilities.device_id = track_id;
capabilities.echo_cancellation.emplace_back(false);
capabilities.auto_gain_control.emplace_back(false);
capabilities.noise_suppression.emplace_back(false);
capabilities.sample_size = {
media::SampleFormatToBitsPerChannel(media::kSampleFormatS16), // min
media::SampleFormatToBitsPerChannel(media::kSampleFormatS16) // max
};
media_stream_source->SetCapabilities(capabilities);
media_stream_audio_source->ConnectToTrack(media_stream_component);
descriptor->AddRemoteTrack(media_stream_component);
}
// Class to register to the events of |m_mediaElement|, acting accordingly on
// the tracks of |m_mediaStream|.
class MediaElementEventListener final : public NativeEventListener {
public:
MediaElementEventListener(HTMLMediaElement*, MediaStream*);
void UpdateSources(ExecutionContext*);
void Trace(Visitor*) const override;
// EventListener implementation.
void Invoke(ExecutionContext*, Event*) override;
private:
Member<HTMLMediaElement> media_element_;
Member<MediaStream> media_stream_;
HeapHashSet<WeakMember<MediaStreamSource>> sources_;
};
MediaElementEventListener::MediaElementEventListener(HTMLMediaElement* element,
MediaStream* stream)
: NativeEventListener(), media_element_(element), media_stream_(stream) {
UpdateSources(element->GetExecutionContext());
}
void MediaElementEventListener::Invoke(ExecutionContext* context,
Event* event) {
DVLOG(2) << __func__ << " " << event->type();
DCHECK(media_stream_);
if (event->type() == event_type_names::kEnded) {
const MediaStreamTrackVector tracks = media_stream_->getTracks();
for (const auto& track : tracks) {
track->stopTrack(context);
media_stream_->RemoveTrackByComponentAndFireEvents(
track->Component(),
MediaStreamDescriptorClient::DispatchEventTiming::kScheduled);
}
media_stream_->StreamEnded();
return;
}
if (event->type() != event_type_names::kLoadedmetadata)
return;
// If |media_element_| is a MediaStream, clone the new tracks.
if (media_element_->GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream) {
const MediaStreamTrackVector tracks = media_stream_->getTracks();
for (const auto& track : tracks) {
track->stopTrack(context);
media_stream_->RemoveTrackByComponentAndFireEvents(
track->Component(),
MediaStreamDescriptorClient::DispatchEventTiming::kScheduled);
}
MediaStreamDescriptor* const descriptor = media_element_->GetSrcObject();
DCHECK(descriptor);
for (unsigned i = 0; i < descriptor->NumberOfAudioComponents(); i++) {
media_stream_->AddTrackByComponentAndFireEvents(
descriptor->AudioComponent(i),
MediaStreamDescriptorClient::DispatchEventTiming::kScheduled);
}
for (unsigned i = 0; i < descriptor->NumberOfVideoComponents(); i++) {
media_stream_->AddTrackByComponentAndFireEvents(
descriptor->VideoComponent(i),
MediaStreamDescriptorClient::DispatchEventTiming::kScheduled);
}
UpdateSources(context);
return;
}
auto* descriptor = MakeGarbageCollected<MediaStreamDescriptor>(
WTF::CreateCanonicalUUIDString(), MediaStreamComponentVector(),
MediaStreamComponentVector());
if (media_element_->HasVideo()) {
CreateHTMLVideoElementCapturer(
To<LocalDOMWindow>(context)->GetFrame(), descriptor,
media_element_->GetWebMediaPlayer(),
media_element_->GetExecutionContext()->GetTaskRunner(
TaskType::kInternalMediaRealTime));
}
if (media_element_->HasAudio()) {
CreateHTMLAudioElementCapturer(
To<LocalDOMWindow>(context)->GetFrame(), descriptor,
media_element_->GetWebMediaPlayer(),
media_element_->GetExecutionContext()->GetTaskRunner(
TaskType::kInternalMediaRealTime));
}
MediaStreamComponentVector video_components = descriptor->VideoComponents();
for (auto component : video_components) {
media_stream_->AddTrackByComponentAndFireEvents(
component,
MediaStreamDescriptorClient::DispatchEventTiming::kScheduled);
}
MediaStreamComponentVector audio_components = descriptor->AudioComponents();
for (auto component : audio_components) {
media_stream_->AddTrackByComponentAndFireEvents(
component,
MediaStreamDescriptorClient::DispatchEventTiming::kScheduled);
}
DVLOG(2) << "#videotracks: " << video_components.size()
<< " #audiotracks: " << audio_components.size();
UpdateSources(context);
}
void DidStopMediaStreamSource(MediaStreamSource* source) {
if (!source)
return;
WebPlatformMediaStreamSource* const platform_source =
source->GetPlatformSource();
DCHECK(platform_source);
platform_source->StopSource();
}
void MediaElementEventListener::UpdateSources(ExecutionContext* context) {
for (auto track : media_stream_->getTracks())
sources_.insert(track->Component()->Source());
// Handling of the ended event in JS triggered by DidStopMediaStreamSource()
// may cause a reentrant call to this function, which can modify |sources_|.
// Iterate over a copy of |sources_| to avoid invalidation of the iterator
// when a reentrant call occurs.
auto sources_copy = sources_;
if (!media_element_->currentSrc().IsEmpty() &&
!media_element_->IsMediaDataCorsSameOrigin()) {
for (auto source : sources_copy)
DidStopMediaStreamSource(source.Get());
}
}
void MediaElementEventListener::Trace(Visitor* visitor) const {
visitor->Trace(media_element_);
visitor->Trace(media_stream_);
visitor->Trace(sources_);
EventListener::Trace(visitor);
}
} // anonymous namespace
// static
MediaStream* HTMLMediaElementCapture::captureStream(
ScriptState* script_state,
HTMLMediaElement& element,
ExceptionState& exception_state) {
// Avoid capturing from EME-protected Media Elements.
if (HTMLMediaElementEncryptedMedia::mediaKeys(element)) {
// This exception is not defined in the spec, see
// https://github.com/w3c/mediacapture-fromelement/issues/20.
exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
"Stream capture not supported with EME");
return nullptr;
}
if (!script_state->ContextIsValid()) {
exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
"The context has been destroyed");
return nullptr;
}
ExecutionContext* context = ExecutionContext::From(script_state);
if (!element.currentSrc().IsEmpty() && !element.IsMediaDataCorsSameOrigin()) {
exception_state.ThrowSecurityError(
"Cannot capture from element with cross-origin data");
return nullptr;
}
auto* descriptor = MakeGarbageCollected<MediaStreamDescriptor>(
WTF::CreateCanonicalUUIDString(), MediaStreamComponentVector(),
MediaStreamComponentVector());
// Create() duplicates the MediaStreamTracks inside |descriptor|.
MediaStream* stream = MediaStream::Create(context, descriptor);
MediaElementEventListener* listener =
MakeGarbageCollected<MediaElementEventListener>(&element, stream);
element.addEventListener(event_type_names::kLoadedmetadata, listener, false);
element.addEventListener(event_type_names::kEnded, listener, false);
// If |element| is actually playing a MediaStream, just clone it.
if (element.GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream) {
MediaStreamDescriptor* const descriptor = element.GetSrcObject();
DCHECK(descriptor);
return MediaStream::Create(context, descriptor);
}
LocalFrame* frame = ToLocalFrameIfNotDetached(script_state->GetContext());
DCHECK(frame);
if (element.HasVideo()) {
CreateHTMLVideoElementCapturer(frame, descriptor,
element.GetWebMediaPlayer(),
element.GetExecutionContext()->GetTaskRunner(
TaskType::kInternalMediaRealTime));
}
if (element.HasAudio()) {
CreateHTMLAudioElementCapturer(frame, descriptor,
element.GetWebMediaPlayer(),
element.GetExecutionContext()->GetTaskRunner(
TaskType::kInternalMediaRealTime));
}
listener->UpdateSources(context);
// If element.currentSrc().isNull() then |stream| will have no tracks, those
// will be added eventually afterwards via MediaElementEventListener.
return stream;
}
} // namespace blink