Skip to content

Commit d8280ce

Browse files
committed
[YouTube] Parse watching count in live streams items
1 parent 3638f0e commit d8280ce

3 files changed

Lines changed: 86 additions & 15 deletions

File tree

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,12 +304,65 @@ public long getTimeStamp() throws ParsingException {
304304
public long getViewCount() throws ParsingException {
305305
assertPageFetched();
306306
try {
307+
if (getStreamType().equals(StreamType.LIVE_STREAM)) {
308+
return getLiveStreamWatchingCount();
309+
}
310+
307311
return Long.parseLong(doc.select("meta[itemprop=interactionCount]").attr(CONTENT));
308312
} catch (Exception e) {//todo: find fallback method
309313
throw new ParsingException("Could not get number of views", e);
310314
}
311315
}
312316

317+
private long getLiveStreamWatchingCount() throws ExtractionException, IOException, JsonParserException {
318+
// https://www.youtube.com/youtubei/v1/updated_metadata?alt=json&key=
319+
String innerTubeKey = null, clientVersion = null;
320+
if (playerArgs != null && !playerArgs.isEmpty()) {
321+
innerTubeKey = playerArgs.getString("innertube_api_key");
322+
clientVersion = playerArgs.getString("innertube_context_client_version");
323+
} else if (!videoInfoPage.isEmpty()) {
324+
innerTubeKey = videoInfoPage.get("innertube_api_key");
325+
clientVersion = videoInfoPage.get("innertube_context_client_version");
326+
}
327+
328+
if (innerTubeKey == null || innerTubeKey.isEmpty()) {
329+
throw new ExtractionException("Couldn't get innerTube key");
330+
}
331+
332+
if (clientVersion == null || clientVersion.isEmpty()) {
333+
throw new ExtractionException("Couldn't get innerTube client version");
334+
}
335+
336+
final String metadataUrl = "https://www.youtube.com/youtubei/v1/updated_metadata?alt=json&key=" + innerTubeKey;
337+
final byte[] dataBody = ("{\"context\":{\"client\":{\"clientName\":1,\"clientVersion\":\"" + clientVersion + "\"}}" +
338+
",\"videoId\":\"" + getId() + "\"}").getBytes("UTF-8");
339+
final Response response = getDownloader().execute(Request.newBuilder()
340+
.post(metadataUrl, dataBody)
341+
.addHeader("Content-Type", "application/json")
342+
.build());
343+
final JsonObject jsonObject = JsonParser.object().from(response.responseBody());
344+
345+
for (Object actionEntry : jsonObject.getArray("actions")) {
346+
if (!(actionEntry instanceof JsonObject)) continue;
347+
final JsonObject entry = (JsonObject) actionEntry;
348+
349+
final JsonObject updateViewershipAction = entry.getObject("updateViewershipAction", null);
350+
if (updateViewershipAction == null) continue;
351+
352+
final JsonArray viewCountRuns = JsonUtils.getArray(updateViewershipAction, "viewership.videoViewCountRenderer.viewCount.runs");
353+
if (viewCountRuns.isEmpty()) continue;
354+
355+
final JsonObject textObject = viewCountRuns.getObject(0);
356+
if (!textObject.has("text")) {
357+
throw new ExtractionException("Response don't have \"text\" element");
358+
}
359+
360+
return Long.parseLong(Utils.removeNonDigitCharacters(textObject.getString("text")));
361+
}
362+
363+
throw new ExtractionException("Could not find correct results in response");
364+
}
365+
313366
@Override
314367
public long getLikeCount() throws ParsingException {
315368
assertPageFetched();

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamInfoItemExtractor.java

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
import org.jsoup.nodes.Element;
44
import org.jsoup.select.Elements;
55
import org.schabi.newpipe.extractor.exceptions.ParsingException;
6+
import org.schabi.newpipe.extractor.localization.TimeAgoParser;
67
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeParsingHelper;
78
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
89
import org.schabi.newpipe.extractor.stream.StreamType;
9-
import org.schabi.newpipe.extractor.localization.TimeAgoParser;
1010
import org.schabi.newpipe.extractor.utils.Utils;
1111

1212
import javax.annotation.Nullable;
@@ -141,6 +141,10 @@ public String getUploaderUrl() throws ParsingException {
141141

142142
@Override
143143
public String getTextualUploadDate() throws ParsingException {
144+
if (getStreamType().equals(StreamType.LIVE_STREAM)) {
145+
return null;
146+
}
147+
144148
if (cachedUploadDate != null) {
145149
return cachedUploadDate;
146150
}
@@ -160,9 +164,12 @@ public String getTextualUploadDate() throws ParsingException {
160164

161165
@Override
162166
public Calendar getUploadDate() throws ParsingException {
167+
if (getStreamType().equals(StreamType.LIVE_STREAM)) {
168+
return null;
169+
}
170+
163171
String textualUploadDate = getTextualUploadDate();
164-
if (timeAgoParser != null
165-
&& textualUploadDate != null && !"".equals(textualUploadDate)) {
172+
if (timeAgoParser != null && textualUploadDate != null && !textualUploadDate.isEmpty()) {
166173
return timeAgoParser.parse(textualUploadDate);
167174
} else {
168175
return null;
@@ -172,24 +179,35 @@ public Calendar getUploadDate() throws ParsingException {
172179
@Override
173180
public long getViewCount() throws ParsingException {
174181
String input;
175-
try {
176-
// TODO: Return the actual live stream's watcher count
177-
// -1 for no view count
178-
if (getStreamType() == StreamType.LIVE_STREAM) return -1;
179182

180-
Element meta = item.select("div[class=\"yt-lockup-meta\"]").first();
181-
if (meta == null) return -1;
183+
if (getStreamType().equals(StreamType.LIVE_STREAM)) {
184+
Element meta = item.select("ul[class=\"yt-lockup-meta-info\"]").first();
185+
if (meta == null) return 0;
186+
187+
final Elements li = meta.select("li");
188+
if (li.isEmpty()) return 0;
182189

183-
// This case can happen if google releases a special video
184-
if(meta.select("li").size() < 2) return -1;
190+
input = li.first().text();
191+
} else {
192+
try {
193+
Element meta = item.select("div[class=\"yt-lockup-meta\"]").first();
194+
if (meta == null) return -1;
185195

186-
input = meta.select("li").get(1).text();
196+
// This case can happen if google releases a special video
197+
if (meta.select("li").size() < 2) return -1;
187198

188-
} catch (IndexOutOfBoundsException e) {
189-
throw new ParsingException("Could not parse yt-lockup-meta although available: " + getUrl(), e);
199+
input = meta.select("li").get(1).text();
200+
} catch (IndexOutOfBoundsException e) {
201+
throw new ParsingException("Could not parse yt-lockup-meta although available: " + getUrl(), e);
202+
}
203+
}
204+
205+
if (input == null) {
206+
throw new ParsingException("Input is null");
190207
}
191208

192209
try {
210+
193211
return Long.parseLong(Utils.removeNonDigitCharacters(input));
194212
} catch (NumberFormatException e) {
195213
// if this happens the video probably has no views

extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/stream/YoutubeStreamExtractorLivestreamTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public void testGetLength() throws ParsingException {
6868
@Test
6969
public void testGetViewCount() throws ParsingException {
7070
long count = extractor.getViewCount();
71-
assertTrue(Long.toString(count), count >= 7148995);
71+
assertTrue(Long.toString(count), count > -1);
7272
}
7373

7474
@Test

0 commit comments

Comments
 (0)