blob: 27c41a70a3b55896b27e565de513502b1d490824 [file] [log] [blame]
// Copyright 2021 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_audio_track_underlying_sink.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/gmock_callback_support.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/modules/mediastream/web_media_stream_audio_sink.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/to_v8_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_frame.h"
#include "third_party/blink/renderer/core/streams/writable_stream.h"
#include "third_party/blink/renderer/core/streams/writable_stream_default_writer.h"
#include "third_party/blink/renderer/modules/mediastream/mock_media_stream_audio_sink.h"
#include "third_party/blink/renderer/modules/mediastream/pushable_media_stream_audio_source.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/to_v8.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_source.h"
#include "third_party/blink/renderer/platform/testing/io_task_runner_testing_platform_support.h"
using testing::_;
using testing::StrictMock;
namespace blink {
class MediaStreamAudioTrackUnderlyingSinkTest : public testing::Test {
public:
MediaStreamAudioTrackUnderlyingSinkTest() {
// Use the IO thread for testing purposes, instead of an audio task runner.
pushable_audio_source_ = new PushableMediaStreamAudioSource(
Thread::MainThread()->GetTaskRunner(),
Platform::Current()->GetIOTaskRunner());
media_stream_source_ = MakeGarbageCollected<MediaStreamSource>(
"dummy_source_id", MediaStreamSource::kTypeAudio, "dummy_source_name",
/*remote=*/false);
media_stream_source_->SetPlatformSource(
base::WrapUnique(pushable_audio_source_));
}
~MediaStreamAudioTrackUnderlyingSinkTest() override {
platform_->RunUntilIdle();
WebHeap::CollectAllGarbageForTesting();
}
MediaStreamAudioTrackUnderlyingSink* CreateUnderlyingSink(
ScriptState* script_state) {
return MakeGarbageCollected<MediaStreamAudioTrackUnderlyingSink>(
pushable_audio_source_);
}
void CreateTrackAndConnectToSource() {
media_stream_component_ = MakeGarbageCollected<MediaStreamComponent>(
media_stream_source_->Id(), media_stream_source_);
pushable_audio_source_->ConnectToTrack(media_stream_component_);
}
ScriptValue CreateAudioFrame(ScriptState* script_state,
AudioFrame** audio_frame_out = nullptr) {
const scoped_refptr<media::AudioBuffer> media_buffer =
media::AudioBuffer::CreateEmptyBuffer(
media::ChannelLayout::CHANNEL_LAYOUT_STEREO,
/*channel_count=*/2,
/*sample_rate=*/44100,
/*frame_count=*/500, base::TimeDelta());
AudioFrame* audio_frame =
MakeGarbageCollected<AudioFrame>(std::move(media_buffer));
if (audio_frame_out)
*audio_frame_out = audio_frame;
return ScriptValue(script_state->GetIsolate(),
ToV8(audio_frame, script_state->GetContext()->Global(),
script_state->GetIsolate()));
}
protected:
ScopedTestingPlatformSupport<IOTaskRunnerTestingPlatformSupport> platform_;
Persistent<MediaStreamSource> media_stream_source_;
Persistent<MediaStreamComponent> media_stream_component_;
PushableMediaStreamAudioSource* pushable_audio_source_;
};
TEST_F(MediaStreamAudioTrackUnderlyingSinkTest,
WriteToStreamForwardsToMediaStreamSink) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* underlying_sink = CreateUnderlyingSink(script_state);
auto* writable_stream = WritableStream::CreateWithCountQueueingStrategy(
script_state, underlying_sink, 1u);
CreateTrackAndConnectToSource();
base::RunLoop write_loop;
StrictMock<MockMediaStreamAudioSink> mock_sink;
EXPECT_CALL(mock_sink, OnSetFormat(_)).Times(::testing::AnyNumber());
EXPECT_CALL(mock_sink, OnData(_, _))
.WillOnce(base::test::RunOnceClosure(write_loop.QuitClosure()));
WebMediaStreamAudioSink::AddToAudioTrack(
&mock_sink, WebMediaStreamTrack(media_stream_component_.Get()));
NonThrowableExceptionState exception_state;
auto* writer = writable_stream->getWriter(script_state, exception_state);
AudioFrame* audio_frame = nullptr;
auto audio_frame_chunk = CreateAudioFrame(script_state, &audio_frame);
EXPECT_NE(audio_frame, nullptr);
EXPECT_NE(audio_frame->buffer(), nullptr);
ScriptPromiseTester write_tester(
script_state,
writer->write(script_state, audio_frame_chunk, exception_state));
// |audio_frame| should be invalidated after sending it to the sink.
write_tester.WaitUntilSettled();
EXPECT_EQ(audio_frame->buffer(), nullptr);
write_loop.Run();
writer->releaseLock(script_state);
ScriptPromiseTester close_tester(
script_state, writable_stream->close(script_state, exception_state));
close_tester.WaitUntilSettled();
// Writing to the sink after the stream closes should fail.
DummyExceptionStateForTesting dummy_exception_state;
underlying_sink->write(script_state, CreateAudioFrame(script_state), nullptr,
dummy_exception_state);
EXPECT_TRUE(dummy_exception_state.HadException());
EXPECT_EQ(dummy_exception_state.Code(),
static_cast<ExceptionCode>(DOMExceptionCode::kInvalidStateError));
WebMediaStreamAudioSink::RemoveFromAudioTrack(
&mock_sink, WebMediaStreamTrack(media_stream_component_.Get()));
}
TEST_F(MediaStreamAudioTrackUnderlyingSinkTest, WriteInvalidDataFails) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* sink = CreateUnderlyingSink(script_state);
ScriptValue v8_integer = ScriptValue::From(script_state, 0);
// Writing something that is not an AudioFrame to the sink should fail.
DummyExceptionStateForTesting dummy_exception_state;
sink->write(script_state, v8_integer, nullptr, dummy_exception_state);
EXPECT_TRUE(dummy_exception_state.HadException());
// Writing a null value to the sink should fail.
dummy_exception_state.ClearException();
EXPECT_FALSE(dummy_exception_state.HadException());
sink->write(script_state, ScriptValue::CreateNull(v8_scope.GetIsolate()),
nullptr, dummy_exception_state);
EXPECT_TRUE(dummy_exception_state.HadException());
// Writing a closed AudioFrame to the sink should fail.
dummy_exception_state.ClearException();
AudioFrame* audio_frame = nullptr;
auto chunk = CreateAudioFrame(script_state, &audio_frame);
audio_frame->close();
EXPECT_FALSE(dummy_exception_state.HadException());
sink->write(script_state, chunk, nullptr, dummy_exception_state);
EXPECT_TRUE(dummy_exception_state.HadException());
}
TEST_F(MediaStreamAudioTrackUnderlyingSinkTest, WriteToAbortedSinkFails) {
V8TestingScope v8_scope;
ScriptState* script_state = v8_scope.GetScriptState();
auto* underlying_sink = CreateUnderlyingSink(script_state);
auto* writable_stream = WritableStream::CreateWithCountQueueingStrategy(
script_state, underlying_sink, 1u);
NonThrowableExceptionState exception_state;
ScriptPromiseTester abort_tester(
script_state, writable_stream->abort(script_state, exception_state));
abort_tester.WaitUntilSettled();
// Writing to the sink after the stream closes should fail.
DummyExceptionStateForTesting dummy_exception_state;
underlying_sink->write(script_state, CreateAudioFrame(script_state), nullptr,
dummy_exception_state);
EXPECT_TRUE(dummy_exception_state.HadException());
EXPECT_EQ(dummy_exception_state.Code(),
static_cast<ExceptionCode>(DOMExceptionCode::kInvalidStateError));
}
} // namespace blink