Skip to content

Commit e9c38bd

Browse files
committed
[media.ccc.de] Live stream kiosk: detect break "talks" segements
Add and improve tests for MediaCCCLiveStreamKioskExtractor: - test stream items if a live stream is running - use mock tests to check live talk extraction and testing conferences
1 parent 55a2af2 commit e9c38bd

File tree

5 files changed

+214
-12
lines changed

5 files changed

+214
-12
lines changed

extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCLiveStreamKioskExtractor.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import javax.annotation.Nonnull;
1111
import javax.annotation.Nullable;
12+
import java.time.OffsetDateTime;
1213
import java.util.List;
1314

1415
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getThumbnailsFromLiveStreamItem;
@@ -19,17 +20,25 @@ public class MediaCCCLiveStreamKioskExtractor implements StreamInfoItemExtractor
1920
private final String group;
2021
private final JsonObject roomInfo;
2122

23+
@Nonnull
24+
private final JsonObject currentTalk;
25+
2226
public MediaCCCLiveStreamKioskExtractor(final JsonObject conferenceInfo,
2327
final String group,
2428
final JsonObject roomInfo) {
2529
this.conferenceInfo = conferenceInfo;
2630
this.group = group;
2731
this.roomInfo = roomInfo;
32+
this.currentTalk = roomInfo.getObject("talks").getObject("current");
2833
}
2934

3035
@Override
3136
public String getName() throws ParsingException {
32-
return roomInfo.getObject("talks").getObject("current").getString("title");
37+
if (isBreak()) {
38+
return roomInfo.getString("display") + " - Pause";
39+
} else {
40+
return currentTalk.getString("title");
41+
}
3342
}
3443

3544
@Override
@@ -95,6 +104,18 @@ public String getTextualUploadDate() throws ParsingException {
95104
@Nullable
96105
@Override
97106
public DateWrapper getUploadDate() throws ParsingException {
98-
return null;
107+
if (isBreak()) {
108+
return new DateWrapper(OffsetDateTime.parse(currentTalk.getString("fstart")));
109+
} else {
110+
return new DateWrapper(OffsetDateTime.parse(conferenceInfo.getString("startsAt")));
111+
}
112+
}
113+
114+
/**
115+
* Whether the current "talk" is a talk or a pause.
116+
*/
117+
private boolean isBreak() {
118+
return OffsetDateTime.parse(currentTalk.getString("fstart")).isBefore(OffsetDateTime.now())
119+
|| "gap".equals(currentTalk.getString("special"));
99120
}
100121
}

extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCParsingHelper.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,15 @@ public static JsonArray getLiveStreams(final Downloader downloader,
8282
return liveStreams;
8383
}
8484

