Commit f3832d78 authored by Thomas Guilbert's avatar Thomas Guilbert Committed by Commit Bot

Convert VideoTrackReader to use callbacks

This CL replaces VideoTrackReader's ReadableStream with an output
callback, to be passed in at construction time.

Switching VTR to use a callback aligns it with the general direction of
WebCodecs. It also works because there isn't the need for backpressure
when using getUserMedia(). Additionally, a change in constraints to the
MediaStreamTrack would mean that we might have to flush the unread frames
from ReadableStream. This shouldn't happen with a callback (but we
don't invalidate pending frames when constraints change, for now).

Bug: 1080834
Change-Id: I5ff424f9440660dadc52ba6d4d76d14b5840a6d7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2191204
Commit-Queue: Thomas Guilbert <tguilbert@chromium.org>
Auto-Submit: Thomas Guilbert <tguilbert@chromium.org>
Reviewed-by: default avatarDan Sanders <sandersd@chromium.org>
Cr-Commit-Position: refs/heads/master@{#770841}
parent 2367ac92
...@@ -4,12 +4,10 @@ ...@@ -4,12 +4,10 @@
#include "third_party/blink/renderer/modules/webcodecs/video_track_reader.h" #include "third_party/blink/renderer/modules/webcodecs/video_track_reader.h"
#include "base/threading/thread_task_runner_handle.h"
#include "media/base/video_frame.h" #include "media/base/video_frame.h"
#include "third_party/blink/public/web/modules/mediastream/media_stream_video_sink.h"
#include "third_party/blink/public/web/modules/mediastream/media_stream_video_track.h" #include "third_party/blink/public/web/modules/mediastream/media_stream_video_track.h"
#include "third_party/blink/renderer/core/streams/readable_stream.h" #include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.h"
#include "third_party/blink/renderer/core/streams/underlying_source_base.h"
#include "third_party/blink/renderer/modules/webcodecs/video_frame.h" #include "third_party/blink/renderer/modules/webcodecs/video_frame.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h" #include "third_party/blink/renderer/platform/bindings/script_state.h"
...@@ -18,70 +16,93 @@ ...@@ -18,70 +16,93 @@
namespace blink { namespace blink {
class VideoTrackReadableStreamSource final : public UnderlyingSourceBase, VideoTrackReader::VideoTrackReader(ScriptState* script_state,
public MediaStreamVideoSink { MediaStreamTrack* track)
public: : ExecutionContextLifecycleObserver(ExecutionContext::From(script_state)),
VideoTrackReadableStreamSource(ScriptState* script_state, started_(false),
MediaStreamTrack* track) real_time_media_task_runner_(
: UnderlyingSourceBase(script_state), ExecutionContext::From(script_state)
task_runner_(ExecutionContext::From(script_state) ->GetTaskRunner(TaskType::kInternalMediaRealTime)),
->GetTaskRunner(TaskType::kInternalMedia)) { track_(track) {}
ConnectToTrack(track->Component(),
ConvertToBaseRepeatingCallback(CrossThreadBindRepeating( void VideoTrackReader::start(V8VideoDecoderOutputCallback* callback,
&VideoTrackReadableStreamSource::OnFrameFromVideoTrack, ExceptionState& exception_state) {
WrapCrossThreadPersistent(this))), DCHECK(real_time_media_task_runner_->BelongsToCurrentThread());
false /* is_sink_secure */);
if (started_) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"The VideoTrackReader has already been started.");
return;
} }
// Callback of MediaStreamVideoSink::ConnectToTrack started_ = true;
void OnFrameFromVideoTrack(scoped_refptr<media::VideoFrame> media_frame, callback_ = callback;
base::TimeTicks estimated_capture_time) { ConnectToTrack(track_->Component(),
// The value of estimated_capture_time here seems to almost always be the ConvertToBaseRepeatingCallback(CrossThreadBindRepeating(
// system clock and most implementations of this callback ignore it. &VideoTrackReader::OnFrameFromVideoTrack,
// So, we will also ignore it. WrapCrossThreadPersistent(this))),
DCHECK(media_frame); false /* is_sink_secure */);
PostCrossThreadTask( }
*task_runner_.get(), FROM_HERE,
CrossThreadBindOnce(
&VideoTrackReadableStreamSource::OnFrameFromVideoTrackOnTaskRunner,
WrapCrossThreadPersistent(this), std::move(media_frame)));
}
void OnFrameFromVideoTrackOnTaskRunner( void VideoTrackReader::stop(ExceptionState& exception_state) {
scoped_refptr<media::VideoFrame> media_frame) { DCHECK(real_time_media_task_runner_->BelongsToCurrentThread());
Controller()->Enqueue(
MakeGarbageCollected<VideoFrame>(std::move(media_frame)));
}
// MediaStreamVideoSink override if (!started_) {
void OnReadyStateChanged(WebMediaStreamSource::ReadyState state) override { exception_state.ThrowDOMException(
if (state == WebMediaStreamSource::kReadyStateEnded) DOMExceptionCode::kInvalidStateError,
Close(); "The VideoTrackReader has already been stopped.");
return;
} }
// UnderlyingSourceBase overrides. StopInternal();
ScriptPromise pull(ScriptState* script_state) override { }
// Since video tracks are all push-based with no back pressure, there's
// nothing to do here.
return ScriptPromise::CastUndefined(script_state);
}
ScriptPromise Cancel(ScriptState* script_state, ScriptValue reason) override { void VideoTrackReader::StopInternal() {
Close(); DCHECK(real_time_media_task_runner_->BelongsToCurrentThread());
return ScriptPromise::CastUndefined(script_state); started_ = false;
} callback_ = nullptr;
DisconnectFromTrack();
}
void VideoTrackReader::OnFrameFromVideoTrack(
scoped_refptr<media::VideoFrame> media_frame,
base::TimeTicks estimated_capture_time) {
// The value of estimated_capture_time here seems to almost always be the
// system clock and most implementations of this callback ignore it.
// So, we will also ignore it.
DCHECK(media_frame);
PostCrossThreadTask(
*real_time_media_task_runner_.get(), FROM_HERE,
CrossThreadBindOnce(&VideoTrackReader::ExecuteCallbackOnMainThread,
WrapCrossThreadPersistent(this),
std::move(media_frame)));
}
void ContextDestroyed() override { DisconnectFromTrack(); } void VideoTrackReader::ExecuteCallbackOnMainThread(
scoped_refptr<media::VideoFrame> media_frame) {
DCHECK(real_time_media_task_runner_->BelongsToCurrentThread());
void Close() { if (!callback_) {
if (Controller()) // We may have already been stopped.
Controller()->Close(); return;
DisconnectFromTrack();
} }
// VideoFrames will be queue on this task runner. // If |track_|'s constraints changed (e.g. the resolution changed from a call
const scoped_refptr<base::SingleThreadTaskRunner> task_runner_; // to MediaStreamTrack.applyConstraints() in JS), this |media_frame| might
}; // still have the old constraints, due to the thread hop.
// We may want to invalidate |media_frames| when constraints change, but it's
// unclear whether this is a problem for now.
callback_->InvokeAndReportException(
nullptr, MakeGarbageCollected<VideoFrame>(std::move(media_frame)));
}
void VideoTrackReader::OnReadyStateChanged(
WebMediaStreamSource::ReadyState state) {
if (state == WebMediaStreamSource::kReadyStateEnded)
StopInternal();
}
VideoTrackReader* VideoTrackReader::Create(ScriptState* script_state, VideoTrackReader* VideoTrackReader::Create(ScriptState* script_state,
MediaStreamTrack* track, MediaStreamTrack* track,
...@@ -99,23 +120,14 @@ VideoTrackReader* VideoTrackReader::Create(ScriptState* script_state, ...@@ -99,23 +120,14 @@ VideoTrackReader* VideoTrackReader::Create(ScriptState* script_state,
return nullptr; return nullptr;
} }
auto* source = return MakeGarbageCollected<VideoTrackReader>(script_state, track);
MakeGarbageCollected<VideoTrackReadableStreamSource>(script_state, track);
auto* readable =
ReadableStream::CreateWithCountQueueingStrategy(script_state, source, 0);
return MakeGarbageCollected<VideoTrackReader>(readable);
}
VideoTrackReader::VideoTrackReader(ReadableStream* readable)
: readable_(readable) {}
ReadableStream* VideoTrackReader::readable() const {
return readable_;
} }
void VideoTrackReader::Trace(Visitor* visitor) const { void VideoTrackReader::Trace(Visitor* visitor) const {
visitor->Trace(readable_); visitor->Trace(track_);
visitor->Trace(callback_);
ScriptWrappable::Trace(visitor); ScriptWrappable::Trace(visitor);
ExecutionContextLifecycleObserver::Trace(visitor);
} }
} // namespace blink } // namespace blink
...@@ -5,24 +5,35 @@ ...@@ -5,24 +5,35 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBCODECS_VIDEO_TRACK_READER_H_ #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBCODECS_VIDEO_TRACK_READER_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBCODECS_VIDEO_TRACK_READER_H_ #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBCODECS_VIDEO_TRACK_READER_H_
#include "third_party/blink/public/web/modules/mediastream/media_stream_video_sink.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_output_callback.h"
#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_track.h" #include "third_party/blink/renderer/modules/mediastream/media_stream_track.h"
#include "third_party/blink/renderer/modules/modules_export.h" #include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h" #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
namespace blink { namespace blink {
class ReadableStream;
class ScriptState; class ScriptState;
class MODULES_EXPORT VideoTrackReader final : public ScriptWrappable { class MODULES_EXPORT VideoTrackReader final
: public ScriptWrappable,
public ExecutionContextLifecycleObserver,
public MediaStreamVideoSink {
DEFINE_WRAPPERTYPEINFO(); DEFINE_WRAPPERTYPEINFO();
USING_GARBAGE_COLLECTED_MIXIN(VideoTrackReader);
public: public:
static VideoTrackReader* Create(ScriptState* script_state, static VideoTrackReader* Create(ScriptState*,
MediaStreamTrack* track, MediaStreamTrack*,
ExceptionState& exception_state); ExceptionState&);
explicit VideoTrackReader(ReadableStream* readable); VideoTrackReader(ScriptState*, MediaStreamTrack*);
ReadableStream* readable() const;
// Connects |this| to |track_| and starts delivering frames via |callback_|.
void start(V8VideoDecoderOutputCallback*, ExceptionState&);
// Disconnects from |track_| and clears |callback_|.
void stop(ExceptionState&);
void Trace(Visitor* visitor) const override; void Trace(Visitor* visitor) const override;
...@@ -30,7 +41,29 @@ class MODULES_EXPORT VideoTrackReader final : public ScriptWrappable { ...@@ -30,7 +41,29 @@ class MODULES_EXPORT VideoTrackReader final : public ScriptWrappable {
VideoTrackReader(const VideoTrackReader&) = delete; VideoTrackReader(const VideoTrackReader&) = delete;
VideoTrackReader& operator=(const VideoTrackReader&) = delete; VideoTrackReader& operator=(const VideoTrackReader&) = delete;
Member<ReadableStream> readable_; // ExecutionContextLifecycleObserver implementation.
void ContextDestroyed() override { DisconnectFromTrack(); }
// MediaStreamVideoSink implementation.
void OnReadyStateChanged(WebMediaStreamSource::ReadyState) override;
// Callback For MediaStreamVideoSink::ConnectToTrack.
void OnFrameFromVideoTrack(scoped_refptr<media::VideoFrame> media_frame,
base::TimeTicks estimated_capture_time);
void StopInternal();
void ExecuteCallbackOnMainThread(
scoped_refptr<media::VideoFrame> media_frame);
// Whether we are connected to |track_| and using |callback_| to deliver
// frames.
bool started_;
const scoped_refptr<base::SingleThreadTaskRunner>
real_time_media_task_runner_;
Member<V8VideoDecoderOutputCallback> callback_;
Member<MediaStreamTrack> track_;
}; };
} // namespace blink } // namespace blink
......
...@@ -10,5 +10,10 @@ ...@@ -10,5 +10,10 @@
] interface VideoTrackReader { ] interface VideoTrackReader {
[CallWith=ScriptState, RaisesException] [CallWith=ScriptState, RaisesException]
constructor(MediaStreamTrack track); constructor(MediaStreamTrack track);
readonly attribute ReadableStream readable; // of VideoFrame
[RaisesException]
void start(VideoDecoderOutputCallback callback);
[RaisesException]
void stop();
}; };
...@@ -3,7 +3,9 @@ ...@@ -3,7 +3,9 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "base/run_loop.h" #include "base/run_loop.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_frame.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_video_frame.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_track_writer_parameters.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_video_track_writer_parameters.h"
...@@ -15,10 +17,27 @@ ...@@ -15,10 +17,27 @@
#include "third_party/blink/renderer/modules/webcodecs/video_track_writer.h" #include "third_party/blink/renderer/modules/webcodecs/video_track_writer.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h" #include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/bindings/to_v8.h"
#include "third_party/blink/renderer/platform/testing/io_task_runner_testing_platform_support.h" #include "third_party/blink/renderer/platform/testing/io_task_runner_testing_platform_support.h"
namespace blink { namespace blink {
class MockFunction : public ScriptFunction {
public:
static testing::StrictMock<MockFunction>* Create(ScriptState* script_state) {
return MakeGarbageCollected<testing::StrictMock<MockFunction>>(
script_state);
}
v8::Local<v8::Function> Bind() { return BindToV8Function(); }
MOCK_METHOD1(Call, ScriptValue(ScriptValue));
protected:
explicit MockFunction(ScriptState* script_state)
: ScriptFunction(script_state) {}
};
class VideoTrackReaderWriterTest : public testing::Test { class VideoTrackReaderWriterTest : public testing::Test {
public: public:
void TearDown() override { void TearDown() override {
...@@ -42,6 +61,10 @@ class VideoTrackReaderWriterTest : public testing::Test { ...@@ -42,6 +61,10 @@ class VideoTrackReaderWriterTest : public testing::Test {
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
} }
V8VideoDecoderOutputCallback* GetCallback(MockFunction* function) {
return V8VideoDecoderOutputCallback::Create(function->Bind());
}
private: private:
ScopedTestingPlatformSupport<IOTaskRunnerTestingPlatformSupport> platform_; ScopedTestingPlatformSupport<IOTaskRunnerTestingPlatformSupport> platform_;
}; };
...@@ -54,9 +77,13 @@ TEST_F(VideoTrackReaderWriterTest, WriteAndRead) { ...@@ -54,9 +77,13 @@ TEST_F(VideoTrackReaderWriterTest, WriteAndRead) {
params.setReleaseFrames(false); params.setReleaseFrames(false);
auto* writer = auto* writer =
VideoTrackWriter::Create(script_state, &params, ASSERT_NO_EXCEPTION); VideoTrackWriter::Create(script_state, &params, ASSERT_NO_EXCEPTION);
auto* read_output_function = MockFunction::Create(script_state);
auto* reader = VideoTrackReader::Create(script_state, writer->track(), auto* reader = VideoTrackReader::Create(script_state, writer->track(),
ASSERT_NO_EXCEPTION); ASSERT_NO_EXCEPTION);
reader->start(GetCallback(read_output_function), ASSERT_NO_EXCEPTION);
auto* frame = CreateBlackVideoFrame(); auto* frame = CreateBlackVideoFrame();
writer->writable() writer->writable()
->getWriter(script_state, ASSERT_NO_EXCEPTION) ->getWriter(script_state, ASSERT_NO_EXCEPTION)
...@@ -64,24 +91,23 @@ TEST_F(VideoTrackReaderWriterTest, WriteAndRead) { ...@@ -64,24 +91,23 @@ TEST_F(VideoTrackReaderWriterTest, WriteAndRead) {
ScriptValue(scope.GetIsolate(), ToV8(frame, script_state)), ScriptValue(scope.GetIsolate(), ToV8(frame, script_state)),
ASSERT_NO_EXCEPTION); ASSERT_NO_EXCEPTION);
auto read_promise = reader->readable() ScriptValue v8_frame;
->getReader(script_state, ASSERT_NO_EXCEPTION) // We don't care about Call()'s return value, so we use undefined.
->read(script_state, ASSERT_NO_EXCEPTION); ScriptValue undefined_value =
auto v8_read_promise = read_promise.V8Value().As<v8::Promise>(); ScriptValue::From(script_state, ToV8UndefinedGenerator());
EXPECT_CALL(*read_output_function, Call(testing::_))
EXPECT_EQ(v8::Promise::kPending, v8_read_promise->State()); .WillOnce(
testing::DoAll(testing::SaveArg<0>(&v8_frame),
testing::Return(testing::ByMove(undefined_value))));
RunIOUntilIdle(); RunIOUntilIdle();
EXPECT_EQ(v8::Promise::kFulfilled, v8_read_promise->State()); testing::Mock::VerifyAndClear(read_output_function);
auto* read_frame =
V8VideoFrame::ToImplWithTypeCheck(scope.GetIsolate(), v8_frame.V8Value());
auto* read_frame = V8VideoFrame::ToImplWithTypeCheck( reader->stop(ASSERT_NO_EXCEPTION);
scope.GetIsolate(),
v8_read_promise->Result()
->ToObject(scope.GetContext())
.ToLocalChecked()
->Get(scope.GetContext(), V8String(scope.GetIsolate(), "value"))
.ToLocalChecked());
ASSERT_TRUE(frame); ASSERT_TRUE(frame);
EXPECT_EQ(frame->frame(), read_frame->frame()); EXPECT_EQ(frame->frame(), read_frame->frame());
......
...@@ -8831,8 +8831,9 @@ interface VideoTrackList : EventTarget ...@@ -8831,8 +8831,9 @@ interface VideoTrackList : EventTarget
setter onremovetrack setter onremovetrack
interface VideoTrackReader interface VideoTrackReader
attribute @@toStringTag attribute @@toStringTag
getter readable
method constructor method constructor
method start
method stop
interface VideoTrackWriter interface VideoTrackWriter
attribute @@toStringTag attribute @@toStringTag
getter track getter track
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment