From fc6aeff94a3fd87a9091ced90d2e132f7e94ee78 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 21 Jan 2019 17:58:59 +0100 Subject: [PATCH] Implemt special player handling for fc2live --- client/src/main/java/ctbrec/ui/Player.java | 2 +- .../ui/sites/fc2live/Fc2LiveSiteUi.java | 30 ++++- .../java/ctbrec/sites/fc2live/Fc2Model.java | 109 +++++++++++++++--- 3 files changed, 119 insertions(+), 22 deletions(-) diff --git a/client/src/main/java/ctbrec/ui/Player.java b/client/src/main/java/ctbrec/ui/Player.java index 87a9e6b2..516a5ac4 100644 --- a/client/src/main/java/ctbrec/ui/Player.java +++ b/client/src/main/java/ctbrec/ui/Player.java @@ -75,7 +75,7 @@ public class Player { Collections.sort(sources); StreamSource best = sources.get(sources.size()-1); LOG.debug("Playing {}", best.getMediaPlaylistUrl()); - return Player.play(best.getMediaPlaylistUrl()); + return Player.play(best.getMediaPlaylistUrl(), async); } else { Platform.runLater(() -> { Alert alert = new AutosizeAlert(Alert.AlertType.INFORMATION); diff --git a/client/src/main/java/ctbrec/ui/sites/fc2live/Fc2LiveSiteUi.java b/client/src/main/java/ctbrec/ui/sites/fc2live/Fc2LiveSiteUi.java index a2976969..52ae25a8 100644 --- a/client/src/main/java/ctbrec/ui/sites/fc2live/Fc2LiveSiteUi.java +++ b/client/src/main/java/ctbrec/ui/sites/fc2live/Fc2LiveSiteUi.java @@ -2,15 +2,19 @@ package ctbrec.ui.sites.fc2live; import java.io.IOException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import ctbrec.Model; import ctbrec.sites.ConfigUI; import ctbrec.sites.fc2live.Fc2Live; +import ctbrec.sites.fc2live.Fc2Model; import ctbrec.ui.Player; import ctbrec.ui.TabProvider; import ctbrec.ui.sites.AbstractSiteUi; public class Fc2LiveSiteUi extends AbstractSiteUi { - + private static final transient Logger LOG = LoggerFactory.getLogger(Fc2LiveSiteUi.class); private Fc2Live fc2live; private Fc2TabProvider tabProvider; @@ -37,11 +41,25 @@ public class Fc2LiveSiteUi extends AbstractSiteUi { @Override public boolean play(Model model) { new Thread(() -> { - // create websocket - - Player.play(model, false); - - // close websocket + Fc2Model m = (Fc2Model) model; + try { + boolean opened = m.openWebsocket(); + if(opened) { + LOG.debug("Opened new websocket for player"); + } else { + LOG.debug("Using existing websocket for player"); + } + LOG.debug("Starting player"); + Player.play(model, false); + if(opened) { + LOG.debug("Closing websocket for player"); + m.closeWebsocket(); + } else { + LOG.debug("Leaving websocket for player open"); + } + } catch (InterruptedException | IOException e) { + LOG.error("Error opening websocket connection", e); + } }).start(); return true; } diff --git a/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java b/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java index b5b106da..28d605c1 100644 --- a/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java +++ b/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java @@ -8,6 +8,7 @@ import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.function.BiConsumer; +import org.json.JSONArray; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,6 +31,9 @@ import okhttp3.FormBody; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; +import okio.ByteString; public class Fc2Model extends AbstractModel { private static final transient Logger LOG = LoggerFactory.getLogger(Fc2Model.class); @@ -37,6 +41,8 @@ public class Fc2Model extends AbstractModel { private int viewerCount; private boolean online; private String version; + private WebSocket ws; + private String playlistUrl; @Override public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { @@ -91,22 +97,18 @@ public class Fc2Model extends AbstractModel { @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); + try { + boolean opened = openWebsocket(); + List sources = new ArrayList<>(); + LOG.debug("Paylist url {}", playlistUrl); + sources.addAll(parseMasterPlaylist(playlistUrl)); + if(opened) { + closeWebsocket(); } - }); - return sources; + return sources; + } catch (InterruptedException e1) { + throw new ExecutionException(e1); + } } private List parseMasterPlaylist(String playlistUrl) throws IOException, ParseException, PlaylistException { @@ -221,4 +223,81 @@ public class Fc2Model extends AbstractModel { public void setViewerCount(int viewerCount) { this.viewerCount = viewerCount; } + + /** + * Opens a chat websocket connection. This connection is used to retrieve the HLS playlist url. It also has to be kept open as long as the HLS stream is + * "played" + * + * @return true, if a new websocket connection is opened. If the connection was already open, this method returns false + * @throws IOException + */ + public boolean openWebsocket() throws InterruptedException, IOException { + if(ws != null) { + return false; + } else { + Object monitor = new Object(); + loadModelInfo(); + getControlToken((token, url) -> { + url = url + "?control_token=" + token; + LOG.debug("Session token: {}", token); + LOG.debug("Getting playlist token over websocket {}", url); + + Request request = new Request.Builder() + .url(url) + .header("User-Agent", Config.getInstance().getSettings().httpUserAgent) + .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + .header("Accept-Language", "de,en-US;q=0.7,en;q=0.3") + .build(); + ws = getSite().getHttpClient().newWebSocket(request, 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"); + LOG.debug("Master Playlist: {}", playlistUrl); + synchronized (monitor) { + monitor.notify(); + } + } + } + + @Override + public void onMessage(WebSocket webSocket, ByteString bytes) { + LOG.debug("ws btxt {}", bytes.toString()); + } + + @Override + public void onClosed(WebSocket webSocket, int code, String reason) { + LOG.debug("ws closed {} - {}", code, reason); + } + + @Override + public void onFailure(WebSocket webSocket, Throwable t, Response response) { + LOG.debug("ws failure", t); + response.close(); + } + }); + }); + synchronized (monitor) { + monitor.wait(); + } + return true; + } + } + + public boolean closeWebsocket() { + ws.close(1000, ""); + ws = null; + return true; + } }