[MediaCCC] Allow obtaining channel tab extractor from scratch

i.e. without needing to pass through the conference/channel extractor
This was needed because clients (like NewPipe) might rely on link handlers to hold as little data as possible, since they might be kept around for long or passed around in system transactions, so this commit allows obtaining a standalone link handler that does not hold a JsonObject within itself.
This commit is contained in:
Stypox 2023-12-30 22:53:27 +01:00
parent 3402cdb666
commit cc9ade962e
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
4 changed files with 114 additions and 78 deletions

View File

@ -19,6 +19,7 @@ import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandlerFactory;
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCChannelTabExtractor;
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCConferenceExtractor;
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCConferenceKiosk;
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCLiveStreamExtractor;
@ -57,7 +58,9 @@ public class MediaCCCService extends StreamingService {
@Override
public ListLinkHandlerFactory getChannelTabLHFactory() {
return null;
// there is just one channel tab in MediaCCC, the one containing conferences, so there is
// no need for a specific channel tab link handler, but we can just use the channel one
return MediaCCCConferenceLinkHandlerFactory.getInstance();
}
@Override
@ -86,17 +89,13 @@ public class MediaCCCService extends StreamingService {
@Override
public ChannelTabExtractor getChannelTabExtractor(final ListLinkHandler linkHandler) {
if (linkHandler instanceof ReadyChannelTabListLinkHandler) {
// conference data has already been fetched, let the ReadyChannelTabListLinkHandler
// create a MediaCCCChannelTabExtractor with that data
return ((ReadyChannelTabListLinkHandler) linkHandler).getChannelTabExtractor(this);
} else {
// conference data has not been fetched yet, so pass null instead
return new MediaCCCChannelTabExtractor(this, linkHandler, null);
}
/*
Channel tab extractors are only supported in conferences and should only come from a
ReadyChannelTabListLinkHandler instance with a ChannelTabExtractorBuilder instance of the
conferences extractor
If that's not the case, return null in this case, so no channel tabs support
*/
return null;
}
@Override

View File

@ -0,0 +1,69 @@
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
import com.grack.nanojson.JsonObject;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems.MediaCCCStreamInfoItemExtractor;
import java.io.IOException;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* MediaCCC does not really have channel tabs, but rather a list of videos for each conference,
* so this class just acts as a videos channel tab extractor.
*/
public class MediaCCCChannelTabExtractor extends ChannelTabExtractor {
@Nullable
private JsonObject conferenceData;
/**
* @param conferenceData will be not-null if conference data has already been fetched by
* {@link MediaCCCConferenceExtractor}. Otherwise, if this parameter is
* {@code null}, conference data will be fetched anew.
*/
public MediaCCCChannelTabExtractor(final StreamingService service,
final ListLinkHandler linkHandler,
@Nullable final JsonObject conferenceData) {
super(service, linkHandler);
this.conferenceData = conferenceData;
}
@Override
public void onFetchPage(@Nonnull final Downloader downloader)
throws ExtractionException, IOException {
if (conferenceData == null) {
// only fetch conference data if we don't have it already
conferenceData = MediaCCCConferenceExtractor.fetchConferenceData(downloader, getId());
}
}
@Nonnull
@Override
public ListExtractor.InfoItemsPage<InfoItem> getInitialPage() {
final MultiInfoItemsCollector collector =
new MultiInfoItemsCollector(getServiceId());
Objects.requireNonNull(conferenceData) // will surely be != null after onFetchPage
.getArray("events")
.stream()
.filter(JsonObject.class::isInstance)
.map(JsonObject.class::cast)
.forEach(event -> collector.commit(new MediaCCCStreamInfoItemExtractor(event)));
return new ListExtractor.InfoItemsPage<>(collector, null);
}
@Override
public ListExtractor.InfoItemsPage<InfoItem> getPage(final Page page) {
return ListExtractor.InfoItemsPage.emptyPage();
}
}

View File

@ -1,24 +1,20 @@
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getImageListFromLogoImageUrl;
import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser;
import com.grack.nanojson.JsonParserException;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
import org.schabi.newpipe.extractor.Image;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.linkhandler.ReadyChannelTabListLinkHandler;
import org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems.MediaCCCStreamInfoItemExtractor;
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCConferenceLinkHandlerFactory;
import java.io.IOException;
@ -27,8 +23,6 @@ import java.util.List;
import javax.annotation.Nonnull;
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getImageListFromLogoImageUrl;
public class MediaCCCConferenceExtractor extends ChannelExtractor {
private JsonObject conferenceData;
@ -37,6 +31,19 @@ public class MediaCCCConferenceExtractor extends ChannelExtractor {
super(service, linkHandler);
}
static JsonObject fetchConferenceData(@Nonnull final Downloader downloader,
@Nonnull final String conferenceId)
throws IOException, ExtractionException {
final String conferenceUrl
= MediaCCCConferenceLinkHandlerFactory.CONFERENCE_API_ENDPOINT + conferenceId;
try {
return JsonParser.object().from(downloader.get(conferenceUrl).responseBody());
} catch (final JsonParserException jpe) {
throw new ExtractionException("Could not parse json returned by URL: " + conferenceUrl);
}
}
@Nonnull
@Override
public List<Image> getAvatars() {
@ -88,20 +95,15 @@ public class MediaCCCConferenceExtractor extends ChannelExtractor {
@Nonnull
@Override
public List<ListLinkHandler> getTabs() throws ParsingException {
return List.of(new ReadyChannelTabListLinkHandler(getUrl(), getId(),
ChannelTabs.VIDEOS, new VideosTabExtractorBuilder(conferenceData)));
return List.of(new ReadyChannelTabListLinkHandler(getUrl(), getId(), ChannelTabs.VIDEOS,
(service, linkHandler) ->
new MediaCCCChannelTabExtractor(service, linkHandler, conferenceData)));
}
@Override
public void onFetchPage(@Nonnull final Downloader downloader)
throws IOException, ExtractionException {
final String conferenceUrl
= MediaCCCConferenceLinkHandlerFactory.CONFERENCE_API_ENDPOINT + getId();
try {
conferenceData = JsonParser.object().from(downloader.get(conferenceUrl).responseBody());
} catch (final JsonParserException jpe) {
throw new ExtractionException("Could not parse json returned by URL: " + conferenceUrl);
}
conferenceData = fetchConferenceData(downloader, getId());
}
@Nonnull
@ -109,55 +111,4 @@ public class MediaCCCConferenceExtractor extends ChannelExtractor {
public String getName() throws ParsingException {
return conferenceData.getString("title");
}
private static final class VideosTabExtractorBuilder
implements ReadyChannelTabListLinkHandler.ChannelTabExtractorBuilder {
private final JsonObject conferenceData;
VideosTabExtractorBuilder(final JsonObject conferenceData) {
this.conferenceData = conferenceData;
}
@Nonnull
@Override
public ChannelTabExtractor build(@Nonnull final StreamingService service,
@Nonnull final ListLinkHandler linkHandler) {
return new VideosChannelTabExtractor(service, linkHandler, conferenceData);
}
}
private static final class VideosChannelTabExtractor extends ChannelTabExtractor {
private final JsonObject conferenceData;
VideosChannelTabExtractor(final StreamingService service,
final ListLinkHandler linkHandler,
final JsonObject conferenceData) {
super(service, linkHandler);
this.conferenceData = conferenceData;
}
@Override
public void onFetchPage(@Nonnull final Downloader downloader) {
// Nothing to do here, as data was already fetched
}
@Nonnull
@Override
public ListExtractor.InfoItemsPage<InfoItem> getInitialPage() {
final MultiInfoItemsCollector collector =
new MultiInfoItemsCollector(getServiceId());
conferenceData.getArray("events")
.stream()
.filter(JsonObject.class::isInstance)
.map(JsonObject.class::cast)
.forEach(event -> collector.commit(new MediaCCCStreamInfoItemExtractor(event)));
return new InfoItemsPage<>(collector, null);
}
@Override
public InfoItemsPage<InfoItem> getPage(final Page page) {
return InfoItemsPage.emptyPage();
}
}
}

View File

@ -1,11 +1,17 @@
package org.schabi.newpipe.extractor.services.media_ccc.linkHandler;
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
import org.schabi.newpipe.extractor.utils.Parser;
import java.util.List;
/**
* Since MediaCCC does not really have channel tabs (i.e. it only has one single "tab" with videos),
* this link handler acts both as the channel link handler and the channel tab link handler. That's
* why {@link #getAvailableContentFilter()} has been overridden.
*/
public final class MediaCCCConferenceLinkHandlerFactory extends ListLinkHandlerFactory {
private static final MediaCCCConferenceLinkHandlerFactory INSTANCE
@ -46,4 +52,15 @@ public final class MediaCCCConferenceLinkHandlerFactory extends ListLinkHandlerF
return false;
}
}
/**
* @see MediaCCCConferenceLinkHandlerFactory
* @return MediaCCC's only channel "tab", i.e. {@link ChannelTabs#VIDEOS}
*/
@Override
public String[] getAvailableContentFilter() {
return new String[]{
ChannelTabs.VIDEOS,
};
}
}