diff --git a/client/src/main/java/ctbrec/ui/CamrecApplication.java b/client/src/main/java/ctbrec/ui/CamrecApplication.java index d62e01f5..82c8d66f 100644 --- a/client/src/main/java/ctbrec/ui/CamrecApplication.java +++ b/client/src/main/java/ctbrec/ui/CamrecApplication.java @@ -46,6 +46,7 @@ import ctbrec.sites.fc2live.Fc2Live; import ctbrec.sites.flirt4free.Flirt4Free; import ctbrec.sites.jasmin.LiveJasmin; import ctbrec.sites.mfc.MyFreeCams; +import ctbrec.sites.showup.Showup; import ctbrec.sites.streamate.Streamate; import ctbrec.sites.stripchat.Stripchat; import ctbrec.ui.news.NewsTab; @@ -98,6 +99,7 @@ public class CamrecApplication extends Application { sites.add(new Flirt4Free()); sites.add(new LiveJasmin()); sites.add(new MyFreeCams()); + sites.add(new Showup()); sites.add(new Streamate()); sites.add(new Stripchat()); loadConfig(); diff --git a/client/src/main/java/ctbrec/ui/SiteUiFactory.java b/client/src/main/java/ctbrec/ui/SiteUiFactory.java index 65ec1496..5cc25734 100644 --- a/client/src/main/java/ctbrec/ui/SiteUiFactory.java +++ b/client/src/main/java/ctbrec/ui/SiteUiFactory.java @@ -9,6 +9,7 @@ import ctbrec.sites.fc2live.Fc2Live; import ctbrec.sites.flirt4free.Flirt4Free; import ctbrec.sites.jasmin.LiveJasmin; import ctbrec.sites.mfc.MyFreeCams; +import ctbrec.sites.showup.Showup; import ctbrec.sites.streamate.Streamate; import ctbrec.sites.stripchat.Stripchat; import ctbrec.ui.sites.bonga.BongaCamsSiteUi; @@ -19,6 +20,7 @@ import ctbrec.ui.sites.fc2live.Fc2LiveSiteUi; import ctbrec.ui.sites.flirt4free.Flirt4FreeSiteUi; import ctbrec.ui.sites.jasmin.LiveJasminSiteUi; import ctbrec.ui.sites.myfreecams.MyFreeCamsSiteUi; +import ctbrec.ui.sites.showup.ShowupSiteUi; import ctbrec.ui.sites.streamate.StreamateSiteUi; import ctbrec.ui.sites.stripchat.StripchatSiteUi; @@ -32,6 +34,7 @@ public class SiteUiFactory { private static Flirt4FreeSiteUi flirt4FreeSiteUi; private static LiveJasminSiteUi jasminSiteUi; private static MyFreeCamsSiteUi mfcSiteUi; + private static ShowupSiteUi showupSiteUi; private static StreamateSiteUi streamateSiteUi; private static StripchatSiteUi stripchatSiteUi; @@ -73,6 +76,11 @@ public class SiteUiFactory { mfcSiteUi = new MyFreeCamsSiteUi((MyFreeCams) site); } return mfcSiteUi; + } else if (site instanceof Showup) { + if (showupSiteUi == null) { + showupSiteUi = new ShowupSiteUi((Showup) site); + } + return showupSiteUi; } else if (site instanceof Streamate) { if (streamateSiteUi == null) { streamateSiteUi = new StreamateSiteUi((Streamate) site); diff --git a/client/src/main/java/ctbrec/ui/sites/showup/ShowupSiteUi.java b/client/src/main/java/ctbrec/ui/sites/showup/ShowupSiteUi.java new file mode 100644 index 00000000..bd35d731 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/sites/showup/ShowupSiteUi.java @@ -0,0 +1,33 @@ +package ctbrec.ui.sites.showup; + +import java.io.IOException; + +import ctbrec.sites.ConfigUI; +import ctbrec.sites.showup.Showup; +import ctbrec.ui.sites.AbstractSiteUi; +import ctbrec.ui.tabs.TabProvider; + +public class ShowupSiteUi extends AbstractSiteUi { + + private Showup site; + + public ShowupSiteUi(Showup site) { + this.site = site; + } + + @Override + public TabProvider getTabProvider() { + return new ShowupTabProvider(site); + } + + @Override + public ConfigUI getConfigUI() { + return null; + } + + @Override + public boolean login() throws IOException { + return false; + } + +} diff --git a/client/src/main/java/ctbrec/ui/sites/showup/ShowupTabProvider.java b/client/src/main/java/ctbrec/ui/sites/showup/ShowupTabProvider.java new file mode 100644 index 00000000..5831216c --- /dev/null +++ b/client/src/main/java/ctbrec/ui/sites/showup/ShowupTabProvider.java @@ -0,0 +1,40 @@ +package ctbrec.ui.sites.showup; + +import java.util.ArrayList; +import java.util.List; + +import ctbrec.sites.showup.Showup; +import ctbrec.ui.tabs.TabProvider; +import ctbrec.ui.tabs.ThumbOverviewTab; +import javafx.scene.Scene; +import javafx.scene.control.Tab; + +public class ShowupTabProvider extends TabProvider { + + private Showup site; + + public ShowupTabProvider(Showup site) { + this.site = site; + } + + @Override + public List getTabs(Scene scene) { + List tabs = new ArrayList<>(); + tabs.add(createTab("Women", "female")); + tabs.add(createTab("Men", "male")); + tabs.add(createTab("All", "all")); + return tabs; + } + + @Override + public Tab getFollowedTab() { + return null; + } + + private Tab createTab(String title, String category) { + ShowupUpdateService updateService = new ShowupUpdateService(site, category); + ThumbOverviewTab tab = new ThumbOverviewTab(title, updateService, site); + tab.setRecorder(site.getRecorder()); + return tab; + } +} diff --git a/client/src/main/java/ctbrec/ui/sites/showup/ShowupUpdateService.java b/client/src/main/java/ctbrec/ui/sites/showup/ShowupUpdateService.java new file mode 100644 index 00000000..02030888 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/sites/showup/ShowupUpdateService.java @@ -0,0 +1,34 @@ +package ctbrec.ui.sites.showup; + +import java.io.IOException; +import java.util.List; + +import ctbrec.Model; +import ctbrec.sites.showup.Showup; +import ctbrec.sites.showup.ShowupHttpClient; +import ctbrec.ui.tabs.PaginatedScheduledService; +import javafx.concurrent.Task; + +public class ShowupUpdateService extends PaginatedScheduledService { + + private Showup showup; + private String category; + + public ShowupUpdateService(Showup showup, String category) { + this.showup = showup; + this.category = category; + } + + @Override + protected Task> createTask() { + return new Task>() { + @Override + public List call() throws IOException { + ShowupHttpClient httpClient = (ShowupHttpClient) showup.getHttpClient(); + httpClient.setCookie("category", category); + return showup.getModelList(); + } + }; + } + +} diff --git a/common/src/main/java/ctbrec/ModelNotFoundException.java b/common/src/main/java/ctbrec/ModelNotFoundException.java new file mode 100644 index 00000000..da57f325 --- /dev/null +++ b/common/src/main/java/ctbrec/ModelNotFoundException.java @@ -0,0 +1,12 @@ +package ctbrec; + +public class ModelNotFoundException extends RuntimeException { + + public ModelNotFoundException(String modelName) { + super(modelName); + } + + public ModelNotFoundException(String modelName, Exception e) { + super(modelName, e); + } +} diff --git a/common/src/main/java/ctbrec/recorder/PlaylistGenerator.java b/common/src/main/java/ctbrec/recorder/PlaylistGenerator.java index 069b09cd..dd522d49 100644 --- a/common/src/main/java/ctbrec/recorder/PlaylistGenerator.java +++ b/common/src/main/java/ctbrec/recorder/PlaylistGenerator.java @@ -37,9 +37,13 @@ public class PlaylistGenerator { private List listeners = new ArrayList<>(); public File generate(File directory) throws IOException, ParseException, PlaylistException { + return generate(directory, "ts"); + } + + public File generate(File directory, String fileSuffix) throws IOException, ParseException, PlaylistException { LOG.info("Starting playlist generation for {}", directory); // get a list of all ts files and sort them by sequence - File[] files = directory.listFiles(f -> f.getName().endsWith(".ts")); + File[] files = directory.listFiles(f -> f.getName().endsWith('.' + fileSuffix)); if (files == null || files.length == 0) { LOG.debug("{} is empty. Not going to generate a playlist", directory); throw new InvalidPlaylistException("Directory is empty"); @@ -57,15 +61,18 @@ public class PlaylistGenerator { int done = 0; for (File file : files) { try { - float duration = (float) MpegUtil.getFileDuration(file); - if (duration <= 0) { - throw new InvalidTrackLengthException("Track has negative duration: " + file.getName()); - } else { - track.add(new TrackData.Builder() - .withUri(file.getName()) - .withTrackInfo(new TrackInfo(duration, file.getName())) - .build()); + float duration = 0; + if (file.getName().toLowerCase().endsWith(".ts")) { + duration = (float) MpegUtil.getFileDuration(file); + if (duration <= 0) { + throw new InvalidTrackLengthException("Track has negative duration: " + file.getName()); + } } + + track.add(new TrackData.Builder() + .withUri(file.getName()) + .withTrackInfo(new TrackInfo(duration, file.getName())) + .build()); } catch (Exception e) { LOG.warn("Couldn't determine duration for {}. Skipping this file.", file.getName()); File corruptedFile = new File(directory, file.getName() + ".corrupt"); diff --git a/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java index 3bd6c144..3c4fd217 100644 --- a/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java +++ b/common/src/main/java/ctbrec/recorder/download/hls/HlsDownload.java @@ -9,6 +9,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InterruptedIOException; import java.net.URL; import java.nio.file.FileSystems; import java.nio.file.Files; @@ -60,7 +61,7 @@ public class HlsDownload extends AbstractHlsDownload { private NumberFormat nf = new DecimalFormat("000000"); private transient AtomicBoolean downloadFinished = new AtomicBoolean(false); private ZonedDateTime splitRecStartTime; - private transient Config config; + protected transient Config config; public HlsDownload(HttpClient client) { super(client); @@ -270,6 +271,7 @@ public class HlsDownload extends AbstractHlsDownload { @Override void internalStop() { running = false; + downloadThreadPool.shutdownNow(); } private static class SegmentDownload implements Callable { @@ -289,7 +291,7 @@ public class HlsDownload extends AbstractHlsDownload { @Override public Boolean call() throws Exception { LOG.trace("Downloading segment {} to {}", url, file); - for (int tries = 1; tries <= 3; tries++) { + for (int tries = 1; tries <= 3 && !Thread.currentThread().isInterrupted(); tries++) { Request request = new Request.Builder() .url(url) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) @@ -304,7 +306,7 @@ public class HlsDownload extends AbstractHlsDownload { } byte[] b = new byte[1024 * 100]; int length = -1; - while( (length = in.read(b)) >= 0 ) { + while( (length = in.read(b)) >= 0 && !Thread.currentThread().isInterrupted()) { fos.write(b, 0, length); } return true; @@ -312,7 +314,10 @@ public class HlsDownload extends AbstractHlsDownload { } catch(FileNotFoundException e) { LOG.debug("Segment does not exist {}", url.getFile()); break; + } catch (InterruptedIOException e) { + break; } catch(Exception e) { + LOG.error("Error", e); if (tries == 3) { LOG.warn("Error while downloading segment. Segment {} finally failed: {}", file.toFile().getName(), e.getMessage()); } else { diff --git a/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java index 0647682e..6c2fb850 100644 --- a/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java +++ b/common/src/main/java/ctbrec/recorder/download/hls/MergedFfmpegHlsDownload.java @@ -1,5 +1,7 @@ package ctbrec.recorder.download.hls; +import static ctbrec.io.HttpConstants.*; + import java.io.EOFException; import java.io.File; import java.io.FileOutputStream; @@ -50,7 +52,7 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload { private transient Config config; private transient Process ffmpeg; private transient OutputStream ffmpegStdIn; - private transient Thread ffmpegThread; + protected transient Thread ffmpegThread; private transient Object ffmpegStartMonitor = new Object(); public MergedFfmpegHlsDownload(HttpClient client) { @@ -178,7 +180,7 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload { ffmpegThread.start(); } - private void downloadSegments(String segmentPlaylistUri, boolean livestreamDownload) throws IOException, ParseException, PlaylistException { + protected void downloadSegments(String segmentPlaylistUri, boolean livestreamDownload) throws IOException, ParseException, PlaylistException { int lastSegment = 0; int nextSegment = 0; while (running) { @@ -307,17 +309,21 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload { } } - private void writeSegment(byte[] segmentData) throws IOException { + protected void writeSegment(byte[] segmentData, int offset, int length) throws IOException { if (running) { if (ffmpegStdIn != null) { - ffmpegStdIn.write(segmentData); + ffmpegStdIn.write(segmentData, offset, length); } else { LOG.error("FFmpeg stdin stream is null - skipping writing of segment"); } } } - private boolean splitRecording() { + private void writeSegment(byte[] segmentData) throws IOException { + writeSegment(segmentData, 0, segmentData.length); + } + + protected boolean splitRecording() { if (config.getSettings().splitRecordings > 0) { Duration recordingDuration = Duration.between(splitRecStartTime, ZonedDateTime.now()); long seconds = recordingDuration.getSeconds(); @@ -406,7 +412,10 @@ public class MergedFfmpegHlsDownload extends AbstractHlsDownload { LOG.trace("Downloading segment {}", url.getFile()); int maxTries = 3; for (int i = 1; i <= maxTries && running; i++) { - Request request = new Request.Builder().url(url).addHeader("connection", "keep-alive").build(); + Request request = new Request.Builder().url(url) + .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) + .header(CONNECTION, KEEP_ALIVE) + .build(); try (Response response = client.execute(request)) { if (response.isSuccessful()) { byte[] segment = response.body().bytes(); diff --git a/common/src/main/java/ctbrec/sites/showup/Showup.java b/common/src/main/java/ctbrec/sites/showup/Showup.java new file mode 100644 index 00000000..36ad387c --- /dev/null +++ b/common/src/main/java/ctbrec/sites/showup/Showup.java @@ -0,0 +1,169 @@ +package ctbrec.sites.showup; + +import static ctbrec.Model.State.*; +import static ctbrec.io.HttpConstants.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Objects; + +import ctbrec.Config; +import ctbrec.Model; +import ctbrec.ModelNotFoundException; +import ctbrec.UnknownModel; +import ctbrec.io.HttpClient; +import ctbrec.io.HttpException; +import ctbrec.sites.AbstractSite; +import okhttp3.Request; +import okhttp3.Response; + +public class Showup extends AbstractSite { + + private static final transient Logger LOG = LoggerFactory.getLogger(Showup.class); + private ShowupHttpClient httpClient; + + @Override + public String getName() { + return "Showup.tv"; + } + + @Override + public String getBaseUrl() { + return "https://showup.tv"; + } + + @Override + public String getAffiliateLink() { + return getBaseUrl(); + } + + @Override + public Model createModel(String name) { + try { + for (Model m : getModelList()) { + if (Objects.equal(m.getName(), name)) { + return m; + } + } + } catch (IOException e) { + throw new ModelNotFoundException(name, e); + } + throw new ModelNotFoundException(name); + } + + public List getModelList() throws IOException { + String url = getBaseUrl() + "/site/get_stream_list/big"; + Request req = new Request.Builder() + .url(url) + .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) + .build(); + + try (Response response = getHttpClient().execute(req)) { + if (response.isSuccessful()) { + String body = response.body().string(); + LOG.trace(body); + JSONObject json = new JSONObject(body); + List models = new ArrayList<>(); + JSONArray list = json.getJSONArray("list"); + for (int i = 0; i < list.length(); i++) { + JSONObject entry = list.getJSONObject(i); + ShowupModel model = new ShowupModel(); + model.setUid(entry.getLong("uid")); + model.setPreview(getBaseUrl() + "/files/" + entry.optString("big_img") + ".jpg"); + model.setDescription(entry.optString("description")); + model.setName(entry.optString("username")); + model.setUrl(getBaseUrl() + '/' + model.getName()); + if(entry.optInt("is_group") == 1) { + model.setOnlineState(GROUP); + } else if(entry.optInt("is_prv") == 1) { + model.setOnlineState(PRIVATE); + } else { + model.setOnlineState(ONLINE); + } + model.setStreamId(entry.optString("stream_id")); + model.setStreamTranscoderAddr(entry.optString("stream_transcoder_addr")); + model.setSite(this); + models.add(model); + } + return models; + } else { + throw new HttpException(response.code(), response.message()); + } + } + } + + @Override + public Double getTokenBalance() throws IOException { + return 0d; + } + + @Override + public String getBuyTokensLink() { + return getBaseUrl(); + } + + @Override + public boolean login() throws IOException { + return false; + } + + @Override + public HttpClient getHttpClient() { + if (httpClient == null) { + httpClient = new ShowupHttpClient(); + } + return httpClient; + } + + @Override + public void init() throws IOException { + // noop + } + + @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 ShowupModel; + } + + @Override + public boolean credentialsAvailable() { + return false; + } + + @Override + public Model createModelFromUrl(String url) { + Matcher matcher = Pattern.compile(getBaseUrl() + "(?:/profile)?/(.*)").matcher(url); + if (matcher.find()) { + return createModel(matcher.group(1)); + } else { + return new UnknownModel(); + } + } + +} diff --git a/common/src/main/java/ctbrec/sites/showup/ShowupDownload.java b/common/src/main/java/ctbrec/sites/showup/ShowupDownload.java new file mode 100644 index 00000000..0245b7d3 --- /dev/null +++ b/common/src/main/java/ctbrec/sites/showup/ShowupDownload.java @@ -0,0 +1,39 @@ +package ctbrec.sites.showup; + +import java.io.File; +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; + +import com.iheartradio.m3u8.ParseException; +import com.iheartradio.m3u8.PlaylistException; + +import ctbrec.Recording; +import ctbrec.io.HttpClient; +import ctbrec.recorder.PlaylistGenerator; +import ctbrec.recorder.download.hls.HlsDownload; + +public class ShowupDownload extends HlsDownload { + + public ShowupDownload(HttpClient client) { + super(client); + } + + @Override + protected File generatePlaylist(Recording recording) throws IOException, ParseException, PlaylistException { + File recDir = recording.getAbsoluteFile(); + if (!config.getSettings().generatePlaylist) { + return null; + } + PlaylistGenerator playlistGenerator = new PlaylistGenerator(); + playlistGenerator.addProgressListener(recording::setProgress); + File playlist = playlistGenerator.generate(recDir, "mp4"); + recording.setProgress(-1); + return playlist; + } + + @Override + public Duration getLength() { + return Duration.between(getStartTime(), Instant.now()); + } +} diff --git a/common/src/main/java/ctbrec/sites/showup/ShowupHttpClient.java b/common/src/main/java/ctbrec/sites/showup/ShowupHttpClient.java new file mode 100644 index 00000000..a2b9ced0 --- /dev/null +++ b/common/src/main/java/ctbrec/sites/showup/ShowupHttpClient.java @@ -0,0 +1,41 @@ +package ctbrec.sites.showup; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import com.google.common.base.Objects; + +import ctbrec.io.HttpClient; +import okhttp3.Cookie; + +public class ShowupHttpClient extends HttpClient { + + protected ShowupHttpClient() { + super("showup"); + setCookie("accept_rules", "true"); + setCookie("category", "all"); + } + + public void setCookie(String name, String value) { + Cookie cookie = new Cookie.Builder().domain("showup.tv").name(name).value(value).build(); + + Map> cookies = cookieJar.getCookies(); + List cookiesForDomain = cookies.computeIfAbsent(cookie.domain(), k -> new ArrayList()); + for (Iterator iterator = cookiesForDomain.iterator(); iterator.hasNext();) { + Cookie existingCookie = iterator.next(); + if (Objects.equal(existingCookie.name(), cookie.name())) { + iterator.remove(); + } + } + cookiesForDomain.add(cookie); + } + + @Override + public boolean login() throws IOException { + return false; + } + +} diff --git a/common/src/main/java/ctbrec/sites/showup/ShowupMergedDownload.java b/common/src/main/java/ctbrec/sites/showup/ShowupMergedDownload.java new file mode 100644 index 00000000..9b266f51 --- /dev/null +++ b/common/src/main/java/ctbrec/sites/showup/ShowupMergedDownload.java @@ -0,0 +1,72 @@ +package ctbrec.sites.showup; + +import static ctbrec.io.HttpConstants.*; + +import java.io.IOException; +import java.io.InputStream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.iheartradio.m3u8.ParseException; +import com.iheartradio.m3u8.PlaylistException; + +import ctbrec.Config; +import ctbrec.io.HttpClient; +import ctbrec.io.HttpException; +import ctbrec.recorder.download.hls.MergedFfmpegHlsDownload; +import okhttp3.Request; +import okhttp3.Response; + +public class ShowupMergedDownload extends MergedFfmpegHlsDownload { + + private static final Logger LOG = LoggerFactory.getLogger(ShowupMergedDownload.class); + + public ShowupMergedDownload(HttpClient client) { + super(client); + } + + @Override + protected void downloadSegments(String segmentPlaylistUri, boolean livestreamDownload) throws IOException, ParseException, PlaylistException { + try { + SegmentPlaylist lsp = getNextSegments(segmentPlaylistUri); + emptyPlaylistCheck(lsp); + + for (String segment : lsp.segments) { + Request request = new Request.Builder().url(segment) + .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) + .header(CONNECTION, KEEP_ALIVE) + .build(); + try (Response response = client.execute(request)) { + if (response.isSuccessful()) { + InputStream in = response.body().byteStream(); + byte[] buffer = new byte[10240]; + int length = -1; + while ((length = in.read(buffer)) >= 0 && running && !Thread.interrupted()) { + writeSegment(buffer, 0, length); + if (livestreamDownload && splitRecording()) { + break; + } + } + } else { + throw new HttpException(response.code(), response.message()); + } + } + } + } catch (HttpException e) { + if (e.getResponseCode() == 404) { + LOG.debug("Playlist not found (404). Model {} probably went offline", model); + } else if (e.getResponseCode() == 403) { + LOG.debug("Playlist access forbidden (403). Model {} probably went private or offline", model); + } else { + LOG.info("Unexpected error while downloading {}", model, e); + } + running = false; + } catch (Exception e) { + LOG.info("Unexpected error while downloading {}", model, e); + running = false; + } + + ffmpegThread.interrupt(); + } +} diff --git a/common/src/main/java/ctbrec/sites/showup/ShowupModel.java b/common/src/main/java/ctbrec/sites/showup/ShowupModel.java new file mode 100644 index 00000000..605bc414 --- /dev/null +++ b/common/src/main/java/ctbrec/sites/showup/ShowupModel.java @@ -0,0 +1,135 @@ +package ctbrec.sites.showup; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ExecutionException; + +import javax.xml.bind.JAXBException; + +import com.iheartradio.m3u8.ParseException; +import com.iheartradio.m3u8.PlaylistException; +import com.squareup.moshi.JsonReader; +import com.squareup.moshi.JsonWriter; + +import ctbrec.AbstractModel; +import ctbrec.Config; +import ctbrec.Model; +import ctbrec.recorder.download.Download; +import ctbrec.recorder.download.StreamSource; + +public class ShowupModel extends AbstractModel { + + private long uid; + private String streamId; + private String streamTranscoderAddr; + + @Override + public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { + List modelList = getShowupSite().getModelList(); + for (Model model : modelList) { + ShowupModel m = (ShowupModel) model; + if (m.getUid() == uid) { + return m.getOnlineState(false) != State.ONLINE; + } + } + return false; + } + + @Override + public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException, JAXBException { + StreamSource src = new StreamSource(); + src.width = 480; + src.height = 360; + + if(streamId == null || streamTranscoderAddr == null) { + List modelList = getShowupSite().getModelList(); + for (Model model : modelList) { + ShowupModel m = (ShowupModel) model; + if (m.getUid() == uid) { + streamId = m.getStreamId(); + streamTranscoderAddr = m.getStreamTranscoderAddr(); + } + } + } + + int cdnHost = 1 + new Random().nextInt(5); + src.mediaPlaylistUrl = MessageFormat.format("https://cdn-e0{0}.showup.tv/h5live/http/playlist.m3u8?url=rtmp%3A%2F%2F{1}%3A1935%2Fwebrtc&stream={2}_aac", cdnHost, streamTranscoderAddr, streamId); + return Collections.singletonList(src); + } + + @Override + public void invalidateCacheEntries() { + // noop + } + + @Override + public void receiveTip(Double tokens) throws IOException { + // noop + } + + @Override + public int[] getStreamResolution(boolean failFast) throws ExecutionException { + return new int[] { 480, 360 }; + } + + @Override + public boolean follow() throws IOException { + return false; + } + + @Override + public boolean unfollow() throws IOException { + return false; + } + + public long getUid() { + return uid; + } + + public void setUid(long uid) { + this.uid = uid; + } + + @Override + public void readSiteSpecificData(JsonReader reader) throws IOException { + reader.nextName(); + uid = reader.nextLong(); + } + + @Override + public void writeSiteSpecificData(JsonWriter writer) throws IOException { + writer.name("uid").value(uid); + } + + private Showup getShowupSite() { + return (Showup) getSite(); + } + + public String getStreamId() { + return streamId; + } + + public void setStreamId(String streamId) { + this.streamId = streamId; + } + + public String getStreamTranscoderAddr() { + return streamTranscoderAddr; + } + + public void setStreamTranscoderAddr(String streamTranscoderAddr) { + this.streamTranscoderAddr = streamTranscoderAddr; + } + + @Override + public Download createDownload() { + if (Config.isServerMode() && !Config.getInstance().getSettings().recordSingleFile) { + return new ShowupDownload(getSite().getHttpClient()); + } else { + return new ShowupMergedDownload(getSite().getHttpClient()); + } + } +} diff --git a/server/src/main/java/ctbrec/recorder/server/HttpServer.java b/server/src/main/java/ctbrec/recorder/server/HttpServer.java index 0e2905a3..1297188c 100644 --- a/server/src/main/java/ctbrec/recorder/server/HttpServer.java +++ b/server/src/main/java/ctbrec/recorder/server/HttpServer.java @@ -57,6 +57,7 @@ import ctbrec.sites.fc2live.Fc2Live; import ctbrec.sites.flirt4free.Flirt4Free; import ctbrec.sites.jasmin.LiveJasmin; import ctbrec.sites.mfc.MyFreeCams; +import ctbrec.sites.showup.Showup; import ctbrec.sites.streamate.Streamate; import ctbrec.sites.stripchat.Stripchat; @@ -110,6 +111,7 @@ public class HttpServer { sites.add(new Flirt4Free()); sites.add(new LiveJasmin()); sites.add(new MyFreeCams()); + sites.add(new Showup()); sites.add(new Streamate()); sites.add(new Stripchat()); }