85+
/**
86+
* <p>Reset cached live stream data.</p>
87+
* This is a temporary method which can be used to reset the cached live stream data until a
88+
* caching policy for {@link #getLiveStreams(Downloader, Localization)} is implemented.
89+
*/
90+
public static void resetCachedLiveStreamInfo() {
91+
liveStreams = null;
92+
}
93+
8594
/**
8695
* Get an {@link Image} list from a given image logo URL.
8796
*

extractor/src/test/java/org/schabi/newpipe/extractor/services/media_ccc/MediaCCCLiveStreamListExtractorTest.java

Lines changed: 88 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,107 @@
22

33
import org.junit.jupiter.api.BeforeAll;
44
import org.junit.jupiter.api.Test;
5+
import org.schabi.newpipe.downloader.DownloaderFactory;
56
import org.schabi.newpipe.downloader.DownloaderTestImpl;
7+
import org.schabi.newpipe.downloader.MockOnly;
68
import org.schabi.newpipe.extractor.InfoItem;
9+
import org.schabi.newpipe.extractor.ListExtractor;
710
import org.schabi.newpipe.extractor.NewPipe;
811
import org.schabi.newpipe.extractor.kiosk.KioskExtractor;
12+
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCLiveStreamKiosk;
13+
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper;
914

1015
import java.util.List;
1116

17+
import static org.junit.jupiter.api.Assertions.assertEquals;
18+
import static org.junit.jupiter.api.Assertions.assertTrue;
1219
import static org.schabi.newpipe.extractor.ServiceList.MediaCCC;
20+
import static org.schabi.newpipe.extractor.services.DefaultTests.defaultTestListOfItems;
1321

1422
public class MediaCCCLiveStreamListExtractorTest {
15-
private static KioskExtractor extractor;
1623

17-
@BeforeAll
18-
public static void setUpClass() throws Exception {
19-
NewPipe.init(DownloaderTestImpl.getInstance());
20-
extractor = MediaCCC.getKioskList().getExtractorById("live", null);
21-
extractor.fetchPage();
24+
private static final String RESOURCE_PATH = DownloaderFactory.RESOURCE_PATH
25+
+ "services/media.ccc.de/kiosk/live/";
26+
private static final String LIVE_KIOSK_ID = MediaCCCLiveStreamKiosk.KIOSK_ID;
27+
28+
/**
29+
* Test against the media.ccc.de livestream API endpoint
30+
* and ensure that no exceptions are thrown.
31+
*/
32+
public static class LiveDataTest {
33+
private static KioskExtractor extractor;
34+
35+
@BeforeAll
36+
public static void setUpClass() throws Exception {
37+
NewPipe.init(DownloaderTestImpl.getInstance());
38+
extractor = MediaCCC.getKioskList().getExtractorById(LIVE_KIOSK_ID, null);
39+
MediaCCCParsingHelper.resetCachedLiveStreamInfo();
40+
extractor.fetchPage();
41+
}
42+
43+
@Test
44+
void getConferencesListTest() throws Exception {
45+
final ListExtractor.InfoItemsPage liveStreamPage = extractor.getInitialPage();
46+
final List<InfoItem> items = liveStreamPage.getItems();
47+
if (items.isEmpty()) {
48+
// defaultTestListOfItems() fails, if items is empty.
49+
// This can happen if there are no current live streams.
50+
// In this case, we just check if an exception was thrown
51+
assertTrue(liveStreamPage.getErrors().isEmpty());
52+
} else {
53+
defaultTestListOfItems(MediaCCC, items, liveStreamPage.getErrors());
54+
}
55+
}
2256
}
2357

