diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLive.java b/common/src/main/java/ctbrec/sites/manyvids/MVLive.java index 73b2ca4e..e3cd81ad 100644 --- a/common/src/main/java/ctbrec/sites/manyvids/MVLive.java +++ b/common/src/main/java/ctbrec/sites/manyvids/MVLive.java @@ -62,11 +62,6 @@ public class MVLive extends AbstractSite { return httpClient; } - @Override - public void init() throws IOException { - MVLiveClient.getInstance().setSite(this); - } - @Override public void shutdown() { if (httpClient != null) { @@ -94,4 +89,9 @@ public class MVLive extends AbstractSite { return false; } + @Override + public void init() throws IOException { + + } + } diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java index 0d136958..02b44195 100644 --- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java +++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java @@ -10,6 +10,8 @@ import java.util.Map; import java.util.Optional; import java.util.Random; import java.util.UUID; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.json.JSONArray; @@ -37,8 +39,6 @@ public class MVLiveClient { private static final Logger LOG = LoggerFactory.getLogger(MVLiveClient.class); - private static MVLiveClient instance; - private MVLive site; private WebSocket ws; private Random rng = new Random(); private volatile boolean running = false; @@ -47,19 +47,12 @@ public class MVLiveClient { private String masterPlaylist = null; private String roomNumber; private String roomId; + private ScheduledExecutorService scheduler; - private MVLiveClient() { - } + private MVLiveHttpClient httpClient; - public static synchronized MVLiveClient getInstance() { - if (instance == null) { - instance = new MVLiveClient(); - } - return instance; - } - - public void setSite(MVLive site) { - this.site = site; + public MVLiveClient(MVLiveHttpClient httpClient) { + this.httpClient = httpClient; } public void start(MVLiveModel model) throws IOException { @@ -86,7 +79,7 @@ public class MVLiveClient { .header(ACCEPT, MIMETYPE_APPLICATION_JSON) .header(COOKIE, getPhpSessionIdCookie()) .build(); - try (Response response = site.getHttpClient().execute(req)) { + try (Response response = httpClient.execute(req)) { if (response.isSuccessful()) { return new JSONObject(response.body().string()); } else { @@ -100,7 +93,7 @@ public class MVLiveClient { .url("https://www.manyvids.com/tak-live-redirect.php") .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .build(); - try (Response response = site.getHttpClient().execute(req)) { + try (Response response = httpClient.execute(req)) { if (!response.isSuccessful()) { throw new HttpException(response.code(), response.message()); } @@ -108,12 +101,13 @@ public class MVLiveClient { } private String getPhpSessionIdCookie() { - List cookies = site.getHttpClient().getCookiesByName("PHPSESSID"); + List cookies = httpClient.getCookiesByName("PHPSESSID"); return cookies.stream().map(c -> c.name() + "=" + c.value()).findFirst().orElse(""); } public void stop() { running = false; + scheduler.shutdown(); ws.close(1000, "Good Bye"); // terminate normally (1000) ws = null; } @@ -126,13 +120,15 @@ public class MVLiveClient { .header(ORIGIN, WS_ORIGIN) .header(COOKIE, getPhpSessionIdCookie()) .build(); - WebSocket websocket = site.getHttpClient().newWebSocket(req, new WebSocketListener() { + WebSocket websocket = httpClient.newWebSocket(req, new WebSocketListener() { @Override public void onOpen(WebSocket webSocket, Response response) { super.onOpen(webSocket, response); try { connecting = false; LOG.debug("WS open: [{}]", response.body().string()); + scheduler = new ScheduledThreadPoolExecutor(1); + scheduler.scheduleAtFixedRate(() -> sendMessages(new Ping()), 5, 5, TimeUnit.SECONDS); } catch (IOException e) { LOG.error("Error while processing onOpen event", e); } @@ -186,7 +182,7 @@ public class MVLiveClient { LOG.debug("<-- {}", rr); masterPlaylist = rr.getJSONObject("body").optString("videoUrl"); LOG.debug("Got the URL: {}", masterPlaylist); - stop(); + //stop(); synchronized (streamUrlMonitor) { streamUrlMonitor.notifyAll(); } @@ -242,12 +238,12 @@ public class MVLiveClient { while (running) { synchronized (streamUrlMonitor) { streamUrlMonitor.wait(TimeUnit.SECONDS.toMillis(20000)); - running = false; + break; } } - if (ws != null) { - stop(); - } + // if (ws != null) { + // stop(); + // } return new StreamLocation(roomId, roomNumber, masterPlaylist); } } diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveHttpClient.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveHttpClient.java index 63032193..e85f609a 100644 --- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveHttpClient.java +++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveHttpClient.java @@ -14,5 +14,4 @@ public class MVLiveHttpClient extends HttpClient { public boolean login() throws IOException { return false; } - } diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveMergedHlsDownload.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveMergedHlsDownload.java new file mode 100644 index 00000000..8c9cdd10 --- /dev/null +++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveMergedHlsDownload.java @@ -0,0 +1,52 @@ +package ctbrec.sites.manyvids; + +import java.io.IOException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ctbrec.io.HttpClient; +import ctbrec.recorder.download.hls.MergedFfmpegHlsDownload; + +public class MVLiveMergedHlsDownload extends MergedFfmpegHlsDownload { + + private static final Logger LOG = LoggerFactory.getLogger(MVLiveMergedHlsDownload.class); + + private ScheduledExecutorService scheduler; + + public MVLiveMergedHlsDownload(HttpClient client) { + super(client); + } + + @Override + public void start() throws IOException { + try { + scheduler = new ScheduledThreadPoolExecutor(1, r -> { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("MVLive CF cookie updater"); + t.setPriority(Thread.MIN_PRIORITY); + return t; + }); + scheduler.scheduleAtFixedRate(() -> updateCloudFlareCookies(), 2, 2, TimeUnit.MINUTES); + super.start(); + } finally { + scheduler.shutdown(); + + } + } + + private void updateCloudFlareCookies() { + try { + ((MVLiveModel)getModel()).updateCloudFlareCookies(); + } catch (IOException e) { + LOG.error("Couldn't update cloudflare cookies for model {}", getModel(), e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOG.error("Couldn't update cloudflare cookies for model {}", getModel(), e); + } + } +} diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java index 8e702fb8..3adebaf2 100644 --- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java +++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java @@ -27,7 +27,9 @@ import com.iheartradio.m3u8.data.PlaylistData; import ctbrec.AbstractModel; import ctbrec.Config; import ctbrec.io.HttpException; +import ctbrec.recorder.download.Download; import ctbrec.recorder.download.StreamSource; +import ctbrec.recorder.download.hls.HlsDownload; import okhttp3.Request; import okhttp3.Response; @@ -35,19 +37,23 @@ public class MVLiveModel extends AbstractModel { private static final transient Logger LOG = LoggerFactory.getLogger(MVLiveModel.class); + private MVLiveClient client; + private String roomNumber; + @Override public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { - //return getOnlineState(true) == Model.State.ONLINE; + // TODO return getOnlineState(true) == Model.State.ONLINE; return true; } @Override public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException { LOG.debug("Loading {}", getUrl()); - MVLiveClient client = MVLiveClient.getInstance(); try { - StreamLocation streamLocation = client.getStreamLocation(this); - getCloudFlareCookies(streamLocation.roomNumber); + StreamLocation streamLocation = getClient().getStreamLocation(this); + LOG.debug("Got the stream location from WS {}", streamLocation.masterPlaylist); + roomNumber = streamLocation.roomNumber; + updateCloudFlareCookies(); MasterPlaylist masterPlaylist = getMasterPlaylist(streamLocation.masterPlaylist); List sources = new ArrayList<>(); for (PlaylistData playlist : masterPlaylist.getPlaylists()) { @@ -94,8 +100,8 @@ public class MVLiveModel extends AbstractModel { } } - private void getCloudFlareCookies(String roomNumber) throws IOException { - String url = MVLive.WS_ORIGIN + "/api/" + roomNumber + "/player-settings/" + getName(); + public void updateCloudFlareCookies() throws IOException, InterruptedException { + String url = MVLive.WS_ORIGIN + "/api/" + getRoomNumber() + "/player-settings/" + getName(); LOG.debug("Getting CF cookies: {}", url); Request req = new Request.Builder() .url(url) @@ -104,14 +110,33 @@ public class MVLiveModel extends AbstractModel { try (Response response = site.getHttpClient().execute(req)) { if (!response.isSuccessful()) { throw new HttpException(response.code(), response.message()); - } else { - LOG.debug("headers: {}", response.headers()); } + // else { + // LOG.debug("headers: {}", response.headers()); + // } } } + public String getRoomNumber() throws IOException, InterruptedException { + if (roomNumber == null) { + StreamLocation streamLocation = getClient().getStreamLocation(this); + roomNumber = streamLocation.roomNumber; + } + return roomNumber; + } + + private synchronized MVLiveClient getClient() { + if (client == null) { + MVLive site = (MVLive) getSite(); + MVLiveHttpClient httpClient = (MVLiveHttpClient) site.getHttpClient(); + client = new MVLiveClient(httpClient); + } + return client; + } + @Override public void invalidateCacheEntries() { + roomNumber = null; } @Override @@ -132,4 +157,13 @@ public class MVLiveModel extends AbstractModel { public boolean unfollow() throws IOException { return false; } + + @Override + public Download createDownload() { + if (Config.isServerMode() && !Config.getInstance().getSettings().recordSingleFile) { + return new HlsDownload(getSite().getHttpClient()); + } else { + return new MVLiveMergedHlsDownload(getSite().getHttpClient()); + } + } }