diff --git a/packager/media/formats/webvtt/webvtt_pipeline_unittest.cc b/packager/media/formats/webvtt/webvtt_pipeline_unittest.cc index 3423e7ae43..565858891c 100644 --- a/packager/media/formats/webvtt/webvtt_pipeline_unittest.cc +++ b/packager/media/formats/webvtt/webvtt_pipeline_unittest.cc @@ -26,6 +26,7 @@ TEST(WebVttTextPipelineTest, SegmentedOutput) { const char* kOutput1 = "memory://output/template-1.vtt"; const char* kOutput2 = "memory://output/template-2.vtt"; const char* kOutput3 = "memory://output/template-3.vtt"; + const char* kOutput4 = "memory://output/template-4.vtt"; const char* kInputFile = "memory://input.vtt"; const char* kInput = @@ -53,8 +54,14 @@ TEST(WebVttTextPipelineTest, SegmentedOutput) { "Thank you.\n"; // Segment One - // 00:00:10.000 to 00:00:20.000 + // 00:00:00.000 to 00:00:10.000 const char* kExpectedOutput1 = + "WEBVTT\n" + "\n"; + + // Segment Two + // 00:00:10.000 to 00:00:20.000 + const char* kExpectedOutput2 = "WEBVTT\n" "\n" "1\n" @@ -62,9 +69,9 @@ TEST(WebVttTextPipelineTest, SegmentedOutput) { "This blade has a dark past.\n" "\n"; - // Segment Two + // Segment Three // 00:00:20.000 to 00:00:30.000 - const char* kExpectedOutput2 = + const char* kExpectedOutput3 = "WEBVTT\n" "\n" "1\n" @@ -80,9 +87,9 @@ TEST(WebVttTextPipelineTest, SegmentedOutput) { "You're a fool for traveling alone,\nso completely unprepared.\n" "\n"; - // Segment Three + // Segment Four // 00:00:30.000 to 00:00:40.000 - const char* kExpectedOutput3 = + const char* kExpectedOutput4 = "WEBVTT\n" "\n" "3\n" @@ -128,6 +135,7 @@ TEST(WebVttTextPipelineTest, SegmentedOutput) { ASSERT_FILE_STREQ(kOutput1, kExpectedOutput1); ASSERT_FILE_STREQ(kOutput2, kExpectedOutput2); ASSERT_FILE_STREQ(kOutput3, kExpectedOutput3); + ASSERT_FILE_STREQ(kOutput4, kExpectedOutput4); } } // namespace media } // namespace shaka diff --git a/packager/media/formats/webvtt/webvtt_segmenter.cc b/packager/media/formats/webvtt/webvtt_segmenter.cc index 1eb5e5458a..4d561f2721 100644 --- a/packager/media/formats/webvtt/webvtt_segmenter.cc +++ b/packager/media/formats/webvtt/webvtt_segmenter.cc @@ -10,7 +10,7 @@ namespace shaka { namespace media { namespace { const size_t kStreamIndex = 0; -} +} // namespace WebVttSegmenter::WebVttSegmenter(uint64_t segment_duration_ms) : segment_duration_ms_(segment_duration_ms) {} @@ -33,16 +33,18 @@ Status WebVttSegmenter::Process(std::unique_ptr stream_data) { } Status WebVttSegmenter::OnFlushRequest(size_t input_stream_index) { - while (segment_map_.size()) { - uint64_t segment = segment_map_.begin()->first; + // At this point we know that there is a single series of consecutive + // segments, all we need to do is run through all of them. + for (const auto& pair : segment_map_) { + Status status = DispatchSegmentWithSamples(pair.first, pair.second); - // OnSegmentEnd will remove the segment from the map. - Status status = OnSegmentEnd(segment); if (!status.ok()) { return status; } } + segment_map_.clear(); + return FlushAllDownstreams(); } @@ -66,58 +68,58 @@ Status WebVttSegmenter::OnTextSample(std::shared_ptr sample) { return Status::OK; } - // Now that we are accepting this new segment, its starting segment is our - // new head segment. - head_segment_ = start_segment; - // Add the sample to each segment it spans. for (uint64_t segment = start_segment; segment <= ending_segment; segment++) { segment_map_[segment].push_back(sample); } - // Output all the segments that come before the start of this cue's first - // segment. - while (segment_map_.size()) { - // Since the segments are in accending order, we can break out of the loop - // once we catch-up to the new samples starting segment. - const uint64_t segment = segment_map_.begin()->first; - if (segment >= start_segment) { - break; + // Move forward segment-by-segment so that we output empty segments to fill + // any segments with no cues. + for (uint64_t segment = head_segment_; segment < start_segment; segment++) { + auto it = segment_map_.find(segment); + + Status status; + if (it == segment_map_.end()) { + const WebVttSegmentSamples kNoSamples; + status.Update(DispatchSegmentWithSamples(segment, kNoSamples)); + } else { + // We found a segment, output all the samples. Remove it from the map as + // we should never need to write to it again. + status.Update(DispatchSegmentWithSamples(segment, it->second)); + segment_map_.erase(it); } - // Output the segment. If there is an error, there is no reason to continue. - Status status = OnSegmentEnd(segment); + // If we fail to output a single sample, just stop. if (!status.ok()) { return status; } } + // Jump ahead to the start of this segment as we should never have any samples + // start before |start_segment|. + head_segment_ = start_segment; + return Status::OK; } -Status WebVttSegmenter::OnSegmentEnd(uint64_t segment) { +Status WebVttSegmenter::DispatchSegmentWithSamples( + uint64_t segment, + const WebVttSegmentSamples& samples) { Status status; - - const auto found = segment_map_.find(segment); - if (found != segment_map_.end()) { - for (const auto& sample : found->second) { - status.Update(DispatchTextSample(kStreamIndex, sample)); - } - - // Stop storing the segment. - segment_map_.erase(found); + for (const auto& sample : samples) { + status.Update(DispatchTextSample(kStreamIndex, sample)); } - // Only send the segment info if all the samples were accepted. - if (status.ok()) { - std::shared_ptr info = std::make_shared(); - info->start_timestamp = segment * segment_duration_ms_; - info->duration = segment_duration_ms_; - - status.Update(DispatchSegmentInfo(kStreamIndex, std::move(info))); + // Only send the segment info if all the samples were successful. + if (!status.ok()) { + return status; } - return status; + std::shared_ptr info = std::make_shared(); + info->start_timestamp = segment * segment_duration_ms_; + info->duration = segment_duration_ms_; + + return DispatchSegmentInfo(kStreamIndex, std::move(info)); } } // namespace media } // namespace shaka diff --git a/packager/media/formats/webvtt/webvtt_segmenter.h b/packager/media/formats/webvtt/webvtt_segmenter.h index 97bd2578c9..cf6f0746bd 100644 --- a/packager/media/formats/webvtt/webvtt_segmenter.h +++ b/packager/media/formats/webvtt/webvtt_segmenter.h @@ -30,19 +30,20 @@ class WebVttSegmenter : public MediaHandler { WebVttSegmenter(const WebVttSegmenter&) = delete; WebVttSegmenter& operator=(const WebVttSegmenter&) = delete; + using WebVttSample = std::shared_ptr; + using WebVttSegmentSamples = std::vector; + Status InitializeInternal() override; Status OnTextSample(std::shared_ptr sample); - Status OnSegmentEnd(uint64_t segment); + Status DispatchSegmentWithSamples(uint64_t segment, + const WebVttSegmentSamples& samples); uint64_t segment_duration_ms_; - using WebVttSample = std::shared_ptr; - using WebVttSegment = std::vector; - // Mapping of segment number to segment. - std::map segment_map_; + std::map segment_map_; uint64_t head_segment_ = 0; }; diff --git a/packager/media/formats/webvtt/webvtt_segmenter_unittest.cc b/packager/media/formats/webvtt/webvtt_segmenter_unittest.cc index 731dcb4ef1..827e6762b7 100644 --- a/packager/media/formats/webvtt/webvtt_segmenter_unittest.cc +++ b/packager/media/formats/webvtt/webvtt_segmenter_unittest.cc @@ -142,9 +142,13 @@ TEST_F(WebVttSegmenterTest, CreatesSegmentsForCues) { // | | // | | [---B---] // | | -TEST_F(WebVttSegmenterTest, SkipsEmptySegments) { +TEST_F(WebVttSegmenterTest, OutputsEmptySegments) { const uint64_t kSampleDuration = kSegmentDuration / 2; + const int64_t kSegment1Start = kStartTime; + const int64_t kSegment2Start = kSegment1Start + kSegmentDuration; + const int64_t kSegment3Start = kSegment2Start + kSegmentDuration; + { testing::InSequence s; @@ -157,19 +161,25 @@ TEST_F(WebVttSegmenterTest, SkipsEmptySegments) { kNoSettings, kPayload[0]))); EXPECT_CALL( *Output(kOutputIndex), - OnProcess(IsSegmentInfo(kStreamIndex, kStartTimeSigned, - kSegmentDuration, !kSubSegment, !kEncrypted))); + OnProcess(IsSegmentInfo(kStreamIndex, kSegment1Start, kSegmentDuration, + !kSubSegment, !kEncrypted))); - // Segment Two + // Segment Two (empty segment) + EXPECT_CALL( + *Output(kOutputIndex), + OnProcess(IsSegmentInfo(kStreamIndex, kSegment2Start, kSegmentDuration, + !kSubSegment, !kEncrypted))); + + // Segment Three EXPECT_CALL(*Output(kOutputIndex), OnProcess(IsTextSample( kId[1], kStartTime + 2 * kSegmentDuration, kStartTime + 2 * kSegmentDuration + kSampleDuration, kNoSettings, kPayload[1]))); - EXPECT_CALL(*Output(kOutputIndex), - OnProcess(IsSegmentInfo( - kStreamIndex, kStartTimeSigned + 2 * kSegmentDuration, - kSegmentDuration, !kSubSegment, !kEncrypted))); + EXPECT_CALL( + *Output(kOutputIndex), + OnProcess(IsSegmentInfo(kStreamIndex, kSegment3Start, kSegmentDuration, + !kSubSegment, !kEncrypted))); EXPECT_CALL(*Output(kOutputIndex), OnFlush(kStreamIndex)); }