forked from j62/ctbrec
Add regular cloudflare cookie updates
MVLive updates those cookies every 2 minutes. This makes sure, that the streaming continues. IF the cookies are not updated, the stream ends after around 2:30 minutes
This commit is contained in:
parent
db186e65f4
commit
36885900b5
|
@ -62,11 +62,6 @@ public class MVLive extends AbstractSite {
|
||||||
return httpClient;
|
return httpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init() throws IOException {
|
|
||||||
MVLiveClient.getInstance().setSite(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
if (httpClient != null) {
|
if (httpClient != null) {
|
||||||
|
@ -94,4 +89,9 @@ public class MVLive extends AbstractSite {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() throws IOException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
|
@ -37,8 +39,6 @@ public class MVLiveClient {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MVLiveClient.class);
|
private static final Logger LOG = LoggerFactory.getLogger(MVLiveClient.class);
|
||||||
|
|
||||||
private static MVLiveClient instance;
|
|
||||||
private MVLive site;
|
|
||||||
private WebSocket ws;
|
private WebSocket ws;
|
||||||
private Random rng = new Random();
|
private Random rng = new Random();
|
||||||
private volatile boolean running = false;
|
private volatile boolean running = false;
|
||||||
|
@ -47,19 +47,12 @@ public class MVLiveClient {
|
||||||
private String masterPlaylist = null;
|
private String masterPlaylist = null;
|
||||||
private String roomNumber;
|
private String roomNumber;
|
||||||
private String roomId;
|
private String roomId;
|
||||||
|
private ScheduledExecutorService scheduler;
|
||||||
|
|
||||||
private MVLiveClient() {
|
private MVLiveHttpClient httpClient;
|
||||||
}
|
|
||||||
|
|
||||||
public static synchronized MVLiveClient getInstance() {
|
public MVLiveClient(MVLiveHttpClient httpClient) {
|
||||||
if (instance == null) {
|
this.httpClient = httpClient;
|
||||||
instance = new MVLiveClient();
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSite(MVLive site) {
|
|
||||||
this.site = site;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start(MVLiveModel model) throws IOException {
|
public void start(MVLiveModel model) throws IOException {
|
||||||
|
@ -86,7 +79,7 @@ public class MVLiveClient {
|
||||||
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
|
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
|
||||||
.header(COOKIE, getPhpSessionIdCookie())
|
.header(COOKIE, getPhpSessionIdCookie())
|
||||||
.build();
|
.build();
|
||||||
try (Response response = site.getHttpClient().execute(req)) {
|
try (Response response = httpClient.execute(req)) {
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
return new JSONObject(response.body().string());
|
return new JSONObject(response.body().string());
|
||||||
} else {
|
} else {
|
||||||
|
@ -100,7 +93,7 @@ public class MVLiveClient {
|
||||||
.url("https://www.manyvids.com/tak-live-redirect.php")
|
.url("https://www.manyvids.com/tak-live-redirect.php")
|
||||||
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||||
.build();
|
.build();
|
||||||
try (Response response = site.getHttpClient().execute(req)) {
|
try (Response response = httpClient.execute(req)) {
|
||||||
if (!response.isSuccessful()) {
|
if (!response.isSuccessful()) {
|
||||||
throw new HttpException(response.code(), response.message());
|
throw new HttpException(response.code(), response.message());
|
||||||
}
|
}
|
||||||
|
@ -108,12 +101,13 @@ public class MVLiveClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getPhpSessionIdCookie() {
|
private String getPhpSessionIdCookie() {
|
||||||
List<Cookie> cookies = site.getHttpClient().getCookiesByName("PHPSESSID");
|
List<Cookie> cookies = httpClient.getCookiesByName("PHPSESSID");
|
||||||
return cookies.stream().map(c -> c.name() + "=" + c.value()).findFirst().orElse("");
|
return cookies.stream().map(c -> c.name() + "=" + c.value()).findFirst().orElse("");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
running = false;
|
running = false;
|
||||||
|
scheduler.shutdown();
|
||||||
ws.close(1000, "Good Bye"); // terminate normally (1000)
|
ws.close(1000, "Good Bye"); // terminate normally (1000)
|
||||||
ws = null;
|
ws = null;
|
||||||
}
|
}
|
||||||
|
@ -126,13 +120,15 @@ public class MVLiveClient {
|
||||||
.header(ORIGIN, WS_ORIGIN)
|
.header(ORIGIN, WS_ORIGIN)
|
||||||
.header(COOKIE, getPhpSessionIdCookie())
|
.header(COOKIE, getPhpSessionIdCookie())
|
||||||
.build();
|
.build();
|
||||||
WebSocket websocket = site.getHttpClient().newWebSocket(req, new WebSocketListener() {
|
WebSocket websocket = httpClient.newWebSocket(req, new WebSocketListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onOpen(WebSocket webSocket, Response response) {
|
public void onOpen(WebSocket webSocket, Response response) {
|
||||||
super.onOpen(webSocket, response);
|
super.onOpen(webSocket, response);
|
||||||
try {
|
try {
|
||||||
connecting = false;
|
connecting = false;
|
||||||
LOG.debug("WS open: [{}]", response.body().string());
|
LOG.debug("WS open: [{}]", response.body().string());
|
||||||
|
scheduler = new ScheduledThreadPoolExecutor(1);
|
||||||
|
scheduler.scheduleAtFixedRate(() -> sendMessages(new Ping()), 5, 5, TimeUnit.SECONDS);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Error while processing onOpen event", e);
|
LOG.error("Error while processing onOpen event", e);
|
||||||
}
|
}
|
||||||
|
@ -186,7 +182,7 @@ public class MVLiveClient {
|
||||||
LOG.debug("<-- {}", rr);
|
LOG.debug("<-- {}", rr);
|
||||||
masterPlaylist = rr.getJSONObject("body").optString("videoUrl");
|
masterPlaylist = rr.getJSONObject("body").optString("videoUrl");
|
||||||
LOG.debug("Got the URL: {}", masterPlaylist);
|
LOG.debug("Got the URL: {}", masterPlaylist);
|
||||||
stop();
|
//stop();
|
||||||
synchronized (streamUrlMonitor) {
|
synchronized (streamUrlMonitor) {
|
||||||
streamUrlMonitor.notifyAll();
|
streamUrlMonitor.notifyAll();
|
||||||
}
|
}
|
||||||
|
@ -242,12 +238,12 @@ public class MVLiveClient {
|
||||||
while (running) {
|
while (running) {
|
||||||
synchronized (streamUrlMonitor) {
|
synchronized (streamUrlMonitor) {
|
||||||
streamUrlMonitor.wait(TimeUnit.SECONDS.toMillis(20000));
|
streamUrlMonitor.wait(TimeUnit.SECONDS.toMillis(20000));
|
||||||
running = false;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ws != null) {
|
// if (ws != null) {
|
||||||
stop();
|
// stop();
|
||||||
}
|
// }
|
||||||
return new StreamLocation(roomId, roomNumber, masterPlaylist);
|
return new StreamLocation(roomId, roomNumber, masterPlaylist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,5 +14,4 @@ public class MVLiveHttpClient extends HttpClient {
|
||||||
public boolean login() throws IOException {
|
public boolean login() throws IOException {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,7 +27,9 @@ import com.iheartradio.m3u8.data.PlaylistData;
|
||||||
import ctbrec.AbstractModel;
|
import ctbrec.AbstractModel;
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.io.HttpException;
|
import ctbrec.io.HttpException;
|
||||||
|
import ctbrec.recorder.download.Download;
|
||||||
import ctbrec.recorder.download.StreamSource;
|
import ctbrec.recorder.download.StreamSource;
|
||||||
|
import ctbrec.recorder.download.hls.HlsDownload;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
@ -35,19 +37,23 @@ public class MVLiveModel extends AbstractModel {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(MVLiveModel.class);
|
private static final transient Logger LOG = LoggerFactory.getLogger(MVLiveModel.class);
|
||||||
|
|
||||||
|
private MVLiveClient client;
|
||||||
|
private String roomNumber;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
|
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
|
||||||
//return getOnlineState(true) == Model.State.ONLINE;
|
// TODO return getOnlineState(true) == Model.State.ONLINE;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
|
public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
|
||||||
LOG.debug("Loading {}", getUrl());
|
LOG.debug("Loading {}", getUrl());
|
||||||
MVLiveClient client = MVLiveClient.getInstance();
|
|
||||||
try {
|
try {
|
||||||
StreamLocation streamLocation = client.getStreamLocation(this);
|
StreamLocation streamLocation = getClient().getStreamLocation(this);
|
||||||
getCloudFlareCookies(streamLocation.roomNumber);
|
LOG.debug("Got the stream location from WS {}", streamLocation.masterPlaylist);
|
||||||
|
roomNumber = streamLocation.roomNumber;
|
||||||
|
updateCloudFlareCookies();
|
||||||
MasterPlaylist masterPlaylist = getMasterPlaylist(streamLocation.masterPlaylist);
|
MasterPlaylist masterPlaylist = getMasterPlaylist(streamLocation.masterPlaylist);
|
||||||
List<StreamSource> sources = new ArrayList<>();
|
List<StreamSource> sources = new ArrayList<>();
|
||||||
for (PlaylistData playlist : masterPlaylist.getPlaylists()) {
|
for (PlaylistData playlist : masterPlaylist.getPlaylists()) {
|
||||||
|
@ -94,8 +100,8 @@ public class MVLiveModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getCloudFlareCookies(String roomNumber) throws IOException {
|
public void updateCloudFlareCookies() throws IOException, InterruptedException {
|
||||||
String url = MVLive.WS_ORIGIN + "/api/" + roomNumber + "/player-settings/" + getName();
|
String url = MVLive.WS_ORIGIN + "/api/" + getRoomNumber() + "/player-settings/" + getName();
|
||||||
LOG.debug("Getting CF cookies: {}", url);
|
LOG.debug("Getting CF cookies: {}", url);
|
||||||
Request req = new Request.Builder()
|
Request req = new Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
|
@ -104,14 +110,33 @@ public class MVLiveModel extends AbstractModel {
|
||||||
try (Response response = site.getHttpClient().execute(req)) {
|
try (Response response = site.getHttpClient().execute(req)) {
|
||||||
if (!response.isSuccessful()) {
|
if (!response.isSuccessful()) {
|
||||||
throw new HttpException(response.code(), response.message());
|
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
|
@Override
|
||||||
public void invalidateCacheEntries() {
|
public void invalidateCacheEntries() {
|
||||||
|
roomNumber = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -132,4 +157,13 @@ public class MVLiveModel extends AbstractModel {
|
||||||
public boolean unfollow() throws IOException {
|
public boolean unfollow() throws IOException {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Download createDownload() {
|
||||||
|
if (Config.isServerMode() && !Config.getInstance().getSettings().recordSingleFile) {
|
||||||
|
return new HlsDownload(getSite().getHttpClient());
|
||||||
|
} else {
|
||||||
|
return new MVLiveMergedHlsDownload(getSite().getHttpClient());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue