blob: 5d55827be36f150be09ce94fce4ad1b0a6d36b15 [file] [log] [blame]
// Copyright 2020 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/mediastream/media_stream_video_track_underlying_source.h"
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/modules/mediastream/web_media_stream_track.h"
#include "third_party/blink/public/web/web_heap.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/streams/readable_stream.h"
#include "third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_track.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_video_track.h"
#include "third_party/blink/renderer/modules/mediastream/mock_media_stream_video_sink.h"
#include "third_party/blink/renderer/modules/mediastream/pushable_media_stream_video_source.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/testing/io_task_runner_testing_platform_support.h"
#include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
using testing::_;
namespace blink {
class MediaStreamVideoTrackUnderlyingSourceTest : public testing::Test {
public:
MediaStreamVideoTrackUnderlyingSourceTest()
: media_stream_source_(MakeGarbageCollected<MediaStreamSource>(
"dummy_source_id",
MediaStreamSource::kTypeVideo,
"dummy_source_name",
false /* remote */)),
pushable_video_source_(new PushableMediaStreamVideoSource()) {
media_stream_source_->SetPlatformSource(
base::WrapUnique(pushable_video_source_));
}
~MediaStreamVideoTrackUnderlyingSourceTest() override {
platform_->RunUntilIdle();
WebHeap::CollectAllGarbageForTesting();
}
MediaStreamComponent* CreateTrack(ExecutionContext* execution_context) {
return MakeGarbageCollected<MediaStreamTrack>(
execution_context,
MediaStreamVideoTrack::CreateVideoTrack(
pushable_video_source_,
MediaStreamVideoSource::ConstraintsOnceCallback(),
/*enabled=*/true))
->Component();
}
MediaStreamVideoTrackUnderlyingSource* CreateSource(
ScriptState* script_state,
MediaStreamComponent* track,
wtf_size_t buffer_size) {
return MakeGarbageCollected<MediaStreamVideoTrackUnderlyingSource>(
script_state, track, buffer_size);
}
MediaStreamVideoTrackUnderlyingSource* CreateSource(
ScriptState* script_state,
MediaStreamComponent* track) {
return CreateSource(script_state, track, 1u);
}
MediaStreamVideoTrackUnderlyingSource* CreateSource(ScriptState* script_state,
wtf_size_t buffer_size) {
MediaStreamComponent* track =
CreateTrack(ExecutionContext::From(script_state));
return CreateSource(script_state, track, buffer_size);
}
MediaStreamVideoTrackUnderlyingSource* CreateSource(
ScriptState* script_state) {
return CreateSource(script_state, 1u);
}
protected:
void PushFrame(
const base::Optional<base::TimeDelta>& timestamp = base::nullopt) {
const scoped_refptr<media::VideoFrame> frame =
media::VideoFrame::CreateBlackFrame(gfx::Size(10, 5));
if (timestamp)
frame->set_timestamp(*timestamp);
pushable_video_source_->PushFrame(frame, base::TimeTicks());
platform_->RunUntilIdle();
}
ScopedTestingPlatformSupport<IOTaskRunnerTestingPlatformSupport> platform_;
const Persistent<MediaStreamSource> media_stream_source_;
PushableMediaStreamVideoSource* const pushable_video_source_;
};
TEST_F(MediaStreamVideoTrackUnderlyingSourceTest,
VideoFrameFlowsThroughStreamAndCloses) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* source = CreateSource(script_state);
auto* stream =
ReadableStream::CreateWithCountQueueingStrategy(script_state, source, 0);
NonThrowableExceptionState exception_state;
auto* reader =
stream->GetDefaultReaderForTesting(script_state, exception_state);
ScriptPromiseTester read_tester(script_state,
reader->read(script_state, exception_state));
EXPECT_FALSE(read_tester.IsFulfilled());
PushFrame();
read_tester.WaitUntilSettled();
EXPECT_TRUE(read_tester.IsFulfilled());
source->Close();
}
TEST_F(MediaStreamVideoTrackUnderlyingSourceTest,
CancelStreamDisconnectsFromTrack) {
V8TestingScope v8_scope;
MediaStreamComponent* track = CreateTrack(v8_scope.GetExecutionContext());
MediaStreamVideoTrack* video_track = MediaStreamVideoTrack::From(track);
// Initially the track has no sinks.
EXPECT_EQ(video_track->CountSinks(), 0u);
auto* source = CreateSource(v8_scope.GetScriptState(), track);
auto* stream = ReadableStream::CreateWithCountQueueingStrategy(
v8_scope.GetScriptState(), source, 0);
// The stream is a sink to the track.
EXPECT_EQ(video_track->CountSinks(), 1u);
NonThrowableExceptionState exception_state;
stream->cancel(v8_scope.GetScriptState(), exception_state);
// Canceling the stream disconnects it from the track.
EXPECT_EQ(video_track->CountSinks(), 0u);
}
TEST_F(MediaStreamVideoTrackUnderlyingSourceTest,
DropOldFramesWhenQueueIsFull) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
const wtf_size_t buffer_size = 5;
auto* source = CreateSource(script_state, buffer_size);
EXPECT_EQ(source->MaxQueueSize(), buffer_size);
// Create a stream to ensure there is a controller associated to the source.
ReadableStream::CreateWithCountQueueingStrategy(script_state, source, 0);
// Add a sink to the track to make it possible to wait until a pushed frame
// is delivered to sinks, including |source|, which is a sink of the track.
MockMediaStreamVideoSink mock_sink;
mock_sink.ConnectToTrack(WebMediaStreamTrack(source->Track()));
auto push_frame_sync = [&mock_sink, this](const base::TimeDelta timestamp) {
base::RunLoop sink_loop;
EXPECT_CALL(mock_sink, OnVideoFrame(_))
.WillOnce(base::test::RunOnceClosure(sink_loop.QuitClosure()));
PushFrame(timestamp);
sink_loop.Run();
};
const auto& queue = source->QueueForTesting();
for (wtf_size_t i = 0; i < buffer_size; ++i) {
EXPECT_EQ(queue.size(), i);
base::TimeDelta timestamp = base::TimeDelta::FromSeconds(i);
push_frame_sync(timestamp);
EXPECT_EQ(queue.back()->timestamp(), timestamp);
EXPECT_EQ(queue.front()->timestamp(), base::TimeDelta::FromSeconds(0));
}
// Push another frame while the queue is full.
EXPECT_EQ(queue.size(), buffer_size);
push_frame_sync(base::TimeDelta::FromSeconds(buffer_size));
// Since the queue was full, the oldest frame from the queue should have been
// dropped.
EXPECT_EQ(queue.size(), buffer_size);
EXPECT_EQ(queue.back()->timestamp(),
base::TimeDelta::FromSeconds(buffer_size));
EXPECT_EQ(queue.front()->timestamp(), base::TimeDelta::FromSeconds(1));
// Pulling with frames in the queue should move the oldest frame in the queue
// to the stream's controller.
EXPECT_EQ(source->DesiredSizeForTesting(), 0);
EXPECT_FALSE(source->IsPendingPullForTesting());
source->pull(script_state);
EXPECT_EQ(source->DesiredSizeForTesting(), -1);
EXPECT_FALSE(source->IsPendingPullForTesting());
EXPECT_EQ(queue.size(), buffer_size - 1);
EXPECT_EQ(queue.front()->timestamp(), base::TimeDelta::FromSeconds(2));
source->Close();
EXPECT_EQ(queue.size(), 0u);
}
TEST_F(MediaStreamVideoTrackUnderlyingSourceTest,
BypassQueueAfterPullWithEmptyBuffer) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* source = CreateSource(script_state);
// Create a stream to ensure there is a controller associated to the source.
ReadableStream::CreateWithCountQueueingStrategy(script_state, source, 0);
MockMediaStreamVideoSink mock_sink;
mock_sink.ConnectToTrack(WebMediaStreamTrack(source->Track()));
auto push_frame_sync = [&mock_sink, this]() {
base::RunLoop sink_loop;
EXPECT_CALL(mock_sink, OnVideoFrame(_))
.WillOnce(base::test::RunOnceClosure(sink_loop.QuitClosure()));
PushFrame();
sink_loop.Run();
};
// At first, the queue is empty and the desired size is empty as well.
EXPECT_TRUE(source->QueueForTesting().empty());
EXPECT_EQ(source->DesiredSizeForTesting(), 0);
EXPECT_FALSE(source->IsPendingPullForTesting());
source->pull(script_state);
EXPECT_TRUE(source->QueueForTesting().empty());
EXPECT_EQ(source->DesiredSizeForTesting(), 0);
EXPECT_TRUE(source->IsPendingPullForTesting());
push_frame_sync();
// Since a pull was pending, the frame is put directly in the stream
// controller, bypassing the source queue.
EXPECT_TRUE(source->QueueForTesting().empty());
EXPECT_EQ(source->DesiredSizeForTesting(), -1);
EXPECT_FALSE(source->IsPendingPullForTesting());
source->Close();
}
TEST_F(MediaStreamVideoTrackUnderlyingSourceTest, QueueSizeCannotBeZero) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* source = CreateSource(script_state, 0u);
// Queue size is always at least 1, even if 0 is requested.
EXPECT_EQ(source->MaxQueueSize(), 1u);
source->Close();
}
} // namespace blink