| // Copyright 2023 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 "starboard/nplb/player_test_fixture.h" |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include "starboard/common/string.h" |
| #include "starboard/nplb/drm_helpers.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace starboard { |
| namespace nplb { |
| |
| using shared::starboard::player::video_dmp::VideoDmpReader; |
| using testing::FakeGraphicsContextProvider; |
| |
| using GroupedSamples = SbPlayerTestFixture::GroupedSamples; |
| using AudioSamplesDescriptor = GroupedSamples::AudioSamplesDescriptor; |
| using VideoSamplesDescriptor = GroupedSamples::VideoSamplesDescriptor; |
| |
| // TODO: Refine the implementation. |
| class SbPlayerTestFixture::GroupedSamplesIterator { |
| public: |
| explicit GroupedSamplesIterator(const GroupedSamples& grouped_samples) |
| : grouped_samples_(grouped_samples) {} |
| |
| bool HasMoreAudio() const { |
| return audio_samples_index_ < grouped_samples_.audio_samples_.size(); |
| } |
| |
| bool HasMoreVideo() const { |
| return video_samples_index_ < grouped_samples_.video_samples_.size(); |
| } |
| |
| AudioSamplesDescriptor GetCurrentAudioSamplesToWrite() const { |
| SB_DCHECK(HasMoreAudio()); |
| AudioSamplesDescriptor descriptor = |
| grouped_samples_.audio_samples_[audio_samples_index_]; |
| descriptor.start_index += current_written_audio_samples_; |
| descriptor.samples_count -= current_written_audio_samples_; |
| return descriptor; |
| } |
| |
| VideoSamplesDescriptor GetCurrentVideoSamplesToWrite() const { |
| SB_DCHECK(HasMoreVideo()); |
| VideoSamplesDescriptor descriptor = |
| grouped_samples_.video_samples_[video_samples_index_]; |
| descriptor.start_index += current_written_video_samples_; |
| descriptor.samples_count -= current_written_video_samples_; |
| return descriptor; |
| } |
| |
| void AdvanceAudio(int samples_count) { |
| SB_DCHECK(HasMoreAudio()); |
| if (grouped_samples_.audio_samples_[audio_samples_index_] |
| .is_end_of_stream) { |
| // For EOS, |samples_count| must be 1. |
| SB_DCHECK(samples_count == 1); |
| SB_DCHECK(current_written_audio_samples_ == 0); |
| audio_samples_index_++; |
| return; |
| } |
| |
| SB_DCHECK( |
| current_written_audio_samples_ + samples_count <= |
| grouped_samples_.audio_samples_[audio_samples_index_].samples_count); |
| |
| current_written_audio_samples_ += samples_count; |
| if (current_written_audio_samples_ == |
| grouped_samples_.audio_samples_[audio_samples_index_].samples_count) { |
| audio_samples_index_++; |
| current_written_audio_samples_ = 0; |
| } |
| } |
| |
| void AdvanceVideo(int samples_count) { |
| SB_DCHECK(HasMoreVideo()); |
| if (grouped_samples_.video_samples_[video_samples_index_] |
| .is_end_of_stream) { |
| // For EOS, |samples_count| must be 1. |
| SB_DCHECK(samples_count == 1); |
| SB_DCHECK(current_written_video_samples_ == 0); |
| video_samples_index_++; |
| return; |
| } |
| |
| SB_DCHECK( |
| current_written_video_samples_ + samples_count <= |
| grouped_samples_.video_samples_[video_samples_index_].samples_count); |
| |
| current_written_video_samples_ += samples_count; |
| if (current_written_video_samples_ == |
| grouped_samples_.video_samples_[video_samples_index_].samples_count) { |
| video_samples_index_++; |
| current_written_video_samples_ = 0; |
| } |
| } |
| |
| private: |
| const GroupedSamples& grouped_samples_; |
| int audio_samples_index_ = 0; |
| int current_written_audio_samples_ = 0; |
| int video_samples_index_ = 0; |
| int current_written_video_samples_ = 0; |
| }; |
| |
| GroupedSamples& GroupedSamples::AddAudioSamples(int start_index, |
| int number_of_samples) { |
| AddAudioSamples(start_index, number_of_samples, 0, 0, 0); |
| return *this; |
| } |
| |
| GroupedSamples& GroupedSamples::AddAudioSamples( |
| int start_index, |
| int number_of_samples, |
| SbTime timestamp_offset, |
| SbTime discarded_duration_from_front, |
| SbTime discarded_duration_from_back) { |
| SB_DCHECK(start_index >= 0); |
| SB_DCHECK(number_of_samples >= 0); |
| SB_DCHECK(audio_samples_.empty() || !audio_samples_.back().is_end_of_stream); |
| // Currently, the implementation only supports writing one sample at a time |
| // if |discarded_duration_from_front| or |discarded_duration_from_back| is not |
| // 0. |
| SB_DCHECK(discarded_duration_from_front == 0 || number_of_samples == 1); |
| SB_DCHECK(discarded_duration_from_back == 0 || number_of_samples == 1); |
| |
| AudioSamplesDescriptor descriptor; |
| descriptor.start_index = start_index; |
| descriptor.samples_count = number_of_samples; |
| descriptor.timestamp_offset = timestamp_offset; |
| descriptor.discarded_duration_from_front = discarded_duration_from_front; |
| descriptor.discarded_duration_from_back = discarded_duration_from_back; |
| audio_samples_.push_back(descriptor); |
| |
| return *this; |
| } |
| |
| GroupedSamples& GroupedSamples::AddAudioEOS() { |
| SB_DCHECK(audio_samples_.empty() || !audio_samples_.back().is_end_of_stream); |
| |
| AudioSamplesDescriptor descriptor; |
| descriptor.is_end_of_stream = true; |
| audio_samples_.push_back(descriptor); |
| |
| return *this; |
| } |
| |
| GroupedSamples& GroupedSamples::AddVideoSamples(int start_index, |
| int number_of_samples) { |
| SB_DCHECK(start_index >= 0); |
| SB_DCHECK(number_of_samples >= 0); |
| SB_DCHECK(video_samples_.empty() || !video_samples_.back().is_end_of_stream); |
| |
| VideoSamplesDescriptor descriptor; |
| descriptor.start_index = start_index; |
| descriptor.samples_count = number_of_samples; |
| video_samples_.push_back(descriptor); |
| |
| return *this; |
| } |
| |
| GroupedSamples& GroupedSamples::AddVideoEOS() { |
| SB_DCHECK(video_samples_.empty() || !video_samples_.back().is_end_of_stream); |
| |
| VideoSamplesDescriptor descriptor; |
| descriptor.is_end_of_stream = true; |
| video_samples_.push_back(descriptor); |
| |
| return *this; |
| } |
| |
| SbPlayerTestFixture::CallbackEvent::CallbackEvent() : event_type(kEmptyEvent) {} |
| |
| SbPlayerTestFixture::CallbackEvent::CallbackEvent(SbPlayer player, |
| SbMediaType type, |
| SbPlayerDecoderState state, |
| int ticket) |
| : event_type(kDecoderStateEvent), |
| player(player), |
| media_type(type), |
| decoder_state(state), |
| ticket(ticket) {} |
| |
| SbPlayerTestFixture::CallbackEvent::CallbackEvent(SbPlayer player, |
| SbPlayerState state, |
| int ticket) |
| : event_type(kPlayerStateEvent), |
| player(player), |
| player_state(state), |
| ticket(ticket) {} |
| |
| SbPlayerTestFixture::SbPlayerTestFixture( |
| const SbPlayerTestConfig& config, |
| FakeGraphicsContextProvider* fake_graphics_context_provider) |
| : output_mode_(config.output_mode), |
| key_system_(config.key_system), |
| max_video_capabilities_(config.max_video_capabilities), |
| fake_graphics_context_provider_(fake_graphics_context_provider) { |
| SB_DCHECK(output_mode_ == kSbPlayerOutputModeDecodeToTexture || |
| output_mode_ == kSbPlayerOutputModePunchOut); |
| |
| const char* audio_dmp_filename = config.audio_filename; |
| const char* video_dmp_filename = config.video_filename; |
| |
| if (audio_dmp_filename && strlen(audio_dmp_filename) > 0) { |
| audio_dmp_reader_.reset(new VideoDmpReader( |
| audio_dmp_filename, VideoDmpReader::kEnableReadOnDemand)); |
| } |
| if (video_dmp_filename && strlen(video_dmp_filename) > 0) { |
| video_dmp_reader_.reset(new VideoDmpReader( |
| video_dmp_filename, VideoDmpReader::kEnableReadOnDemand)); |
| } |
| |
| Initialize(); |
| } |
| |
| SbPlayerTestFixture::~SbPlayerTestFixture() { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| TearDown(); |
| } |
| |
| void SbPlayerTestFixture::Seek(const SbTime time) { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| SB_DCHECK(SbPlayerIsValid(player_)); |
| |
| ASSERT_FALSE(error_occurred_); |
| ASSERT_FALSE(HasReceivedPlayerState(kSbPlayerStateDestroyed)); |
| ASSERT_TRUE(HasReceivedPlayerState(kSbPlayerStateInitialized)); |
| |
| can_accept_more_audio_data_ = false; |
| can_accept_more_video_data_ = false; |
| player_state_set_.clear(); |
| player_state_set_.insert(kSbPlayerStateInitialized); |
| audio_end_of_stream_written_ = false; |
| video_end_of_stream_written_ = false; |
| |
| #if SB_API_VERSION >= 15 |
| SbPlayerSeek(player_, time, ++ticket_); |
| #else // SB_API_VERSION >= 15 |
| SbPlayerSeek2(player_, time, ++ticket_); |
| #endif // SB_API_VERSION >= 15 |
| } |
| |
| void SbPlayerTestFixture::Write(const GroupedSamples& grouped_samples) { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| SB_DCHECK(SbPlayerIsValid(player_)); |
| SB_DCHECK(!audio_end_of_stream_written_); |
| SB_DCHECK(!video_end_of_stream_written_); |
| |
| ASSERT_FALSE(error_occurred_); |
| |
| int max_audio_samples_per_write = |
| SbPlayerGetMaximumNumberOfSamplesPerWrite(player_, kSbMediaTypeAudio); |
| int max_video_samples_per_write = |
| SbPlayerGetMaximumNumberOfSamplesPerWrite(player_, kSbMediaTypeVideo); |
| |
| GroupedSamplesIterator iterator(grouped_samples); |
| SB_DCHECK(!iterator.HasMoreAudio() || audio_dmp_reader_); |
| SB_DCHECK(!iterator.HasMoreVideo() || video_dmp_reader_); |
| |
| const SbTime kDefaultWriteTimeout = kSbTimeSecond * 5; |
| |
| SbTimeMonotonic start = SbTimeGetMonotonicNow(); |
| while (SbTimeGetMonotonicNow() - start < kDefaultWriteTimeout) { |
| if (CanWriteMoreAudioData() && iterator.HasMoreAudio()) { |
| auto descriptor = iterator.GetCurrentAudioSamplesToWrite(); |
| if (descriptor.is_end_of_stream) { |
| SB_DCHECK(!audio_end_of_stream_written_); |
| ASSERT_NO_FATAL_FAILURE(WriteEndOfStream(kSbMediaTypeAudio)); |
| iterator.AdvanceAudio(1); |
| } else { |
| SB_DCHECK(descriptor.samples_count > 0); |
| SB_DCHECK(descriptor.start_index + descriptor.samples_count < |
| audio_dmp_reader_->number_of_audio_buffers()) |
| << "Audio dmp file is not long enough to finish the test."; |
| |
| auto samples_to_write = |
| std::min(max_audio_samples_per_write, descriptor.samples_count); |
| ASSERT_NO_FATAL_FAILURE( |
| WriteAudioSamples(descriptor.start_index, samples_to_write, |
| descriptor.timestamp_offset, |
| descriptor.discarded_duration_from_front, |
| descriptor.discarded_duration_from_back)); |
| iterator.AdvanceAudio(samples_to_write); |
| } |
| } |
| if (CanWriteMoreVideoData() && iterator.HasMoreVideo()) { |
| auto descriptor = iterator.GetCurrentVideoSamplesToWrite(); |
| if (descriptor.is_end_of_stream) { |
| SB_DCHECK(!video_end_of_stream_written_); |
| ASSERT_NO_FATAL_FAILURE(WriteEndOfStream(kSbMediaTypeVideo)); |
| iterator.AdvanceVideo(1); |
| } else { |
| SB_DCHECK(descriptor.samples_count > 0); |
| SB_DCHECK(descriptor.start_index + descriptor.samples_count < |
| video_dmp_reader_->number_of_video_buffers()) |
| << "Video dmp file is not long enough to finish the test."; |
| |
| auto samples_to_write = |
| std::min(max_video_samples_per_write, descriptor.samples_count); |
| ASSERT_NO_FATAL_FAILURE( |
| WriteVideoSamples(descriptor.start_index, samples_to_write)); |
| iterator.AdvanceVideo(samples_to_write); |
| } |
| } |
| |
| if (iterator.HasMoreAudio() || iterator.HasMoreVideo()) { |
| ASSERT_NO_FATAL_FAILURE(WaitForDecoderStateNeedsData()); |
| } else { |
| return; |
| } |
| } |
| |
| FAIL() << "Failed to write all samples."; |
| } |
| |
| void SbPlayerTestFixture::WaitForPlayerPresenting() { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| SB_DCHECK(SbPlayerIsValid(player_)); |
| |
| ASSERT_FALSE(error_occurred_); |
| ASSERT_NO_FATAL_FAILURE(WaitForPlayerState(kSbPlayerStatePresenting)); |
| } |
| |
| void SbPlayerTestFixture::WaitForPlayerEndOfStream() { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| SB_DCHECK(SbPlayerIsValid(player_)); |
| SB_DCHECK(!audio_dmp_reader_ || audio_end_of_stream_written_); |
| SB_DCHECK(!video_dmp_reader_ || video_end_of_stream_written_); |
| |
| ASSERT_FALSE(error_occurred_); |
| ASSERT_NO_FATAL_FAILURE(WaitForPlayerState(kSbPlayerStateEndOfStream)); |
| } |
| |
| SbTime SbPlayerTestFixture::GetCurrentMediaTime() const { |
| #if SB_API_VERSION >= 15 |
| SbPlayerInfo info = {}; |
| SbPlayerGetInfo(player_, &info); |
| #else // SB_API_VERSION >= 15 |
| SbPlayerInfo2 info = {}; |
| SbPlayerGetInfo2(player_, &info); |
| #endif // SB_API_VERSION >= 15 |
| return info.current_media_timestamp; |
| } |
| |
| void SbPlayerTestFixture::SetAudioWriteDuration(SbTime duration) { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| SB_DCHECK(duration > 0); |
| audio_write_duration_ = duration; |
| } |
| |
| SbTime SbPlayerTestFixture::GetAudioSampleTimestamp(int index) const { |
| SB_DCHECK(HasAudio()); |
| SB_DCHECK(index < audio_dmp_reader_->number_of_audio_buffers()); |
| return audio_dmp_reader_->GetPlayerSampleInfo(kSbMediaTypeAudio, index) |
| .timestamp; |
| } |
| |
| int SbPlayerTestFixture::ConvertDurationToAudioBufferCount( |
| SbTime duration) const { |
| SB_DCHECK(HasAudio()); |
| SB_DCHECK(audio_dmp_reader_->number_of_audio_buffers()); |
| return duration * audio_dmp_reader_->number_of_audio_buffers() / |
| audio_dmp_reader_->audio_duration(); |
| } |
| |
| int SbPlayerTestFixture::ConvertDurationToVideoBufferCount( |
| SbTime duration) const { |
| SB_DCHECK(HasVideo()); |
| SB_DCHECK(video_dmp_reader_->number_of_video_buffers()); |
| return duration * video_dmp_reader_->number_of_video_buffers() / |
| video_dmp_reader_->video_duration(); |
| } |
| |
| // static |
| void SbPlayerTestFixture::DecoderStatusCallback(SbPlayer player, |
| void* context, |
| SbMediaType type, |
| SbPlayerDecoderState state, |
| int ticket) { |
| auto fixture = static_cast<SbPlayerTestFixture*>(context); |
| fixture->OnDecoderState(player, type, state, ticket); |
| } |
| |
| // static |
| void SbPlayerTestFixture::PlayerStatusCallback(SbPlayer player, |
| void* context, |
| SbPlayerState state, |
| int ticket) { |
| auto fixture = static_cast<SbPlayerTestFixture*>(context); |
| fixture->OnPlayerState(player, state, ticket); |
| } |
| |
| // static |
| void SbPlayerTestFixture::ErrorCallback(SbPlayer player, |
| void* context, |
| SbPlayerError error, |
| const char* message) { |
| auto fixture = static_cast<SbPlayerTestFixture*>(context); |
| fixture->OnError(player, error, message); |
| } |
| |
| void SbPlayerTestFixture::OnDecoderState(SbPlayer player, |
| SbMediaType media_type, |
| SbPlayerDecoderState state, |
| int ticket) { |
| callback_event_queue_.Put(CallbackEvent(player, media_type, state, ticket)); |
| } |
| |
| void SbPlayerTestFixture::OnPlayerState(SbPlayer player, |
| SbPlayerState state, |
| int ticket) { |
| callback_event_queue_.Put(CallbackEvent(player, state, ticket)); |
| } |
| |
| void SbPlayerTestFixture::OnError(SbPlayer player, |
| SbPlayerError error, |
| const char* message) { |
| SB_LOG(ERROR) << FormatString("Got SbPlayerError %d with message '%s'", error, |
| message != NULL ? message : ""); |
| error_occurred_ = true; |
| } |
| |
| void SbPlayerTestFixture::Initialize() { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| // Initialize drm system. |
| if (!key_system_.empty()) { |
| drm_system_ = SbDrmCreateSystem( |
| key_system_.c_str(), NULL /* context */, DummySessionUpdateRequestFunc, |
| DummySessionUpdatedFunc, DummySessionKeyStatusesChangedFunc, |
| DummyServerCertificateUpdatedFunc, DummySessionClosedFunc); |
| ASSERT_TRUE(SbDrmSystemIsValid(drm_system_)); |
| } |
| |
| // Initialize player. |
| auto audio_codec = kSbMediaAudioCodecNone; |
| auto video_codec = kSbMediaVideoCodecNone; |
| const shared::starboard::media::AudioStreamInfo* audio_stream_info = NULL; |
| |
| if (audio_dmp_reader_) { |
| audio_codec = audio_dmp_reader_->audio_codec(); |
| audio_stream_info = &audio_dmp_reader_->audio_stream_info(); |
| } |
| if (video_dmp_reader_) { |
| video_codec = video_dmp_reader_->video_codec(); |
| } |
| |
| // TODO: refine CallSbPlayerCreate() to use real video sample info. |
| player_ = CallSbPlayerCreate( |
| fake_graphics_context_provider_->window(), video_codec, audio_codec, |
| drm_system_, audio_stream_info, max_video_capabilities_.c_str(), |
| DummyDeallocateSampleFunc, DecoderStatusCallback, PlayerStatusCallback, |
| ErrorCallback, this, output_mode_, |
| fake_graphics_context_provider_->decoder_target_provider()); |
| ASSERT_TRUE(SbPlayerIsValid(player_)); |
| ASSERT_NO_FATAL_FAILURE(WaitForPlayerState(kSbPlayerStateInitialized)); |
| ASSERT_NO_FATAL_FAILURE(Seek(0)); |
| SbPlayerSetPlaybackRate(player_, 1.0); |
| SbPlayerSetVolume(player_, 1.0); |
| } |
| |
| void SbPlayerTestFixture::TearDown() { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| // We should always destroy |player_| and |drm_system_|, no matter if there's |
| // any unexpected player error. |
| if (SbPlayerIsValid(player_)) { |
| destroy_player_called_ = true; |
| SbPlayerDestroy(player_); |
| } |
| if (SbDrmSystemIsValid(drm_system_)) { |
| SbDrmDestroySystem(drm_system_); |
| } |
| |
| // We expect player resources are released and all events are sent already |
| // after SbPlayerDestroy() finishes. |
| while (callback_event_queue_.Size() > 0) { |
| ASSERT_NO_FATAL_FAILURE(WaitAndProcessNextEvent()); |
| } |
| ASSERT_TRUE(HasReceivedPlayerState(kSbPlayerStateDestroyed)); |
| ASSERT_FALSE(error_occurred_); |
| |
| player_ = kSbPlayerInvalid; |
| drm_system_ = kSbDrmSystemInvalid; |
| } |
| |
| bool SbPlayerTestFixture::CanWriteMoreAudioData() { |
| if (!can_accept_more_audio_data_) { |
| return false; |
| } |
| |
| if (!audio_write_duration_) { |
| return true; |
| } |
| |
| return last_written_audio_timestamp_ - GetCurrentMediaTime() < |
| audio_write_duration_; |
| } |
| |
| bool SbPlayerTestFixture::CanWriteMoreVideoData() { |
| return can_accept_more_video_data_; |
| } |
| |
| void SbPlayerTestFixture::WriteAudioSamples( |
| int start_index, |
| int samples_to_write, |
| SbTime timestamp_offset, |
| SbTime discarded_duration_from_front, |
| SbTime discarded_duration_from_back) { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| SB_DCHECK(SbPlayerIsValid(player_)); |
| SB_DCHECK(audio_dmp_reader_); |
| SB_DCHECK(start_index >= 0); |
| SB_DCHECK(samples_to_write > 0); |
| SB_DCHECK(samples_to_write <= SbPlayerGetMaximumNumberOfSamplesPerWrite( |
| player_, kSbMediaTypeAudio)); |
| SB_DCHECK(start_index + samples_to_write + 1 < |
| audio_dmp_reader_->number_of_audio_buffers()); |
| SB_DCHECK(discarded_duration_from_front == 0 || samples_to_write == 1); |
| SB_DCHECK(discarded_duration_from_back == 0 || samples_to_write == 1); |
| |
| CallSbPlayerWriteSamples( |
| player_, kSbMediaTypeAudio, audio_dmp_reader_.get(), start_index, |
| samples_to_write, timestamp_offset, |
| std::vector<SbTime>(samples_to_write, discarded_duration_from_front), |
| std::vector<SbTime>(samples_to_write, discarded_duration_from_back)); |
| |
| last_written_audio_timestamp_ = |
| audio_dmp_reader_ |
| ->GetPlayerSampleInfo(kSbMediaTypeAudio, |
| start_index + samples_to_write) |
| .timestamp; |
| |
| can_accept_more_audio_data_ = false; |
| } |
| |
| void SbPlayerTestFixture::WriteVideoSamples(int start_index, |
| int samples_to_write) { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| SB_DCHECK(start_index >= 0); |
| SB_DCHECK(samples_to_write > 0); |
| SB_DCHECK(SbPlayerIsValid(player_)); |
| SB_DCHECK(samples_to_write <= SbPlayerGetMaximumNumberOfSamplesPerWrite( |
| player_, kSbMediaTypeVideo)); |
| SB_DCHECK(video_dmp_reader_); |
| SB_DCHECK(start_index + samples_to_write < |
| video_dmp_reader_->number_of_video_buffers()); |
| |
| CallSbPlayerWriteSamples(player_, kSbMediaTypeVideo, video_dmp_reader_.get(), |
| start_index, samples_to_write); |
| can_accept_more_video_data_ = false; |
| } |
| |
| void SbPlayerTestFixture::WriteEndOfStream(SbMediaType media_type) { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| SB_DCHECK(SbPlayerIsValid(player_)); |
| |
| if (media_type == kSbMediaTypeAudio) { |
| SB_DCHECK(audio_dmp_reader_); |
| SB_DCHECK(!audio_end_of_stream_written_); |
| SbPlayerWriteEndOfStream(player_, kSbMediaTypeAudio); |
| can_accept_more_audio_data_ = false; |
| audio_end_of_stream_written_ = true; |
| } else { |
| SB_DCHECK(media_type == kSbMediaTypeVideo); |
| SB_DCHECK(video_dmp_reader_); |
| SB_DCHECK(!video_end_of_stream_written_); |
| SbPlayerWriteEndOfStream(player_, kSbMediaTypeVideo); |
| can_accept_more_video_data_ = false; |
| video_end_of_stream_written_ = true; |
| } |
| } |
| |
| void SbPlayerTestFixture::WaitAndProcessNextEvent(SbTime timeout) { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| auto event = callback_event_queue_.GetTimed(timeout); |
| |
| // Ignore callback events for previous Seek(). |
| if (event.ticket != ticket_) { |
| return; |
| } |
| |
| switch (event.event_type) { |
| case CallbackEventType::kEmptyEvent: |
| break; |
| case CallbackEventType::kDecoderStateEvent: { |
| ASSERT_EQ(event.player, player_); |
| // Callbacks may be in-flight at the time that the player is destroyed by |
| // a call to |SbPlayerDestroy|. In this case, the callbacks are ignored. |
| // However no new callbacks are expected after receiving the player status |
| // |kSbPlayerStateDestroyed|. |
| ASSERT_FALSE(HasReceivedPlayerState(kSbPlayerStateDestroyed)); |
| // There's only one valid SbPlayerDecoderState. The received decoder state |
| // must be kSbPlayerDecoderStateNeedsData. |
| ASSERT_EQ(event.decoder_state, kSbPlayerDecoderStateNeedsData); |
| if (event.media_type == kSbMediaTypeAudio) { |
| ASSERT_FALSE(can_accept_more_audio_data_); |
| can_accept_more_audio_data_ = true; |
| } else { |
| ASSERT_TRUE(event.media_type == kSbMediaTypeVideo); |
| ASSERT_FALSE(can_accept_more_video_data_); |
| can_accept_more_video_data_ = true; |
| } |
| break; |
| } |
| case CallbackEventType::kPlayerStateEvent: { |
| ASSERT_EQ(event.player, player_); |
| ASSERT_NO_FATAL_FAILURE(AssertPlayerStateIsValid(event.player_state)); |
| player_state_set_.insert(event.player_state); |
| break; |
| } |
| } |
| ASSERT_FALSE(error_occurred_); |
| } |
| |
| void SbPlayerTestFixture::WaitForDecoderStateNeedsData(const SbTime timeout) { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| bool old_can_accept_more_audio_data = can_accept_more_audio_data_; |
| bool old_can_accept_more_video_data = can_accept_more_video_data_; |
| |
| SbTimeMonotonic start = SbTimeGetMonotonicNow(); |
| do { |
| ASSERT_FALSE(error_occurred_); |
| GetDecodeTargetWhenSupported(); |
| ASSERT_NO_FATAL_FAILURE(WaitAndProcessNextEvent()); |
| if (old_can_accept_more_audio_data != can_accept_more_audio_data_ || |
| old_can_accept_more_video_data != can_accept_more_video_data_) { |
| return; |
| } |
| } while (SbTimeGetMonotonicNow() - start < timeout); |
| } |
| |
| void SbPlayerTestFixture::WaitForPlayerState(const SbPlayerState desired_state, |
| const SbTime timeout) { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (HasReceivedPlayerState(desired_state)) { |
| return; |
| } |
| SbTimeMonotonic start = SbTimeGetMonotonicNow(); |
| do { |
| ASSERT_FALSE(error_occurred_); |
| ASSERT_NO_FATAL_FAILURE(GetDecodeTargetWhenSupported()); |
| ASSERT_NO_FATAL_FAILURE(WaitAndProcessNextEvent()); |
| if (HasReceivedPlayerState(desired_state)) { |
| return; |
| } |
| } while (SbTimeGetMonotonicNow() - start < timeout); |
| |
| FAIL() << "WaitForPlayerState() did not receive expected state."; |
| } |
| |
| void SbPlayerTestFixture::GetDecodeTargetWhenSupported() { |
| if (!SbPlayerIsValid(player_)) { |
| return; |
| } |
| #if SB_HAS(GLES2) |
| fake_graphics_context_provider_->RunOnGlesContextThread([&]() { |
| ASSERT_TRUE(SbPlayerIsValid(player_)); |
| if (output_mode_ != kSbPlayerOutputModeDecodeToTexture) { |
| ASSERT_EQ(SbPlayerGetCurrentFrame(player_), kSbDecodeTargetInvalid); |
| return; |
| } |
| ASSERT_EQ(output_mode_, kSbPlayerOutputModeDecodeToTexture); |
| SbDecodeTarget frame = SbPlayerGetCurrentFrame(player_); |
| if (SbDecodeTargetIsValid(frame)) { |
| SbDecodeTargetRelease(frame); |
| } |
| }); |
| #endif // SB_HAS(GLES2) |
| } |
| |
| void SbPlayerTestFixture::AssertPlayerStateIsValid(SbPlayerState state) const { |
| // Note: it is possible to receive the same state that has been previously |
| // received in the case of multiple Seek() calls. Prior to any Seek commands |
| // issued in this test, we should reset the |player_state_set_| member. |
| ASSERT_FALSE(HasReceivedPlayerState(state)); |
| |
| switch (state) { |
| case kSbPlayerStateInitialized: |
| // No other states have been received before getting Initialized. |
| ASSERT_TRUE(player_state_set_.empty()); |
| return; |
| case kSbPlayerStatePrerolling: |
| ASSERT_TRUE(HasReceivedPlayerState(kSbPlayerStateInitialized)); |
| ASSERT_FALSE(HasReceivedPlayerState(kSbPlayerStateDestroyed)); |
| return; |
| case kSbPlayerStatePresenting: |
| ASSERT_TRUE(HasReceivedPlayerState(kSbPlayerStateInitialized)); |
| ASSERT_TRUE(HasReceivedPlayerState(kSbPlayerStatePrerolling)); |
| ASSERT_FALSE(HasReceivedPlayerState(kSbPlayerStateDestroyed)); |
| return; |
| case kSbPlayerStateEndOfStream: |
| if (audio_dmp_reader_) { |
| ASSERT_TRUE(audio_end_of_stream_written_); |
| } |
| if (video_dmp_reader_) { |
| ASSERT_TRUE(video_end_of_stream_written_); |
| } |
| ASSERT_TRUE(HasReceivedPlayerState(kSbPlayerStateInitialized)); |
| ASSERT_TRUE(HasReceivedPlayerState(kSbPlayerStatePrerolling)); |
| ASSERT_FALSE(HasReceivedPlayerState(kSbPlayerStateDestroyed)); |
| return; |
| case kSbPlayerStateDestroyed: |
| // Nothing stops the user of the player from destroying the player during |
| // any of the previous states. |
| ASSERT_TRUE(destroy_player_called_); |
| return; |
| } |
| FAIL() << "Received an invalid SbPlayerState."; |
| } |
| |
| } // namespace nplb |
| } // namespace starboard |