@@ -73,8 +73,8 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
7373
7474 private JsonObject jsonResponse ;
7575
76- @ SuppressWarnings ( "OptionalUsedAsFieldOrParameterType" )
77- private Optional < ChannelHeader > channelHeader ;
76+ @ Nullable
77+ private ChannelHeader channelHeader ;
7878
7979 private String channelId ;
8080
@@ -132,7 +132,7 @@ public String getId() throws ParsingException {
132132 public String getName () throws ParsingException {
133133 assertPageFetched ();
134134 return YoutubeChannelHelper .getChannelName (
135- channelHeader , jsonResponse , channelAgeGateRenderer );
135+ channelHeader , channelAgeGateRenderer , jsonResponse );
136136 }
137137
138138 @ Nonnull
@@ -146,40 +146,40 @@ public List<Image> getAvatars() throws ParsingException {
146146 .orElseThrow (() -> new ParsingException ("Could not get avatars" ));
147147 }
148148
149- return channelHeader .map (header -> {
150- switch (header .headerType ) {
151- case PAGE :
152- final JsonObject imageObj = header .json .getObject (CONTENT )
153- .getObject (PAGE_HEADER_VIEW_MODEL )
154- .getObject (IMAGE );
155-
156- if (imageObj .has (CONTENT_PREVIEW_IMAGE_VIEW_MODEL )) {
157- return imageObj .getObject (CONTENT_PREVIEW_IMAGE_VIEW_MODEL )
158- .getObject (IMAGE )
159- .getArray (SOURCES );
160- }
161-
162- if (imageObj .has ("decoratedAvatarViewModel" )) {
163- return imageObj .getObject ("decoratedAvatarViewModel" )
164- .getObject (AVATAR )
165- .getObject ("avatarViewModel" )
166- .getObject (IMAGE )
167- .getArray (SOURCES );
149+ return Optional .ofNullable (channelHeader )
150+ .map (header -> {
151+ switch (header .headerType ) {
152+ case PAGE :
153+ final JsonObject imageObj = header .json .getObject (CONTENT )
154+ .getObject (PAGE_HEADER_VIEW_MODEL )
155+ .getObject (IMAGE );
156+
157+ if (imageObj .has (CONTENT_PREVIEW_IMAGE_VIEW_MODEL )) {
158+ return imageObj .getObject (CONTENT_PREVIEW_IMAGE_VIEW_MODEL )
159+ .getObject (IMAGE )
160+ .getArray (SOURCES );
161+ }
162+
163+ if (imageObj .has ("decoratedAvatarViewModel" )) {
164+ return imageObj .getObject ("decoratedAvatarViewModel" )
165+ .getObject (AVATAR )
166+ .getObject ("avatarViewModel" )
167+ .getObject (IMAGE )
168+ .getArray (SOURCES );
169+ }
170+
171+ // Return an empty avatar array as a fallback
172+ return new JsonArray ();
173+ case INTERACTIVE_TABBED :
174+ return header .json .getObject ("boxArt" )
175+ .getArray (THUMBNAILS );
176+ case C4_TABBED :
177+ case CAROUSEL :
178+ default :
179+ return header .json .getObject (AVATAR )
180+ .getArray (THUMBNAILS );
168181 }
169-
170- // Return an empty avatar array as a fallback
171- return new JsonArray ();
172- case INTERACTIVE_TABBED :
173- return header .json .getObject ("boxArt" )
174- .getArray (THUMBNAILS );
175-
176- case C4_TABBED :
177- case CAROUSEL :
178- default :
179- return header .json .getObject (AVATAR )
180- .getArray (THUMBNAILS );
181- }
182- })
182+ })
183183 .map (YoutubeParsingHelper ::getImagesFromThumbnailsArray )
184184 .orElseThrow (() -> new ParsingException ("Could not get avatars" ));
185185 }
@@ -192,7 +192,8 @@ public List<Image> getBanners() {
192192 return List .of ();
193193 }
194194
195- return channelHeader .map (header -> {
195+ return Optional .ofNullable (channelHeader )
196+ .map (header -> {
196197 if (header .headerType == HeaderType .PAGE ) {
197198 final JsonObject pageHeaderViewModel = header .json .getObject (CONTENT )
198199 .getObject (PAGE_HEADER_VIEW_MODEL );
@@ -235,16 +236,14 @@ public long getSubscriberCount() throws ParsingException {
235236 return UNKNOWN_SUBSCRIBER_COUNT ;
236237 }
237238
238- if (channelHeader .isPresent ()) {
239- final ChannelHeader header = channelHeader .get ();
240-
241- if (header .headerType == HeaderType .INTERACTIVE_TABBED ) {
239+ if (channelHeader != null ) {
240+ if (channelHeader .headerType == HeaderType .INTERACTIVE_TABBED ) {
242241 // No subscriber count is available on interactiveTabbedHeaderRenderer header
243242 return UNKNOWN_SUBSCRIBER_COUNT ;
244243 }
245244
246- final JsonObject headerJson = header .json ;
247- if (header .headerType == HeaderType .PAGE ) {
245+ final JsonObject headerJson = channelHeader .json ;
246+ if (channelHeader .headerType == HeaderType .PAGE ) {
248247 return getSubscriberCountFromPageChannelHeader (headerJson );
249248 }
250249
@@ -321,19 +320,17 @@ public String getDescription() throws ParsingException {
321320 }
322321
323322 try {
324- if (channelHeader .isPresent ()) {
325- final ChannelHeader header = channelHeader .get ();
326- if (header .headerType == HeaderType .INTERACTIVE_TABBED ) {
327- /*
328- In an interactiveTabbedHeaderRenderer, the real description, is only available
329- in its header
330- The other one returned in non-About tabs accessible in the
331- microformatDataRenderer object of the response may be completely different
332- The description extracted is incomplete and the original one can be only
333- accessed from the About tab
334- */
335- return getTextFromObject (header .json .getObject ("description" ));
336- }
323+ if (channelHeader != null
324+ && channelHeader .headerType == HeaderType .INTERACTIVE_TABBED ) {
325+ /*
326+ In an interactiveTabbedHeaderRenderer, the real description, is only available
327+ in its header
328+ The other one returned in non-About tabs accessible in the
329+ microformatDataRenderer object of the response may be completely different
330+ The description extracted is incomplete and the original one can be only
331+ accessed from the About tab
332+ */
333+ return getTextFromObject (channelHeader .json .getObject ("description" ));
337334 }
338335
339336 return jsonResponse .getObject (METADATA )
@@ -368,8 +365,12 @@ public boolean isVerified() throws ParsingException {
368365 return false ;
369366 }
370367
371- return YoutubeChannelHelper .isChannelVerified (channelHeader .orElseThrow (() ->
372- new ParsingException ("Could not get verified status" )));
368+ if (channelHeader == null ) {
369+ throw new ParsingException (
370+ "Could not get channel verified status, no channel header has been extracted" );
371+ }
372+
373+ return YoutubeChannelHelper .isChannelVerified (channelHeader );
373374 }
374375
375376 @ Nonnull
@@ -421,6 +422,19 @@ private List<ListLinkHandler> getTabsForNonAgeRestrictedChannels() throws Parsin
421422
422423 final String urlSuffix = urlParts [urlParts .length - 1 ];
423424
425+ /*
426+ Make a copy of the channelHeader member to avoid keeping a reference to
427+ this YoutubeChannelExtractor instance which would prevent serialization of
428+ the ReadyChannelTabListLinkHandler instance created above
429+ */
430+ final ChannelHeader channelHeaderCopy ;
431+ if (channelHeader == null ) {
432+ channelHeaderCopy = null ;
433+ } else {
434+ channelHeaderCopy = new ChannelHeader (channelHeader .json ,
435+ channelHeader .headerType );
436+ }
437+
424438 switch (urlSuffix ) {
425439 case "videos" :
426440 // Since the Videos tab has already its contents fetched, make
@@ -431,9 +445,8 @@ private List<ListLinkHandler> getTabsForNonAgeRestrictedChannels() throws Parsin
431445 channelId ,
432446 ChannelTabs .VIDEOS ,
433447 (service , linkHandler ) -> new VideosTabExtractor (
434- service , linkHandler , tabRenderer , channelHeader ,
435- name , id , url )));
436-
448+ service , linkHandler , tabRenderer ,
449+ channelHeaderCopy , name , id , url )));
437450 break ;
438451 case "shorts" :
439452 addNonVideosTab .accept (ChannelTabs .SHORTS );
0 commit comments