From 7ff731ec88a51d803614d63464270d82d5c120a5 Mon Sep 17 00:00:00 2001 From: 0xb00bface <0xboobface@gmail.com> Date: Sat, 18 Jul 2020 19:05:41 +0200 Subject: [PATCH] Fix CamSoda downloads --- .../ctbrec/sites/camsoda/CamsodaModel.java | 168 ++++++++++-------- 1 file changed, 90 insertions(+), 78 deletions(-) diff --git a/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java b/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java index b5988b04..69c3f5f4 100644 --- a/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java +++ b/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java @@ -10,6 +10,7 @@ import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.Optional; import java.util.Random; import java.util.concurrent.ExecutionException; @@ -44,99 +45,124 @@ public class CamsodaModel extends AbstractModel { private static final String EDGE_SERVERS = "edge_servers"; private static final String STATUS = "status"; private static final Logger LOG = LoggerFactory.getLogger(CamsodaModel.class); - private String streamUrl; private transient List streamSources = null; private transient boolean isNew; private float sortOrder = 0; private Random random = new Random(); int[] resolution = new int[2]; - boolean oldStreamUrl = true; + public String getStreamUrl() throws IOException { - if (streamUrl == null) { - if(oldStreamUrl) { - loadModel(); - } else { - getNewStreamUrl(); - } + Request req = createJsonRequest(getTokenInfoUrl()); + JSONObject response = executeJsonRequest(req); + if (response.optInt(STATUS) == 1 && response.optJSONArray(EDGE_SERVERS) != null && response.optJSONArray(EDGE_SERVERS).length() > 0) { + String edgeServer = response.getJSONArray(EDGE_SERVERS).getString(0); + String streamName = response.getString(STREAM_NAME); + String token = response.getString("token"); + return constructStreamUrl(edgeServer, streamName, token); + } else { + throw new JSONException("JSON response has not the expected structure"); } - return streamUrl; } - public String getNewStreamUrl() throws IOException { + private String getTokenInfoUrl() { String guestUsername = "guest_" + 10_000 + random.nextInt(50_000); - String url = site.getBaseUrl() + "/api/v1/video/vtoken/" + getName() + "?username=" + guestUsername; - Request req = new Request.Builder() - .url(url) + String tokenInfoUrl = site.getBaseUrl() + "/api/v1/video/vtoken/" + getName() + "?username=" + guestUsername; + return tokenInfoUrl; + } + + private String constructStreamUrl(String edgeServer, String streamName, String token) { + StringBuilder url = new StringBuilder("https://"); + url.append(edgeServer).append('/'); + if (streamName.contains("-flu")) { + url.append(streamName); + url.append("_h264_aac"); + url.append(streamName.contains("-flu-hd") ? "_720p" : "_480p"); + url.append("/index.m3u8"); + if (!isPublic(streamName)) { + url.append("?token=").append(token); + } + } else { + // https://vide7-ord.camsoda.com/cam/mp4:maxandtokio-enc10-ord_h264_aac_480p/playlist.m3u8 + url.append("cam/mp4:"); + url.append(streamName); + url.append("_h264_aac_480p/playlist.m3u8"); + } + LOG.debug("Stream URL: {}", url); + return url.toString(); + } + + private Request createJsonRequest(String tokenInfoUrl) { + return new Request.Builder() + .url(tokenInfoUrl) .header(ACCEPT, MIMETYPE_APPLICATION_JSON) .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage()) .header(X_REQUESTED_WITH, XML_HTTP_REQUEST) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .build(); - try (Response response = site.getHttpClient().execute(req)) { + } + + private JSONObject executeJsonRequest(Request request) throws IOException { + try (Response response = site.getHttpClient().execute(request)) { if (response.isSuccessful()) { JSONObject jsonResponse = new JSONObject(response.body().string()); - if (jsonResponse.optInt(STATUS) == 1) { - String edgeServer = jsonResponse.getJSONArray(EDGE_SERVERS).getString(0); - String streamName = jsonResponse.getString(STREAM_NAME); - String token = jsonResponse.getString("token"); - streamUrl = "https://" + edgeServer + '/' + streamName + "_h264_aac_480p/index.m3u8?token=" + token; - } else { - throw new JSONException("Response does not contain a token"); - } + return jsonResponse; } else { throw new HttpException(response.code(), response.message()); } } - return streamUrl; + } + + private boolean isPublic(String streamName) { + return Optional.ofNullable(streamName).orElse("").contains("_public"); } @Override public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException { - String playlistUrl = getStreamUrl(); - if (playlistUrl == null) { - return Collections.emptyList(); - } - LOG.trace("Loading playlist {}", playlistUrl); - Request req = new Request.Builder() - .url(playlistUrl) - .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage()) - .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) - .build(); - try (Response response = site.getHttpClient().execute(req)) { - if (response.isSuccessful()) { - InputStream inputStream = response.body().byteStream(); - PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT); - Playlist playlist = parser.parse(); - MasterPlaylist master = playlist.getMasterPlaylist(); - PlaylistData playlistData = master.getPlaylists().get(0); - StreamSource streamsource = new StreamSource(); - if (oldStreamUrl) { - streamsource.mediaPlaylistUrl = streamUrl.replace("playlist.m3u8", playlistData.getUri()); - } else { - int cutOffAt = playlistUrl.indexOf("index.m3u8"); + try { + String playlistUrl = getStreamUrl(); + if (playlistUrl == null) { + return Collections.emptyList(); + } + LOG.info("Loading playlist {}", playlistUrl); + Request req = new Request.Builder() + .url(playlistUrl) + .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage()) + .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) + .build(); + try (Response response = site.getHttpClient().execute(req)) { + if (response.isSuccessful()) { + InputStream inputStream = response.body().byteStream(); + PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT); + Playlist playlist = parser.parse(); + MasterPlaylist master = playlist.getMasterPlaylist(); + PlaylistData playlistData = master.getPlaylists().get(0); + StreamSource streamsource = new StreamSource(); + int cutOffAt = Math.max(playlistUrl.indexOf("index.m3u8"), playlistUrl.indexOf("playlist.m3u8")); String segmentPlaylistUrl = playlistUrl.substring(0, cutOffAt) + playlistData.getUri(); streamsource.mediaPlaylistUrl = segmentPlaylistUrl; - } - if (playlistData.hasStreamInfo()) { - StreamInfo info = playlistData.getStreamInfo(); - streamsource.bandwidth = info.getBandwidth(); - streamsource.width = info.hasResolution() ? info.getResolution().width : 0; - streamsource.height = info.hasResolution() ? info.getResolution().height : 0; + if (playlistData.hasStreamInfo()) { + StreamInfo info = playlistData.getStreamInfo(); + streamsource.bandwidth = info.getBandwidth(); + streamsource.width = info.hasResolution() ? info.getResolution().width : 0; + streamsource.height = info.hasResolution() ? info.getResolution().height : 0; + } else { + streamsource.bandwidth = 0; + streamsource.width = 0; + streamsource.height = 0; + } + streamSources = new ArrayList<>(); + streamSources.add(streamsource); } else { - streamsource.bandwidth = 0; - streamsource.width = 0; - streamsource.height = 0; + LOG.trace("Response: {}", response.body().string()); + throw new HttpException(playlistUrl, response.code(), response.message()); } - streamSources = new ArrayList<>(); - streamSources.add(streamsource); - } else { - LOG.trace("Response: {}", response.body().string()); - throw new HttpException(response.code(), response.message()); } + return streamSources; + } catch (JSONException e) { + return Collections.emptyList(); } - return streamSources; } private void loadModel() throws IOException { @@ -151,19 +177,9 @@ public class CamsodaModel extends AbstractModel { try (Response response = site.getHttpClient().execute(req)) { if (response.isSuccessful()) { JSONObject result = new JSONObject(response.body().string()); - if (result.getBoolean(STATUS)) { + if (result.optBoolean(STATUS)) { JSONObject chat = result.getJSONObject("user").getJSONObject("chat"); String status = chat.getString(STATUS); - oldStreamUrl = !chat.getString(STREAM_NAME).contains("/"); - if (oldStreamUrl && chat.has(EDGE_SERVERS)) { - String edgeServer = chat.getJSONArray(EDGE_SERVERS).getString(0); - String streamName = chat.getString(STREAM_NAME); - if(streamName.contains("/")) { - streamUrl = "https://" + edgeServer + "/" + streamName + "/index.m3u8"; - } else { - streamUrl = "https://" + edgeServer + "/cam/mp4:" + streamName + "_h264_aac_480p/playlist.m3u8"; - } - } setOnlineStateByStatus(status); } else { throw new IOException("Result was not ok"); @@ -226,11 +242,11 @@ public class CamsodaModel extends AbstractModel { return resolution; } else { try { - List streamSources = getStreamSources(); - if (streamSources.isEmpty()) { + List sources = getStreamSources(); + if (sources.isEmpty()) { return new int[] { 0, 0 }; } else { - StreamSource src = streamSources.get(0); + StreamSource src = sources.get(0); resolution = new int[] { src.width, src.height }; return resolution; } @@ -313,10 +329,6 @@ public class CamsodaModel extends AbstractModel { } } - public void setStreamUrl(String streamUrl) { - this.streamUrl = streamUrl; - } - public float getSortOrder() { return sortOrder; }