24-
@Test
25-
public void getConferencesListTest() throws Exception {
26-
final List<InfoItem> items = extractor.getInitialPage().getItems();
27-
// just test if there is an exception thrown
58+
/**
59+
* Test conferences which are available via the API for C3voc internal testing,
60+
* but not intended to be shown to users.
61+
*/
62+
@MockOnly("The live stream API returns different data depending on if and what conferences"
63+
+ " are running. The PreparationTest tests a conference which is used "
64+
+ "for internal testing.")
65+
public static class PreparationTest {
66+
private static KioskExtractor extractor;
67+
68+
@BeforeAll
69+
public static void setUpClass() throws Exception {
70+
NewPipe.init(DownloaderFactory.getDownloader(RESOURCE_PATH + "preparation"));
71+
extractor = MediaCCC.getKioskList().getExtractorById(LIVE_KIOSK_ID, null);
72+
MediaCCCParsingHelper.resetCachedLiveStreamInfo();
73+
extractor.fetchPage();
74+
}
75+
76+
@Test
77+
void getConferencesListTest() throws Exception {
78+
// Testing conferences and the corresponding talks should not be extracted.
79+
assertTrue(extractor.getInitialPage().getItems().isEmpty());
80+
}
2881
}
2982

83+
/**
84+
* Test a running conference.
85+
*/
86+
@MockOnly("The live stream API returns different data depending on if and what conferences"
87+
+ " are running. Using mocks to ensure that there are conferences & talks to extract.")
88+
public static class LiveConferenceTest {
89+
private static KioskExtractor extractor;
90+
91+
@BeforeAll
92+
public static void setUpClass() throws Exception {
93+
NewPipe.init(DownloaderFactory.getDownloader(RESOURCE_PATH + "running"));
94+
extractor = MediaCCC.getKioskList().getExtractorById(LIVE_KIOSK_ID, null);
95+
MediaCCCParsingHelper.resetCachedLiveStreamInfo();
96+
extractor.fetchPage();
97+
}
98+
99+
@Test
100+
void getConferencesListTest() throws Exception {
101+
final ListExtractor.InfoItemsPage liveStreamPage = extractor.getInitialPage();
102+
final List<InfoItem> items = liveStreamPage.getItems();
103+
assertEquals(6, items.size());
104+
defaultTestListOfItems(MediaCCC, items, liveStreamPage.getErrors());
105+
106+
}
107+
}
30108
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"request": {
3+
"httpMethod": "GET",
4+
"url": "https://streaming.media.ccc.de/streams/v2.json",
5+
"headers": {
6+
"Accept-Language": [
7+
"en-GB, en;q\u003d0.9"
8+
]
9+
},
10+
"localization": {
11+
"languageCode": "en",
12+
"countryCode": "GB"
13+
}
14+
},
15+
"response": {
16+
"responseCode": 200,
17+
"responseMessage": "OK",
18+
"responseHeaders": {
19+
"access-control-allow-origin": [
20+
"*"
21+
],
22+
"content-type": [
23+
"application/json"
24+
],
25+
"date": [
26+
"Sat, 05 Aug 2023 10:59:09 GMT"
27+
],
28+
"server": [
29+
"nginx"
30+
],
31+
"strict-transport-security": [
32+
"max-age\u003d31536000"
33+
],
34+
"transfer-encoding": [
35+
"chunked"
36+
],
37+
"vary": [
38+
"Accept-Encoding"
39+
],
40+
"x-cache": [
41+
"HIT origin"
42+
]
43+
},
44+
"responseBody": "[\n {\n \"conference\": \"BornHack 2023\",\n \"slug\": \"bornhack2023\",\n \"author\": \"BornHack ApS\",\n \"description\": \"BornHack is a 7 day outdoor tent camp where hackers, makers and people with an interest in technology or security come together to celebrate technology, socialise, learn and have fun.\",\n \"keywords\": \"\",\n \"schedule\": null,\n \"startsAt\": \"2023-08-02T13:00:00+0000\",\n \"endsAt\": \"2023-08-09T18:00:00+0000\",\n \"isCurrentlyStreaming\": false,\n \"groups\": [\n {\n \"group\": \"Lecture Rooms\",\n \"rooms\": [\n {\n \"slug\": \"bornhack1\",\n \"schedulename\": \"Speakers Tent\",\n \"thumb\": \"https://cdn.c3voc.de/thumbnail/bornhack23s1/thumb.jpeg\",\n \"poster\": \"https://cdn.c3voc.de/thumbnail/bornhack23s1/poster.jpeg\",\n \"link\": \"https://streaming.media.ccc.de/bornhack2023/bornhack1\",\n \"display\": \"Speakers Tent\",\n \"stream\": \"bornhack23s1\",\n \"talks\": {\n \"current\": null,\n \"next\": null\n },\n \"streams\": [\n {\n \"slug\": \"hls-native\",\n \"display\": \"Speakers Tent \",\n \"type\": \"hls\",\n \"isTranslated\": false,\n \"videoSize\": null,\n \"urls\": {}\n },\n {\n \"slug\": \"hls-translated\",\n \"display\": \"Speakers Tent (Translated)\",\n \"type\": \"hls\",\n \"isTranslated\": true,\n \"videoSize\": null,\n \"urls\": {}\n },\n {\n \"slug\": \"hd-native\",\n \"display\": \"Speakers Tent FullHD Video\",\n \"type\": \"video\",\n \"isTranslated\": false,\n \"videoSize\": [\n 1920,\n 1080\n ],\n \"urls\": {\n \"hls\": {\n \"display\": \"HLS\",\n \"tech\": \"1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s\",\n \"url\": \"https://cdn.c3voc.de/hls/bornhack23s1/native_hd.m3u8\"\n }\n }\n },\n {\n \"slug\": \"hd-translated\",\n \"display\": \"Speakers Tent FullHD Video (Translated)\",\n \"type\": \"video\",\n \"isTranslated\": true,\n \"videoSize\": [\n 1920,\n 1080\n ],\n \"urls\": {\n \"hls\": {\n \"display\": \"HLS\",\n \"tech\": \"1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s\",\n \"url\": \"https://cdn.c3voc.de/hls/bornhack23s1/translated_hd.m3u8\"\n }\n }\n }\n ]\n },\n {\n \"slug\": \"bornhack2\",\n \"schedulename\": \"Bar Area\",\n \"thumb\": \"https://cdn.c3voc.de/thumbnail/bornhack23s2/thumb.jpeg\",\n \"poster\": \"https://cdn.c3voc.de/thumbnail/bornhack23s2/poster.jpeg\",\n \"link\": \"https://streaming.media.ccc.de/bornhack2023/bornhack2\",\n \"display\": \"Bar Area\",\n \"stream\": \"bornhack23s2\",\n \"talks\": {\n \"current\": null,\n \"next\": null\n },\n \"streams\": [\n {\n \"slug\": \"hls-native\",\n \"display\": \"Bar Area \",\n \"type\": \"hls\",\n \"isTranslated\": false,\n \"videoSize\": null,\n \"urls\": {}\n },\n {\n \"slug\": \"hls-translated\",\n \"display\": \"Bar Area (Translated)\",\n \"type\": \"hls\",\n \"isTranslated\": true,\n \"videoSize\": null,\n \"urls\": {}\n },\n {\n \"slug\": \"hd-native\",\n \"display\": \"Bar Area FullHD Video\",\n \"type\": \"video\",\n \"isTranslated\": false,\n \"videoSize\": [\n 1920,\n 1080\n ],\n \"urls\": {\n \"hls\": {\n \"display\": \"HLS\",\n \"tech\": \"1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s\",\n \"url\": \"https://cdn.c3voc.de/hls/bornhack23s2/native_hd.m3u8\"\n }\n }\n },\n {\n \"slug\": \"hd-translated\",\n \"display\": \"Bar Area FullHD Video (Translated)\",\n \"type\": \"video\",\n \"isTranslated\": true,\n \"videoSize\": [\n 1920,\n 1080\n ],\n \"urls\": {\n \"hls\": {\n \"display\": \"HLS\",\n \"tech\": \"1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s\",\n \"url\": \"https://cdn.c3voc.de/hls/bornhack23s2/translated_hd.m3u8\"\n }\n }\n }\n ]\n },\n {\n \"slug\": \"bornhack3\",\n \"schedulename\": \"Bar Meetup Area\",\n \"thumb\": \"https://cdn.c3voc.de/thumbnail/bornhack23s3/thumb.jpeg\",\n \"poster\": \"https://cdn.c3voc.de/thumbnail/bornhack23s3/poster.jpeg\",\n \"link\": \"https://streaming.media.ccc.de/bornhack2023/bornhack3\",\n \"display\": \"Bar Meetup Area\",\n \"stream\": \"bornhack23s3\",\n \"talks\": {\n \"current\": null,\n \"next\": null\n },\n \"streams\": [\n {\n \"slug\": \"hls-native\",\n \"display\": \"Bar Meetup Area \",\n \"type\": \"hls\",\n \"isTranslated\": false,\n \"videoSize\": null,\n \"urls\": {}\n },\n {\n \"slug\": \"hls-translated\",\n \"display\": \"Bar Meetup Area (Translated)\",\n \"type\": \"hls\",\n \"isTranslated\": true,\n \"videoSize\": null,\n \"urls\": {}\n },\n {\n \"slug\": \"hd-native\",\n \"display\": \"Bar Meetup Area FullHD Video\",\n \"type\": \"video\",\n \"isTranslated\": false,\n \"videoSize\": [\n 1920,\n 1080\n ],\n \"urls\": {\n \"hls\": {\n \"display\": \"HLS\",\n \"tech\": \"1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s\",\n \"url\": \"https://cdn.c3voc.de/hls/bornhack23s3/native_hd.m3u8\"\n }\n }\n },\n {\n \"slug\": \"hd-translated\",\n \"display\": \"Bar Meetup Area FullHD Video (Translated)\",\n \"type\": \"video\",\n \"isTranslated\": true,\n \"videoSize\": [\n 1920,\n 1080\n ],\n \"urls\": {\n \"hls\": {\n \"display\": \"HLS\",\n \"tech\": \"1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s\",\n \"url\": \"https://cdn.c3voc.de/hls/bornhack23s3/translated_hd.m3u8\"\n }\n }\n }\n ]\n }\n ]\n }\n ]\n }\n]",
45+
"latestUrl": "https://streaming.media.ccc.de/streams/v2.json"
46+
}
47+
}

0 commit comments

Comments
 (0)