From 713328303293ae8ac726d337ea102502ab18e2db Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 19 Nov 2018 23:20:39 +0100 Subject: [PATCH] Start implementation for FC2Live --- .../java/ctbrec/ui/CamrecApplication.java | 2 + .../main/java/ctbrec/ui/SiteUiFactory.java | 8 + .../ctbrec/ui/sites/fc2live/Fc2LiveUi.java | 35 +++ .../ui/sites/fc2live/Fc2TabProvider.java | 38 +++ .../ui/sites/fc2live/Fc2UpdateService.java | 86 +++++++ .../src/main/java/ctbrec/io/HttpClient.java | 7 + .../ctbrec/sites/fc2live/Fc2HttpClient.java | 106 +++++++++ .../java/ctbrec/sites/fc2live/Fc2Live.java | 91 +++++++ .../java/ctbrec/sites/fc2live/Fc2Model.java | 225 ++++++++++++++++++ .../sites/fc2live/Fc2WebSocketClient.java | 76 ++++++ 10 files changed, 674 insertions(+) create mode 100644 client/src/main/java/ctbrec/ui/sites/fc2live/Fc2LiveUi.java create mode 100644 client/src/main/java/ctbrec/ui/sites/fc2live/Fc2TabProvider.java create mode 100644 client/src/main/java/ctbrec/ui/sites/fc2live/Fc2UpdateService.java create mode 100644 common/src/main/java/ctbrec/sites/fc2live/Fc2HttpClient.java create mode 100644 common/src/main/java/ctbrec/sites/fc2live/Fc2Live.java create mode 100644 common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java create mode 100644 common/src/main/java/ctbrec/sites/fc2live/Fc2WebSocketClient.java diff --git a/client/src/main/java/ctbrec/ui/CamrecApplication.java b/client/src/main/java/ctbrec/ui/CamrecApplication.java index 9a4e3eb1..27f35fb7 100644 --- a/client/src/main/java/ctbrec/ui/CamrecApplication.java +++ b/client/src/main/java/ctbrec/ui/CamrecApplication.java @@ -31,6 +31,7 @@ import ctbrec.sites.bonga.BongaCams; import ctbrec.sites.cam4.Cam4; import ctbrec.sites.camsoda.Camsoda; import ctbrec.sites.chaturbate.Chaturbate; +import ctbrec.sites.fc2live.Fc2Live; import ctbrec.sites.mfc.MyFreeCams; import javafx.application.Application; import javafx.application.HostServices; @@ -66,6 +67,7 @@ public class CamrecApplication extends Application { sites.add(new Cam4()); sites.add(new Camsoda()); sites.add(new Chaturbate()); + sites.add(new Fc2Live()); sites.add(new MyFreeCams()); loadConfig(); createHttpClient(); diff --git a/client/src/main/java/ctbrec/ui/SiteUiFactory.java b/client/src/main/java/ctbrec/ui/SiteUiFactory.java index 94352c1e..ef1ad64c 100644 --- a/client/src/main/java/ctbrec/ui/SiteUiFactory.java +++ b/client/src/main/java/ctbrec/ui/SiteUiFactory.java @@ -5,11 +5,13 @@ import ctbrec.sites.bonga.BongaCams; import ctbrec.sites.cam4.Cam4; import ctbrec.sites.camsoda.Camsoda; import ctbrec.sites.chaturbate.Chaturbate; +import ctbrec.sites.fc2live.Fc2Live; import ctbrec.sites.mfc.MyFreeCams; import ctbrec.ui.sites.bonga.BongaCamsSiteUi; import ctbrec.ui.sites.cam4.Cam4SiteUi; import ctbrec.ui.sites.camsoda.CamsodaSiteUi; import ctbrec.ui.sites.chaturbate.ChaturbateSiteUi; +import ctbrec.ui.sites.fc2live.Fc2LiveUi; import ctbrec.ui.sites.myfreecams.MyFreeCamsSiteUi; public class SiteUiFactory { @@ -18,6 +20,7 @@ public class SiteUiFactory { private static Cam4SiteUi cam4SiteUi; private static CamsodaSiteUi camsodaSiteUi; private static ChaturbateSiteUi ctbSiteUi; + private static Fc2LiveUi fc2SiteUi; private static MyFreeCamsSiteUi mfcSiteUi; public static SiteUI getUi(Site site) { @@ -41,6 +44,11 @@ public class SiteUiFactory { ctbSiteUi = new ChaturbateSiteUi((Chaturbate) site); } return ctbSiteUi; + } else if (site instanceof Fc2Live) { + if (fc2SiteUi == null) { + fc2SiteUi = new Fc2LiveUi((Fc2Live) site); + } + return fc2SiteUi; } else if (site instanceof MyFreeCams) { if (mfcSiteUi == null) { mfcSiteUi = new MyFreeCamsSiteUi((MyFreeCams) site); diff --git a/client/src/main/java/ctbrec/ui/sites/fc2live/Fc2LiveUi.java b/client/src/main/java/ctbrec/ui/sites/fc2live/Fc2LiveUi.java new file mode 100644 index 00000000..bc7a61f2 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/sites/fc2live/Fc2LiveUi.java @@ -0,0 +1,35 @@ +package ctbrec.ui.sites.fc2live; + +import java.io.IOException; + +import ctbrec.sites.ConfigUI; +import ctbrec.sites.fc2live.Fc2Live; +import ctbrec.ui.SiteUI; +import ctbrec.ui.TabProvider; + +public class Fc2LiveUi implements SiteUI { + + private Fc2Live fc2live; + private Fc2TabProvider tabProvider; + + public Fc2LiveUi(Fc2Live fc2live) { + this.fc2live = fc2live; + this.tabProvider = new Fc2TabProvider(fc2live); + } + + @Override + public TabProvider getTabProvider() { + return tabProvider; + } + + @Override + public ConfigUI getConfigUI() { + return null; + } + + @Override + public boolean login() throws IOException { + return false; + } + +} diff --git a/client/src/main/java/ctbrec/ui/sites/fc2live/Fc2TabProvider.java b/client/src/main/java/ctbrec/ui/sites/fc2live/Fc2TabProvider.java new file mode 100644 index 00000000..d6e448c9 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/sites/fc2live/Fc2TabProvider.java @@ -0,0 +1,38 @@ +package ctbrec.ui.sites.fc2live; + +import java.util.ArrayList; +import java.util.List; + +import ctbrec.sites.fc2live.Fc2Live; +import ctbrec.ui.TabProvider; +import ctbrec.ui.ThumbOverviewTab; +import javafx.scene.Scene; +import javafx.scene.control.Tab; + +public class Fc2TabProvider extends TabProvider { + + private Fc2Live fc2live; + + public Fc2TabProvider(Fc2Live fc2live) { + this.fc2live = fc2live; + } + + @Override + public List getTabs(Scene scene) { + List tabs = new ArrayList<>(); + tabs.add(createTab("Online", Fc2Live.BASE_URL + "/adult/contents/allchannellist.php")); + return tabs; + } + + private Tab createTab(String title, String url) { + Fc2UpdateService updateService = new Fc2UpdateService(url, fc2live); + ThumbOverviewTab tab = new ThumbOverviewTab(title, updateService, fc2live); + tab.setRecorder(fc2live.getRecorder()); + return tab; + } + + @Override + public Tab getFollowedTab() { + return null; + } +} diff --git a/client/src/main/java/ctbrec/ui/sites/fc2live/Fc2UpdateService.java b/client/src/main/java/ctbrec/ui/sites/fc2live/Fc2UpdateService.java new file mode 100644 index 00000000..46c8cba7 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/sites/fc2live/Fc2UpdateService.java @@ -0,0 +1,86 @@ +package ctbrec.ui.sites.fc2live; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ctbrec.Config; +import ctbrec.Model; +import ctbrec.sites.fc2live.Fc2Live; +import ctbrec.sites.fc2live.Fc2Model; +import ctbrec.ui.PaginatedScheduledService; +import javafx.concurrent.Task; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class Fc2UpdateService extends PaginatedScheduledService { + private static final transient Logger LOG = LoggerFactory.getLogger(Fc2UpdateService.class); + + private String url; + private Fc2Live fc2live; + private int modelsPerPage = 30; + + public Fc2UpdateService(String url, Fc2Live fc2live) { + this.url = url; + this.fc2live = fc2live; + } + + @Override + protected Task> createTask() { + return new Task>() { + @Override + public List call() throws IOException { + RequestBody body = RequestBody.create(null, new byte[0]); + Request req = new Request.Builder() + .url(url) + .method("POST", body) + .header("Accept", "*/*") + .header("Accept-Language", "en-US,en;q=0.5") + .header("Referer", Fc2Live.BASE_URL) + .header("User-Agent", Config.getInstance().getSettings().httpUserAgent) + .header("X-Requested-With", "XMLHttpRequest") + .build(); + LOG.debug("Fetching page {}", url); + try(Response resp = fc2live.getHttpClient().execute(req)) { + if(resp.isSuccessful()) { + List models = new ArrayList<>(); + String msg = resp.body().string(); + JSONObject json = new JSONObject(msg); + JSONArray channels = json.getJSONArray("channel"); + for (int i = 0; i < channels.length(); i++) { + JSONObject channel = channels.getJSONObject(i); + Fc2Model model = (Fc2Model) fc2live.createModel(channel.getString("name")); + model.setId(channel.getString("id")); + model.setUrl(Fc2Live.BASE_URL + '/' + model.getId()); + String previewUrl = channel.getString("image"); + if(previewUrl == null || previewUrl.trim().isEmpty()) { + previewUrl = getClass().getResource("/image_not_found.png").toString(); + } + model.setPreview(previewUrl); + model.setDescription(channel.optString("title")); + model.setViewerCount(channel.optInt("count")); + if(channel.getInt("login") == 0) { + models.add(model); + } + } + return models.stream() + .sorted((m1, m2) -> m2.getViewerCount() - m1.getViewerCount()) + .skip( (page - 1) * modelsPerPage) + .limit(modelsPerPage) + .collect(Collectors.toList()); + } else { + resp.close(); + throw new IOException("HTTP status " + resp.code() + " " + resp.message()); + } + } + } + }; + } +} diff --git a/common/src/main/java/ctbrec/io/HttpClient.java b/common/src/main/java/ctbrec/io/HttpClient.java index df03cf52..2cc40372 100644 --- a/common/src/main/java/ctbrec/io/HttpClient.java +++ b/common/src/main/java/ctbrec/io/HttpClient.java @@ -30,6 +30,8 @@ import okhttp3.OkHttpClient.Builder; import okhttp3.Request; import okhttp3.Response; import okhttp3.Route; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; public abstract class HttpClient { private static final transient Logger LOG = LoggerFactory.getLogger(HttpClient.class); @@ -215,4 +217,9 @@ public abstract class HttpClient { public CookieJar getCookieJar() { return cookieJar; } + + public WebSocket newWebSocket(String url, WebSocketListener l) { + Request request = new Request.Builder().url(url).build(); + return client.newWebSocket(request, l); + } } diff --git a/common/src/main/java/ctbrec/sites/fc2live/Fc2HttpClient.java b/common/src/main/java/ctbrec/sites/fc2live/Fc2HttpClient.java new file mode 100644 index 00000000..db6139d1 --- /dev/null +++ b/common/src/main/java/ctbrec/sites/fc2live/Fc2HttpClient.java @@ -0,0 +1,106 @@ +package ctbrec.sites.fc2live; + +import java.io.IOException; + +import org.jsoup.select.Elements; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ctbrec.Config; +import ctbrec.io.HtmlParser; +import ctbrec.io.HttpClient; +import ctbrec.io.HttpException; +import ctbrec.sites.mfc.MyFreeCams; +import okhttp3.FormBody; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; + +public class Fc2HttpClient extends HttpClient { + + private static final transient Logger LOG = LoggerFactory.getLogger(Fc2HttpClient.class); + + public Fc2HttpClient() { + super("fc2live"); + } + + @Override + public boolean login() throws IOException { + if(loggedIn) { + return true; + } + + if(checkLogin()) { + loggedIn = true; + LOG.debug("Logged in with cookies"); + return true; + } + + String username = Config.getInstance().getSettings().mfcUsername; + String password = Config.getInstance().getSettings().mfcPassword; + RequestBody body = new FormBody.Builder() + .add("username", username) + .add("password", password) + .add("tz", "2") + .add("ss", "1920x1080") + .add("submit_login", "97") + .build(); + Request req = new Request.Builder() + .url(MyFreeCams.BASE_URI + "/php/login.php") + .header("Referer", MyFreeCams.BASE_URI) + .header("Content-Type", "application/x-www-form-urlencoded") + .post(body) + .build(); + Response resp = execute(req); + if(resp.isSuccessful()) { + String page = resp.body().string(); + if(page.contains("Your username or password are incorrect")) { + return false; + } else { + loggedIn = true; + return true; + } + } else { + resp.close(); + LOG.error("Login failed {} {}", resp.code(), resp.message()); + return false; + } + } + + private boolean checkLogin() throws IOException { + Request req = new Request.Builder().url(MyFreeCams.BASE_URI + "/php/account.php?request=status").build(); + try(Response response = execute(req)) { + if(response.isSuccessful()) { + String content = response.body().string(); + try { + Elements tags = HtmlParser.getTags(content, "div.content > p > b"); + tags.get(2).text(); + return true; + } catch(Exception e) { + LOG.debug("Token tag not found. Login failed"); + return false; + } + } else { + throw new HttpException(response.code(), response.message()); + } + } + } + + public WebSocket newWebSocket(Request req, WebSocketListener webSocketListener) { + return client.newWebSocket(req, webSocketListener); + } + + // public Cookie getCookie(String name) { + // CookieJar jar = client.cookieJar(); + // HttpUrl url = HttpUrl.parse(MyFreeCams.BASE_URI); + // List cookies = jar.loadForRequest(url); + // for (Cookie cookie : cookies) { + // if(Objects.equals(cookie.name(), name)) { + // return cookie; + // } + // } + // throw new NoSuchElementException("No cookie with name " + name); + // } +} diff --git a/common/src/main/java/ctbrec/sites/fc2live/Fc2Live.java b/common/src/main/java/ctbrec/sites/fc2live/Fc2Live.java new file mode 100644 index 00000000..9bf8aab2 --- /dev/null +++ b/common/src/main/java/ctbrec/sites/fc2live/Fc2Live.java @@ -0,0 +1,91 @@ +package ctbrec.sites.fc2live; + +import java.io.IOException; + +import ctbrec.Model; +import ctbrec.io.HttpClient; +import ctbrec.sites.AbstractSite; + +public class Fc2Live extends AbstractSite { + + public static final String BASE_URL = "https://live.fc2.com"; + private Fc2HttpClient httpClient; + + @Override + public String getName() { + return "FC2Live"; + } + + @Override + public String getBaseUrl() { + return BASE_URL; + } + + @Override + public String getAffiliateLink() { + return BASE_URL; + } + + @Override + public Model createModel(String name) { + Fc2Model model = new Fc2Model(); + model.setSite(this); + model.setName(name); + return model; + } + + @Override + public Integer getTokenBalance() throws IOException { + return 0; + } + + @Override + public String getBuyTokensLink() { + return BASE_URL; + } + + @Override + public boolean login() throws IOException { + return false; + } + + @Override + public HttpClient getHttpClient() { + if(httpClient == null) { + httpClient = new Fc2HttpClient(); + } + return httpClient; + } + + @Override + public void init() throws IOException { + } + + @Override + public void shutdown() { + if(httpClient != null) { + httpClient.shutdown(); + } + } + + @Override + public boolean supportsTips() { + return false; + } + + @Override + public boolean supportsFollow() { + return false; + } + + @Override + public boolean isSiteForModel(Model m) { + return m instanceof Fc2Model; + } + + @Override + public boolean credentialsAvailable() { + return false; + } + +} diff --git a/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java b/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java new file mode 100644 index 00000000..fb263585 --- /dev/null +++ b/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java @@ -0,0 +1,225 @@ +package ctbrec.sites.fc2live; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.function.BiConsumer; + +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.iheartradio.m3u8.Encoding; +import com.iheartradio.m3u8.Format; +import com.iheartradio.m3u8.ParseException; +import com.iheartradio.m3u8.PlaylistException; +import com.iheartradio.m3u8.PlaylistParser; +import com.iheartradio.m3u8.data.MasterPlaylist; +import com.iheartradio.m3u8.data.Playlist; +import com.iheartradio.m3u8.data.PlaylistData; +import com.iheartradio.m3u8.data.StreamInfo; + +import ctbrec.AbstractModel; +import ctbrec.Config; +import ctbrec.io.HttpException; +import ctbrec.recorder.download.StreamSource; +import okhttp3.FormBody; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class Fc2Model extends AbstractModel { + private static final transient Logger LOG = LoggerFactory.getLogger(Fc2Model.class); + private String id; + private int viewerCount; + private boolean online; + private String onlineState = "n/a"; + private String version; + + @Override + public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { + if(ignoreCache) { + loadModelInfo(); + } + return online; + } + + private void loadModelInfo() throws IOException { + String url = Fc2Live.BASE_URL + "/api/memberApi.php"; + RequestBody body = new FormBody.Builder() + .add("channel", "1") + .add("profile", "1") + .add("streamid", id) + .build(); + Request req = new Request.Builder() + .url(url) + .method("POST", body) + .header("Accept", "*/*") + .header("Accept-Language", "en-US,en;q=0.5") + .header("Referer", Fc2Live.BASE_URL) + .header("User-Agent", Config.getInstance().getSettings().httpUserAgent) + .header("X-Requested-With", "XMLHttpRequest") + .build(); + LOG.debug("Fetching page {}", url); + try(Response resp = getSite().getHttpClient().execute(req)) { + if(resp.isSuccessful()) { + String msg = resp.body().string(); + JSONObject json = new JSONObject(msg); + JSONObject data = json.getJSONObject("data"); + JSONObject channelData = data.getJSONObject("channel_data"); + online = channelData.optInt("is_publish") == 1; + onlineState = online ? "online" : "offline"; + version = channelData.optString("version"); + } else { + resp.close(); + throw new IOException("HTTP status " + resp.code() + " " + resp.message()); + } + } + } + + @Override + public String getOnlineState(boolean failFast) throws IOException, ExecutionException { + if(failFast) { + return onlineState; + } else if(Objects.equals(onlineState, "n/a")){ + loadModelInfo(); + } + return onlineState; + } + + @Override + public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException { + loadModelInfo(); + List sources = new ArrayList<>(); + getControlToken((token, url) -> { + url = url + "?control_token=" + token; + LOG.debug("Session token: {}", token); + LOG.debug("Getting playlist token over websocket {}", url); + Fc2WebSocketClient wsClient = new Fc2WebSocketClient(url, getSite().getHttpClient()); + try { + String playlistUrl = wsClient.getPlaylistUrl(); + LOG.debug("Paylist url {}", playlistUrl); + sources.addAll(parseMasterPlaylist(playlistUrl)); + } catch (InterruptedException | IOException | ParseException | PlaylistException e) { + LOG.error("Couldn't fetch stream information", e); + } + }); + return sources; + } + + private List parseMasterPlaylist(String playlistUrl) throws IOException, ParseException, PlaylistException { + List sources = new ArrayList<>(); + Request req = new Request.Builder() + .url(playlistUrl) + .header("Accept", "*/*") + .header("Accept-Language", "en-US,en;q=0.5") + .header("User-Agent", Config.getInstance().getSettings().httpUserAgent) + .header("Origin", Fc2Live.BASE_URL) + .header("Referer", getUrl()) + .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); + Playlist playlist = parser.parse(); + MasterPlaylist master = playlist.getMasterPlaylist(); + sources.clear(); + for (PlaylistData playlistData : master.getPlaylists()) { + StreamSource streamsource = new StreamSource(); + streamsource.mediaPlaylistUrl = playlistData.getUri(); + 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; + } + sources.add(streamsource); + } + LOG.debug(sources.toString()); + return sources; + } else { + throw new HttpException(response.code(), response.message()); + } + } + } + + private void getControlToken(BiConsumer callback) throws IOException { + String url = Fc2Live.BASE_URL + "/api/getControlServer.php"; + RequestBody body = new FormBody.Builder() + .add("channel_id", id) + .add("channel_version", version) + .add("client_app", "browser_hls") + .add("client_type", "pc") + .add("client_version", "1.6.0 [1]") + .add("mode", "play") + .build(); + Request req = new Request.Builder() + .url(url) + .method("POST", body) + .header("Accept", "*/*") + .header("Accept-Language", "en-US,en;q=0.5") + .header("Referer", Fc2Live.BASE_URL) + .header("User-Agent", Config.getInstance().getSettings().httpUserAgent) + .header("X-Requested-With", "XMLHttpRequest") + .build(); + LOG.debug("Fetching page {}", url); + try(Response resp = getSite().getHttpClient().execute(req)) { + if(resp.isSuccessful()) { + String msg = resp.body().string(); + JSONObject json = new JSONObject(msg); + String wssurl = json.getString("url"); + String token = json.getString("control_token"); + callback.accept(token, wssurl); + } else { + resp.close(); + throw new IOException("HTTP status " + resp.code() + " " + resp.message()); + } + } + } + + @Override + public void invalidateCacheEntries() { + } + + @Override + public void receiveTip(int tokens) throws IOException { + } + + @Override + public int[] getStreamResolution(boolean failFast) throws ExecutionException { + return new int[2]; + } + + @Override + public boolean follow() throws IOException { + return false; + } + + @Override + public boolean unfollow() throws IOException { + return false; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public int getViewerCount() { + return viewerCount; + } + + public void setViewerCount(int viewerCount) { + this.viewerCount = viewerCount; + } +} diff --git a/common/src/main/java/ctbrec/sites/fc2live/Fc2WebSocketClient.java b/common/src/main/java/ctbrec/sites/fc2live/Fc2WebSocketClient.java new file mode 100644 index 00000000..32965998 --- /dev/null +++ b/common/src/main/java/ctbrec/sites/fc2live/Fc2WebSocketClient.java @@ -0,0 +1,76 @@ +package ctbrec.sites.fc2live; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ctbrec.io.HttpClient; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; +import okio.ByteString; + +public class Fc2WebSocketClient { + + private static final transient Logger LOG = LoggerFactory.getLogger(Fc2WebSocketClient.class); + private String url; + private HttpClient client; + + public Fc2WebSocketClient(String url, HttpClient client) { + this.url = url; + this.client = client; + } + + String playlistUrl = ""; + public String getPlaylistUrl() throws InterruptedException { + LOG.debug("Connecting to {}", url); + Object monitor = new Object(); + client.newWebSocket(url, new WebSocketListener() { + @Override + public void onOpen(WebSocket webSocket, Response response) { + response.close(); + webSocket.send("{\"name\":\"get_hls_information\",\"arguments\":{},\"id\":1}"); + } + + @Override + public void onMessage(WebSocket webSocket, String text) { + JSONObject json = new JSONObject(text); + if(json.optString("name").equals("_response_") && json.optInt("id") == 1) { + LOG.debug(json.toString(2)); + JSONObject args = json.getJSONObject("arguments"); + JSONArray playlists = args.getJSONArray("playlists_high_latency"); + JSONObject playlist = playlists.getJSONObject(0); + playlistUrl = playlist.getString("url"); + webSocket.close(1000, ""); + } + } + + @Override + public void onMessage(WebSocket webSocket, ByteString bytes) { + LOG.debug("ws btxt {}", bytes.toString()); + } + + @Override + public void onClosed(WebSocket webSocket, int code, String reason) { + synchronized (monitor) { + monitor.notify(); + } + } + + @Override + public void onFailure(WebSocket webSocket, Throwable t, Response response) { + LOG.debug("ws failure", t); + response.close(); + synchronized (monitor) { + monitor.notify(); + } + } + }); + synchronized (monitor) { + monitor.wait(); + } + return playlistUrl; + } +} +