getStreamHeatmap() throws ParsingException {
+ return Collections.emptyList();
+ }
+
/**
* Meta information about the stream.
*
diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamHeatmapEntry.java b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamHeatmapEntry.java
new file mode 100644
index 0000000000..f8a49c4e31
--- /dev/null
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamHeatmapEntry.java
@@ -0,0 +1,49 @@
+package org.schabi.newpipe.extractor.stream;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+public class StreamHeatmapEntry implements Serializable {
+ private final long startTimeMillis;
+ private final long durationMillis;
+ private final double heatIntensity;
+
+ public StreamHeatmapEntry(final long startTimeMillis,
+ final long durationMillis,
+ final double heatIntensity) {
+ this.startTimeMillis = startTimeMillis;
+ this.durationMillis = durationMillis;
+ this.heatIntensity = heatIntensity;
+ }
+
+ public long getStartTimeMillis() {
+ return startTimeMillis;
+ }
+
+ public long getDurationMillis() {
+ return durationMillis;
+ }
+
+ public double getHeatIntensity() {
+ return heatIntensity;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final StreamHeatmapEntry that = (StreamHeatmapEntry) o;
+ return startTimeMillis == that.startTimeMillis
+ && durationMillis == that.durationMillis
+ && Double.compare(that.heatIntensity, heatIntensity) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(startTimeMillis, durationMillis, heatIntensity);
+ }
+}
diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java
index f2719dec08..5677f9c8eb 100644
--- a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java
@@ -336,6 +336,11 @@ private static void extractOptionalData(final StreamInfo streamInfo,
} catch (final Exception e) {
streamInfo.addError(e);
}
+ try {
+ streamInfo.setStreamHeatmap(extractor.getStreamHeatmap());
+ } catch (final Exception e) {
+ streamInfo.addError(e);
+ }
try {
streamInfo.setMetaInfo(extractor.getMetaInfo());
} catch (final Exception e) {
@@ -356,6 +361,12 @@ private static void extractOptionalData(final StreamInfo streamInfo,
} catch (final Exception e) {
streamInfo.addError(e);
}
+ try {
+ streamInfo.setLiveChat(extractor.hasLiveChat());
+ streamInfo.setLiveChatContinuation(extractor.getLiveChatContinuation());
+ } catch (final Exception e) {
+ streamInfo.addError(e);
+ }
streamInfo.setRelatedItems(ExtractorHelper.getRelatedItemsOrLogError(streamInfo,
extractor));
@@ -405,10 +416,13 @@ private static void extractOptionalData(final StreamInfo streamInfo,
private Locale language = null;
private List tags = List.of();
private List streamSegments = List.of();
+ private List streamHeatmap = List.of();
private List metaInfo = List.of();
private boolean shortFormContent = false;
@Nonnull
private ContentAvailability contentAvailability = ContentAvailability.AVAILABLE;
+ private boolean liveChat = false;
+ private String liveChatContinuation = null;
/**
* Preview frames, e.g. for the storyboard / seekbar thumbnail preview
@@ -731,6 +745,14 @@ public void setStreamSegments(final List streamSegments) {
this.streamSegments = streamSegments;
}
+ public List getStreamHeatmap() {
+ return streamHeatmap;
+ }
+
+ public void setStreamHeatmap(final List streamHeatmap) {
+ this.streamHeatmap = streamHeatmap;
+ }
+
public void setMetaInfo(final List metaInfo) {
this.metaInfo = metaInfo;
}
@@ -764,4 +786,20 @@ public ContentAvailability getContentAvailability() {
public void setContentAvailability(@Nonnull final ContentAvailability availability) {
this.contentAvailability = availability;
}
+
+ public boolean hasLiveChat() {
+ return liveChat;
+ }
+
+ public void setLiveChat(final boolean liveChat) {
+ this.liveChat = liveChat;
+ }
+
+ public String getLiveChatContinuation() {
+ return liveChatContinuation;
+ }
+
+ public void setLiveChatContinuation(final String liveChatContinuation) {
+ this.liveChatContinuation = liveChatContinuation;
+ }
}
diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/DefaultStreamExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/DefaultStreamExtractorTest.java
index 12e19273ae..fcb631e519 100644
--- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/DefaultStreamExtractorTest.java
+++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/DefaultStreamExtractorTest.java
@@ -81,8 +81,10 @@ public abstract class DefaultStreamExtractorTest extends DefaultExtractorTest expectedTags() { return Collections.emptyList(); } // default: no tags
public String expectedSupportInfo() { return ""; } // default: no support info available
public int expectedStreamSegmentsCount() { return -1; } // return 0 or greater to test (default is -1 to ignore)
+ public int expectedStreamHeatmapCount() { return -1; } // return 0 or greater to test (default is -1 to ignore)
public List expectedMetaInfo() throws MalformedURLException { return Collections.emptyList(); } // default: no metadata info available
public ContentAvailability expectedContentAvailability() { return ContentAvailability.UNKNOWN; } // default: unknown content availability
+ public boolean expectedHasLiveChat() { return false; } // default: no live chat available
@Test
@Override
@@ -441,6 +443,12 @@ public void testStreamSegmentsCount() throws Exception {
assertEquals(expectedStreamSegmentsCount(), extractor().getStreamSegments().size());
}
}
+ @Test
+ public void testStreamHeatmapCount() throws Exception {
+ if (expectedStreamHeatmapCount() >= 0) {
+ assertEquals(expectedStreamHeatmapCount(), extractor().getStreamHeatmap().size());
+ }
+ }
/**
* @see DefaultSearchExtractorTest#testMetaInfo()
@@ -480,4 +488,9 @@ public void testMetaInfo() throws Exception {
public void testContentAvailability() throws Exception {
assertEquals(expectedContentAvailability(), extractor().getContentAvailability());
}
+
+ @Test
+ public void testHasLiveChat() throws Exception {
+ assertEquals(expectedHasLiveChat(), extractor().hasLiveChat());
+ }
}
diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeCommentsExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeCommentsExtractorTest.java
index 713fb23611..35c29d7ae9 100644
--- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeCommentsExtractorTest.java
+++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeCommentsExtractorTest.java
@@ -10,11 +10,17 @@
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
import static org.schabi.newpipe.extractor.comments.CommentsInfoItem.UNKNOWN_REPLY_COUNT;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.comments.CommentsInfo;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
+import org.schabi.newpipe.extractor.InitNewPipeTest;
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.downloader.Downloader;
+import org.schabi.newpipe.extractor.downloader.Request;
+import org.schabi.newpipe.extractor.downloader.Response;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.localization.Localization;
import org.schabi.newpipe.extractor.services.DefaultSimpleExtractorTest;
@@ -26,6 +32,8 @@
import java.util.List;
import java.util.Locale;
+import javax.annotation.Nonnull;
+
public class YoutubeCommentsExtractorTest {
abstract static class Base extends DefaultSimpleExtractorTest
@@ -428,6 +436,50 @@ public void testGetCommentsFormatting() throws IOException, ExtractionException
}
}
+ /**
+ * Test live chat mode behavior on a regular video extractor.
+ * Does not extend {@link Base} because these tests do not need network/mock data.
+ */
+ public static class LiveChatMode {
+ private static final String URL = "https://www.youtube.com/watch?v=D00Au7k3i6o";
+
+ @BeforeAll
+ static void setUp() {
+ InitNewPipeTest.initEmpty();
+ NewPipe.init(new Downloader() {
+ @Nonnull
+ @Override
+ public Response execute(@Nonnull final Request request) {
+ throw new UnsupportedOperationException("No communication expected");
+ }
+ });
+ }
+
+ private YoutubeCommentsExtractor createExtractor() throws Exception {
+ return (YoutubeCommentsExtractor) YouTube.getCommentsExtractor(URL);
+ }
+
+ @Test
+ void testIsLiveChatDefaultFalse() throws Exception {
+ assertFalse(createExtractor().isLiveChat());
+ }
+
+ @Test
+ void testSetLiveChatContinuationActivatesLiveChat() throws Exception {
+ final YoutubeCommentsExtractor extractor = createExtractor();
+ assertFalse(extractor.isLiveChat());
+ extractor.setLiveChatContinuation("test-continuation");
+ assertTrue(extractor.isLiveChat());
+ }
+
+ @Test
+ void testCommentsDisabledIsFalseInLiveChatMode() throws Exception {
+ final YoutubeCommentsExtractor extractor = createExtractor();
+ extractor.setLiveChatContinuation("test-continuation");
+ assertTrue(extractor.isLiveChat());
+ assertFalse(extractor.isCommentsDisabled());
+ }
+ }
public static class EditedCommentTest extends Base {
diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/stream/YoutubeStreamExtractorDefaultTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/stream/YoutubeStreamExtractorDefaultTest.java
index ab3ffd7a81..29221ce5d5 100644
--- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/stream/YoutubeStreamExtractorDefaultTest.java
+++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/stream/YoutubeStreamExtractorDefaultTest.java
@@ -622,4 +622,5 @@ void testCheckDescriptiveAudio() throws Exception {
.anyMatch(s -> s.getAudioTrackType() == AudioTrackType.DESCRIPTIVE));
}
}
+
}
diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/stream/YoutubeStreamExtractorLivestreamTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/stream/YoutubeStreamExtractorLivestreamTest.java
index c1e2a1ea48..1fb2cc7891 100644
--- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/stream/YoutubeStreamExtractorLivestreamTest.java
+++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/stream/YoutubeStreamExtractorLivestreamTest.java
@@ -1,5 +1,7 @@
package org.schabi.newpipe.extractor.services.youtube.stream;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
import org.schabi.newpipe.extractor.StreamingService;
@@ -31,7 +33,15 @@ protected StreamExtractor createExtractor() throws Exception {
@Override public String expectedOriginalUrlContains() { return URL; }
@Override public StreamType expectedStreamType() { return StreamType.LIVE_STREAM; }
+ @Override public boolean expectedHasLiveChat() { return true; }
@Override public String expectedUploaderName() { return "Lofi Girl"; }
+
+ @Test
+ void testLiveChatContinuationNotNull() throws Exception {
+ final String continuation = extractor().getLiveChatContinuation();
+ assertNotNull(continuation);
+ assertFalse(continuation.isEmpty());
+ }
@Override public String expectedUploaderUrl() { return "https://www.youtube.com/channel/UCSJ4gkVC6NrvII8umztf0Ow"; }
@Override public long expectedUploaderSubscriberCountAtLeast() { return 9_800_000; }
@Override public List expectedDescriptionContains() {