package com.atomjack.vcfp.net; import android.util.Base64; import com.atomjack.shared.Logger; import com.atomjack.shared.PlayerState; import com.atomjack.shared.Preferences; import com.atomjack.vcfp.PlexHeaders; import com.atomjack.vcfp.R; import com.atomjack.vcfp.VoiceControlForPlexApplication; import com.atomjack.vcfp.exceptions.UnauthorizedException; import com.atomjack.vcfp.interfaces.ActiveConnectionHandler; import com.atomjack.vcfp.interfaces.GenericHandler; import com.atomjack.vcfp.interfaces.InputStreamHandler; import com.atomjack.vcfp.interfaces.PlexDirectoryHandler; import com.atomjack.vcfp.interfaces.PlexMediaHandler; import com.atomjack.vcfp.interfaces.PlexPlayQueueHandler; import com.atomjack.vcfp.model.Connection; import com.atomjack.vcfp.model.MediaContainer; import com.atomjack.vcfp.model.Pin; import com.atomjack.vcfp.model.PlexClient; import com.atomjack.vcfp.model.PlexDirectory; import com.atomjack.vcfp.model.PlexMedia; import com.atomjack.vcfp.model.PlexResponse; import com.atomjack.vcfp.model.PlexServer; import com.atomjack.vcfp.model.PlexUser; import com.atomjack.vcfp.model.Stream; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.ResponseBody; import java.io.IOException; import java.net.SocketTimeoutException; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.concurrent.TimeUnit; import retrofit.Call; import retrofit.Callback; import retrofit.Response; import retrofit.Retrofit; import retrofit.RxJavaCallAdapterFactory; import retrofit.SimpleXmlConverterFactory; import retrofit.http.GET; import retrofit.http.Headers; import retrofit.http.POST; import retrofit.http.PUT; import retrofit.http.Path; import retrofit.http.Query; import retrofit.http.QueryMap; import retrofit.http.Url; import rx.Observable; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; public class PlexHttpClient { private static OkHttpClient httpClient = new OkHttpClient(); public interface PlexHttpService { @GET("/library/sections/{section}/search") Call<MediaContainer> searchSection(@Path("section") String section, @Query("type") String type, @Query("query") String query, @Query(PlexHeaders.XPlexToken) String token); @GET Call<MediaContainer> getMediaContainer(@Url String path, @Query(PlexHeaders.XPlexToken) String token); @GET Observable<MediaContainer> newGetMediaContainer(@Url String path, @Query(PlexHeaders.XPlexToken) String token); @GET Call<PlexResponse> getPlexResponse(@retrofit.http.Header(PlexHeaders.XPlexClientIdentifier) String clientId, @Url String path); @GET("/player/timeline/subscribe?protocol=http") Call<PlexResponse> subscribe(@retrofit.http.Header(PlexHeaders.XPlexClientIdentifier) String clientId, @retrofit.http.Header(PlexHeaders.XPlexDeviceName) String deviceName, @Query("port") int subscriptionPort, @Query("commandID") int commandId); @GET("/player/timeline/unsubscribe") Call<PlexResponse> unsubscribe(@retrofit.http.Header(PlexHeaders.XPlexClientIdentifier) String clientId, @retrofit.http.Header(PlexHeaders.XPlexDeviceName) String deviceName, @retrofit.http.Header(PlexHeaders.XPlexTargetClientIdentifier) String machineIdentifier); @retrofit.http.Headers(PlexHeaders.XPlexDeviceName + ": Voice Control for Plex") @GET("/player/timeline/poll") Call<MediaContainer> pollTimeline(@retrofit.http.Header(PlexHeaders.XPlexClientIdentifier) String clientId, @Query("commandID") int commandId); @GET("/pins/{pinID}.xml") Call<Pin> fetchPin(@Path(value="pinID", encoded = true) int pinID, @retrofit.http.Header(PlexHeaders.XPlexClientIdentifier) String clientId); @GET("/pins/{pinID}.xml") Observable<Pin> newFetchPin(@Path(value="pinID", encoded = true) int pinID, @retrofit.http.Header(PlexHeaders.XPlexClientIdentifier) String clientId); @Headers("Accept: text/xml") @POST("/users/sign_in.xml") Call<PlexUser> signin(@retrofit.http.Header(PlexHeaders.XPlexClientPlatform) String clientPlatform, @retrofit.http.Header(PlexHeaders.XPlexClientIdentifier) String clientId, @Query("auth_token") String authToken); @POST("/pins.xml") Call<Pin> getPinCode(@retrofit.http.Header(PlexHeaders.XPlexClientIdentifier) String clientId); @POST("/pins.xml") Observable<Pin> newGetPinCode(@retrofit.http.Header(PlexHeaders.XPlexClientIdentifier) String clientId); @POST("/playQueues") Call<MediaContainer> createPlayQueue(@QueryMap Map<String, String> options, @retrofit.http.Header(PlexHeaders.XPlexClientIdentifier) String clientId); @GET("/player/playback/{which}") Call<PlexResponse> adjustPlayback(@Path(value="which", encoded=true) String which, @Query("commandID") String commandId, @retrofit.http.Header(PlexHeaders.XPlexClientIdentifier) String clientId); @GET("/player/playback/seekTo") Call<PlexResponse> seekTo(@Query("offset") int offset, @Query(PlexHeaders.commandID) String commandId, @retrofit.http.Header(PlexHeaders.XPlexClientIdentifier) String clientId); @GET("/library/sections") Call<MediaContainer> getLibrarySections(@Query(PlexHeaders.XPlexToken) String accessToken); @GET("/pms/resources") Call<MediaContainer> getResources(@Query(PlexHeaders.XPlexToken) String accessToken); @GET("/player/playback/setStreams") Call<PlexResponse> setStreams(@QueryMap Map<String, String> options, @Query(PlexHeaders.commandID) String commandId, @retrofit.http.Header(PlexHeaders.XPlexClientIdentifier) String clientId); @GET("/users/account.xml") Call<PlexUser> getPlexAccount(@retrofit.http.Header(PlexHeaders.XPlexToken) String authToken); @GET("/library/sections/{section}/all") Observable<MediaContainer> getRandomMovie(@Path(value="section", encoded=true) String section, @Query(PlexHeaders.XPlexToken) String accessToken); @GET("/library/sections/{section}/all") Observable<MediaContainer> getRandomShow(@Path(value="section", encoded=true) String section, @Query(PlexHeaders.XPlexToken) String accessToken); @GET("/library/onDeck/all") Observable<MediaContainer> getRandomOnDeck(@Query(PlexHeaders.XPlexToken) String accessToken); @GET("/library/sections/{section}/all") Call<MediaContainer> getRandomDirectory(@Path(value="section", encoded=true) String section, @Query(PlexHeaders.XPlexToken) String accessToken); @GET("/library/sections/{section}/all") Observable<MediaContainer> newGetRandomDirectory(@Path(value="section", encoded=true) String section, @Query(PlexHeaders.XPlexToken) String accessToken); @GET("/library/metadata/{ratingKey}/allLeaves") Call<MediaContainer> getRandomEpisode(@Path(value="ratingKey", encoded=true) String ratingKey, @Query(PlexHeaders.XPlexToken) String accessToken); @GET("/library/metadata/{ratingKey}/allLeaves") Observable<MediaContainer> newGetRandomEpisode(@Path(value="ratingKey", encoded=true) String ratingKey, @Query(PlexHeaders.XPlexToken) String accessToken); @GET("/{type}/:/transcode/universal/stop") Call<PlexResponse> stopTranscoder(@Path(value="type", encoded = true) String type, @Query(PlexHeaders.XPlexToken) String token, @Query(PlexHeaders.session) String session, @retrofit.http.Header(PlexHeaders.XPlexClientIdentifier) String clientId); @GET("/:/timeline/") Call<PlexResponse> reportProgressToServer(@Query(PlexHeaders.XPlexToken) String token, @Query(PlexHeaders.key) String key, @Query(PlexHeaders.ratingKey) String ratingKey, @Query(PlexHeaders.time) String time, @Query(PlexHeaders.duration) String duration, @Query(PlexHeaders.state) String state, @retrofit.http.Header(PlexHeaders.XPlexClientIdentifier) String clientId, @retrofit.http.Header(PlexHeaders.XPlexDeviceName) String deviceName, @retrofit.http.Header(PlexHeaders.XPlexPlatform) String platform, @retrofit.http.Header(PlexHeaders.XPlexProduct) String product); @PUT("/library/parts/{part_id}?allParts=1") Call<PlexResponse> setSubtitleStreamActive(@Path(value="part_id", encoded = true) String partId, @Query("subtitleStreamID") String streamId, @Query(PlexHeaders.XPlexToken) String token); @PUT("/library/parts/{part_id}") Call<PlexResponse> setAudioStreamActive(@Path(value="part_id", encoded = true) String partId, @Query("audioStreamID") String streamId, @Query(PlexHeaders.XPlexToken) String token); @GET("/library/metadata/{key}/children") Call<MediaContainer> getChildren(@Path(value="key", encoded = true) String key, @Query(PlexHeaders.XPlexToken) String token); @GET("/library/sections/{section}/search?type=2") Observable<MediaContainer> searchForShow(@Path(value="section", encoded = true) String section, @Query("query") String query, @Query(PlexHeaders.XPlexToken) String token); @GET("/library/sections/{section}/onDeck") Observable<MediaContainer> getOnDeck(@Path(value="section", encoded=true) String section, @Query(PlexHeaders.XPlexToken) String token); @GET("/library/sections/{section}/recentlyAdded") Observable<MediaContainer> getRecentlyAdded(@Path(value="section", encoded=true) String section, @Query(PlexHeaders.XPlexToken) String token); } public static void getThumb(String url, final InputStreamHandler inputStreamHandler) { Request request = new Request.Builder() .url(url) .build(); httpClient.newCall(request).enqueue(new com.squareup.okhttp.Callback() { @Override public void onFailure(Request request, IOException e) { e.printStackTrace(); } @Override public void onResponse(com.squareup.okhttp.Response response) throws IOException { Logger.d("got %d bytes", response.body().contentLength()); inputStreamHandler.onSuccess(response.body().byteStream()); } }); } public static PlexHttpService getService(Connection connection) { return getService(connection.uri); } public static PlexHttpService getService(Connection connection, int timeout) { return getService(connection.uri, null, null, false, timeout); } public static PlexHttpService getService(Connection connection, boolean debug) { return getService(connection.uri, debug); } public static PlexHttpService getService(String url) { return getService(url, false); } public static PlexHttpService getService(String url, boolean debug) { return getService(url, null, null, debug); } public static PlexHttpService getService(String url, String username, String password) { return getService(url, username, password, false); } public static PlexHttpService getService(String url, String username, String password, boolean debug) { return getService(url, username, password, debug, 0); } public static PlexHttpService getService(String url, String username, String password, boolean debug, int timeout) { OkHttpClient client = new OkHttpClient(); if(timeout > 0) client.setReadTimeout(timeout, TimeUnit.SECONDS); Retrofit.Builder builder = new Retrofit.Builder() .baseUrl(url) .client(client) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .addConverterFactory(SimpleXmlConverterFactory.create()); if(username != null && password != null) { String creds = username + ":" + password; final String basic = "Basic " + Base64.encodeToString(creds.getBytes(), Base64.NO_WRAP); client.interceptors().add(chain -> { Request original = chain.request(); Request.Builder requestBuilder = original.newBuilder() .header("Authorization", basic) .header("Accept", "text/xml") .method(original.method(), original.body()); Request request = requestBuilder.build(); return chain.proceed(request); }); } if(debug) { client.interceptors().add(chain -> { try { com.squareup.okhttp.Response response = chain.proceed(chain.request()); ResponseBody responseBody = response.body(); String body = response.body().string(); Logger.d("Retrofit@Response: (%d) %s", response.code(), body); return response.newBuilder().body(ResponseBody.create(responseBody.contentType(), body.getBytes())).build(); } catch (Exception e) { e.printStackTrace(); } return null; }); } // Plex Media Player currently returns an empty body or "Failure: 200 OK" instead of valid XML for many calls, so we must detect an empty body // and write our own valid XML in place of it client.interceptors().add(chain -> { try { com.squareup.okhttp.Response response = chain.proceed(chain.request()); ResponseBody responseBody = response.body(); String body = response.body().string(); if(body.equals("") || body.matches(".*Failure: 200 OK\r\n.*")) { body = String.format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" + "<Response code=\"%d\" status=\"%s\" />", response.code(), response.code() == 200 ? "OK" : "Error"); } return response.newBuilder().body(ResponseBody.create(responseBody.contentType(), body.getBytes())).build(); } catch (Exception e) { e.printStackTrace(); } return null; }); Retrofit retrofit = builder.client(client).build(); return retrofit.create(PlexHttpService.class); } public static void searchServer(final PlexServer server, final String section, final String queryTerm, final PlexHttpMediaContainerHandler responseHandler) { server.findServerConnection(new ActiveConnectionHandler() { @Override public void onSuccess(Connection connection) { PlexHttpService service = getService(connection.uri); Call<MediaContainer> call = service.searchSection(section, "1", queryTerm, server.accessToken); call.enqueue(new Callback<MediaContainer>() { @Override public void onResponse(Response<MediaContainer> response, Retrofit retrofit) { responseHandler.onSuccess(response.body()); } @Override public void onFailure(Throwable t) { if (responseHandler != null) responseHandler.onFailure(t); } }); } @Override public void onFailure(int statusCode) { if (responseHandler != null) responseHandler.onFailure(new Throwable()); } }); } public static void getDebug(final PlexServer server, final String path, final PlexHttpMediaContainerHandler responseHandler) { get(server, path, true, responseHandler); } public static void get(final PlexServer server, final String path, final PlexHttpMediaContainerHandler responseHandler) { get(server, path, false, responseHandler); } public static void get(final PlexServer server, final String path, final boolean debug, final PlexHttpMediaContainerHandler responseHandler) { server.findServerConnection(new ActiveConnectionHandler() { @Override public void onSuccess(Connection connection) { PlexHttpService service = getService(connection.uri, debug); Call<MediaContainer> call = service.getMediaContainer(path.substring(1), server.accessToken); call.enqueue(new Callback<MediaContainer>() { @Override public void onResponse(Response<MediaContainer> response, Retrofit retrofit) { try { // Add this server to each of this media container's media objects MediaContainer mediaContainer = response.body(); if (mediaContainer.tracks != null) { for (int i = 0; i < mediaContainer.tracks.size(); i++) { mediaContainer.tracks.get(i).server = server; } } if (mediaContainer.videos != null) { for (int i = 0; i < mediaContainer.videos.size(); i++) { mediaContainer.videos.get(i).server = server; } } responseHandler.onSuccess(response.body()); } catch (Exception e) { responseHandler.onFailure(e); } } @Override public void onFailure(Throwable t) { if (responseHandler != null) responseHandler.onFailure(t); } }); } @Override public void onFailure(int statusCode) { if (responseHandler != null) responseHandler.onFailure(statusCode == 401 ? new UnauthorizedException() : new Throwable()); } }); } public static void subscribe(PlexClient client, int subscriptionPort, int commandId, String uuid, String deviceName, final PlexHttpResponseHandler responseHandler) { String url = String.format("http://%s:%s", client.address, client.port); PlexHttpService service = getService(url); Call<PlexResponse> call = service.subscribe(uuid, deviceName, subscriptionPort, commandId); call.enqueue(new Callback<PlexResponse>() { @Override public void onResponse(Response<PlexResponse> response, Retrofit retrofit) { if (responseHandler != null) { Logger.d("Response code: %d", response.code()); if(response.code() == 200) responseHandler.onSuccess(response.body()); else responseHandler.onFailure(new Throwable()); } } @Override public void onFailure(Throwable t) { if(t.getMessage().matches(".*Failure: 200 OK\n.*")) { if (responseHandler != null) { PlexResponse response = new PlexResponse(); response.code = 200; response.status = "ok"; responseHandler.onSuccess(response); } } else { Logger.d("subscribe onFailure: %s", t.getMessage()); PlexResponse response = new PlexResponse(); response.status = "ok"; t.printStackTrace(); if (responseHandler != null) responseHandler.onFailure(t); } } }); } public static void unsubscribe(PlexClient client, int commandId, String uuid, String deviceName, final PlexHttpResponseHandler responseHandler) { String url = String.format("http://%s:%s", client.address, client.port); PlexHttpService service = getService(url); Call<PlexResponse> call = service.unsubscribe(uuid, deviceName, client.machineIdentifier); call.enqueue(new Callback<PlexResponse>() { @Override public void onResponse(Response<PlexResponse> response, Retrofit retrofit) { responseHandler.onSuccess(response.body()); } @Override public void onFailure(Throwable t) { responseHandler.onFailure(t); } }); } public static void createArtistPlayQueue(Connection connection, PlexDirectory artist, final PlexPlayQueueHandler responseHandler) { HashMap<String, String> qs = new HashMap<>(); qs.put("type", "audio"); qs.put("shuffle", "1"); String uri = String.format("library://%s/item/%%2flibrary%%2fmetadata%%2f%s", artist.server.machineIdentifier, artist.ratingKey); Logger.d("URI: %s", uri); qs.put("uri", uri); if(artist.server.accessToken != null) qs.put(PlexHeaders.XPlexToken, artist.server.accessToken); qs.put("continuous", "0"); qs.put("includeRelated", "1"); PlexHttpService service = getService(String.format("http://%s:%s", connection.address, connection.port), true); Call<MediaContainer> call = service.createPlayQueue(qs, VoiceControlForPlexApplication.getUUID()); call.enqueue(new Callback<MediaContainer>() { @Override public void onResponse(Response<MediaContainer> response, Retrofit retrofit) { if (responseHandler != null) responseHandler.onSuccess(response.body()); } @Override public void onFailure(Throwable t) { Logger.d("createPlayQueue failure."); t.printStackTrace(); } }); } public static void createPlayQueue(Connection connection, final PlexMedia media, boolean resume, final String key, String transientToken, final PlexPlayQueueHandler responseHandler) { Map<String, String> qs = new HashMap<>(); qs.put("type", media.getType()); qs.put("next", "0"); boolean hasOffset = (VoiceControlForPlexApplication.getInstance().prefs.get(Preferences.RESUME, false)|| resume) && media.viewOffset != null;//media.viewOffset != null && Integer.parseInt(media.viewOffset) > 0; if(media.isMovie() && !hasOffset) { qs.put("extrasPrefixCount", Integer.toString(VoiceControlForPlexApplication.getInstance().prefs.get(Preferences.NUM_CINEMA_TRAILERS, 0))); } if(hasOffset) qs.put("viewOffset", media.viewOffset); String uri = String.format("library://%s/item/%%2flibrary%%2fmetadata%%2f%s", media.server.machineIdentifier, key); qs.put("uri", uri); qs.put("window", "50"); // no idea what this is for if (transientToken != null) qs.put("token", transientToken); if (media.server.accessToken != null) qs.put(PlexHeaders.XPlexToken, media.server.accessToken); PlexHttpService service = getService(String.format("http://%s:%s", connection.address, connection.port)); Call<MediaContainer> call = service.createPlayQueue(qs, VoiceControlForPlexApplication.getUUID()); call.enqueue(new Callback<MediaContainer>() { @Override public void onResponse(Response<MediaContainer> response, Retrofit retrofit) { if (responseHandler != null) { MediaContainer mc = response.body(); for(int i=0;i<mc.tracks.size();i++) { mc.tracks.get(i).server = media.server; } for(int i=0;i<mc.videos.size();i++) { mc.videos.get(i).server = media.server; if (mc.videos.get(i).isClip()) mc.videos.get(i).setClipDuration(); } responseHandler.onSuccess(mc); } } @Override public void onFailure(Throwable t) { Logger.d("createPlayQueue failure."); t.printStackTrace(); } }); } public static void getDebug(String baseHostname, String path, final PlexHttpResponseHandler responseHandler) { get(baseHostname, path, true, responseHandler); } public static void get(String baseHostname, String path, final PlexHttpResponseHandler responseHandler) { get(baseHostname, path, false, responseHandler); } public static void get(String baseHostname, String path, boolean debug, final PlexHttpResponseHandler responseHandler) { PlexHttpService service = getService(baseHostname, debug); Call<PlexResponse> call = service.getPlexResponse(VoiceControlForPlexApplication.getInstance().prefs.getUUID(), path.replaceFirst("^/", "")); call.enqueue(new Callback<PlexResponse>() { @Override public void onResponse(Response<PlexResponse> response, Retrofit retrofit) { if (responseHandler != null) responseHandler.onSuccess(response.body()); } @Override public void onFailure(Throwable t) { if (responseHandler != null) responseHandler.onFailure(t); } }); } public static void getPinCode(final PlexPinResponseHandler responseHandler) { PlexHttpService service = getService("https://plex.tv:443"); service.newGetPinCode(VoiceControlForPlexApplication.getUUID()) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(pin -> responseHandler.onSuccess(pin), t -> responseHandler.onFailure(t)); } public static void signin(String authToken, final PlexHttpUserHandler responseHandler) { signin(null, null, authToken, responseHandler); } public static void signin(String username, String password, final PlexHttpUserHandler responseHandler) { signin(username, password, null, responseHandler); } public static void signin(String username, String password, String authToken, final PlexHttpUserHandler responseHandler) { PlexHttpService service = getService("https://plex.tv", username, password, false); Call<PlexUser> call = service.signin("Android", VoiceControlForPlexApplication.getUUID(), authToken); call.enqueue(new Callback<PlexUser>() { @Override public void onResponse(Response<PlexUser> response, Retrofit retrofit) { if(response.code() == 200 || response.code() == 201) responseHandler.onSuccess(response.body()); else { responseHandler.onFailure(response.code()); } } @Override public void onFailure(Throwable t) { t.printStackTrace(); responseHandler.onFailure(0); } }); } public static byte[] getSyncBytes(String url) throws SocketTimeoutException { Request request = new Request.Builder() .url(url) .build(); try { com.squareup.okhttp.Response response = httpClient.newCall(request).execute(); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); return response.body().bytes(); } catch (IOException ex) { ex.printStackTrace(); } return null; } public static void getClientTimeline(PlexClient client, final int commandId, final PlexHttpMediaContainerHandler responseHandler) { String url = String.format("http://%s:%s", client.address, client.port); PlexHttpService service = getService(url); Logger.d("Polling timeline with uuid %s", VoiceControlForPlexApplication.getInstance().prefs.getUUID()); Call<MediaContainer> call = service.pollTimeline(VoiceControlForPlexApplication.getInstance().prefs.getUUID(), commandId); call.enqueue(new Callback<MediaContainer>() { @Override public void onResponse(Response<MediaContainer> response, Retrofit retrofit) { responseHandler.onSuccess(response.body()); } @Override public void onFailure(Throwable t) { responseHandler.onFailure(t); } }); } public static void fetchPin(int pinID, final PlexPinResponseHandler responseHandler) { String url = "https://plex.tv:443"; PlexHttpService service = getService(url); service.newFetchPin(pinID, VoiceControlForPlexApplication.getInstance().prefs.getUUID()) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(s -> responseHandler.onSuccess(s), s -> responseHandler.onFailure(new Throwable())); } public static void getPlexAccount(String authToken, final PlexHttpUserHandler responseHandler) { String url = "https://plex.tv:443"; PlexHttpService service = getService(url); Call<PlexUser> call = service.getPlexAccount(authToken); call.enqueue(new Callback<PlexUser>() { @Override public void onResponse(Response<PlexUser> response, Retrofit retrofit) { if(responseHandler != null) responseHandler.onSuccess(response.body()); } @Override public void onFailure(Throwable t) { } }); } public static void getRandomMovie(final PlexServer server, final PlexMediaHandler onFinish) { server.findServerConnection(new ActiveConnectionHandler() { @Override public void onSuccess(Connection connection) { PlexHttpService service = getService(connection); service.getRandomMovie(server.getRandomMovieSection(), server.accessToken) .subscribeOn(Schedulers.io()) .map(mc -> mc.getRandomVideo()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(video -> onFinish.onFinish(video), e -> onFinish.onFinish(null)); } @Override public void onFailure(int statusCode) { if(onFinish != null) onFinish.onFinish(null); } }); } public static void getRandomShow(final PlexServer server, final PlexDirectoryHandler handler) { server.findServerConnection(new ActiveConnectionHandler() { @Override public void onSuccess(Connection connection) { PlexHttpService service = getService(connection); service.getRandomShow(server.getRandomTvSection(), server.accessToken) .subscribeOn(Schedulers.io()) .map(mc -> mc.getRandomDirectory()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(show -> handler.onFinish(show), e -> handler.onFinish(null)); } @Override public void onFailure(int statusCode) { } }); } public static void getRandomOnDeck(final PlexServer server, final PlexMediaHandler onFinish) { server.findServerConnection(new ActiveConnectionHandler() { @Override public void onSuccess(Connection connection) { PlexHttpService service = getService(connection); service.getRandomOnDeck(server.accessToken) .subscribeOn(Schedulers.io()) .map(mc -> mc.getRandomVideo()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(video -> onFinish.onFinish(video), e -> onFinish.onFinish(null)); } @Override public void onFailure(int statusCode) { if(onFinish != null) onFinish.onFinish(null); } }); } public static void getRandomEpisode(final PlexServer server, final Connection connection, final String showSpecified, final String section, final PlexMediaHandler handler) { final PlexHttpService service = getService(connection); service.searchForShow(section, showSpecified, server.accessToken) .subscribeOn(Schedulers.io()) .filter(shows -> shows != null) .map(shows -> shows.getRandomDirectory()) .flatMap(show -> service.newGetRandomEpisode(show.ratingKey, server.accessToken)) .map(episodes -> episodes.getRandomVideo()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(video -> handler.onFinish(video), e -> handler.onFinish(null)); } public static void getRandomEpisode(final PlexServer server, final PlexMediaHandler onFinish) { server.findServerConnection(new ActiveConnectionHandler() { @Override public void onSuccess(final Connection connection) { final PlexHttpService service = getService(connection); service.newGetRandomDirectory(server.getRandomTvSection(), server.accessToken) .subscribeOn(Schedulers.io()) .map(shows -> shows.getRandomDirectory()) .flatMap(show -> service.newGetRandomEpisode(show.ratingKey, server.accessToken)) .map(episodes -> episodes.getRandomVideo()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(video -> onFinish.onFinish(video), e -> onFinish.onFinish(null)); } @Override public void onFailure(int statusCode) { if(onFinish != null) onFinish.onFinish(null); } }); } public static void getRandomSong(final PlexServer server, final PlexMediaHandler onFinish) { server.findServerConnection(new ActiveConnectionHandler() { @Override public void onSuccess(Connection connection) { final PlexHttpService service = getService(connection); String section = server.musicSections.get(new Random().nextInt(server.musicSections.size())); service.newGetRandomDirectory(section, server.accessToken) .subscribeOn(Schedulers.io()) .map(artists -> artists.getRandomDirectory()) .flatMap(artist -> service.newGetMediaContainer(artist.key, server.accessToken)) .map(albums -> albums.getRandomDirectory()) .flatMap(album -> service.newGetMediaContainer(album.key, server.accessToken)) .map(songs -> songs.getRandomTrack()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(track -> onFinish.onFinish(track), e -> onFinish.onFinish(null)); } @Override public void onFailure(int statusCode) { if(onFinish != null) onFinish.onFinish(null); } }); } public static void getRandomAlbum(final PlexServer server, final PlexDirectoryHandler onFinish) { server.findServerConnection(new ActiveConnectionHandler() { @Override public void onSuccess(Connection connection) { final PlexHttpService service = getService(connection); service.newGetRandomDirectory(server.getRandomMusicSection(), server.accessToken) .subscribeOn(Schedulers.io()) .map(artists -> artists.getRandomDirectory()) .flatMap(artist -> service.newGetMediaContainer(artist.key, server.accessToken)) .map(albums -> albums.getRandomDirectory()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(album -> onFinish.onFinish(album), e -> onFinish.onFinish(null)); } @Override public void onFailure(int statusCode) { if(onFinish != null) onFinish.onFinish(null); } }); } public static void getRandomArtist(final PlexServer server, final PlexDirectoryHandler onFinish) { server.findServerConnection(new ActiveConnectionHandler() { @Override public void onSuccess(Connection connection) { final PlexHttpService service = getService(connection); service.newGetRandomDirectory(server.getRandomMusicSection(), server.accessToken) .subscribeOn(Schedulers.io()) .map(artists -> artists.getRandomDirectory()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(artist -> onFinish.onFinish(artist), e -> onFinish.onFinish(null)); } @Override public void onFailure(int statusCode) { if(onFinish != null) onFinish.onFinish(null); } }); } public static void stopTranscoder(final PlexServer server, final String session, final String type) { stopTranscoder(server, session, type, null); } public static void stopTranscoder(final PlexServer server, final String session, final String type, final GenericHandler handler) { server.findServerConnection(new ActiveConnectionHandler() { @Override public void onSuccess(Connection connection) { PlexHttpService service = getService(connection.uri); Call<PlexResponse> call = service.stopTranscoder(type, server.accessToken, session, VoiceControlForPlexApplication.getInstance().prefs.getUUID()); call.enqueue(new Callback<PlexResponse>() { @Override public void onResponse(Response<PlexResponse> response, Retrofit retrofit) { Logger.d("Stopped transcoder"); if(handler != null) handler.onSuccess(); } @Override public void onFailure(Throwable t) { if(handler != null) handler.onFailure(); } }); } @Override public void onFailure(int statusCode) { if(handler != null) handler.onFailure(); } }); } public static void reportProgressToServer(final PlexMedia media, final int time, final PlayerState state) { media.server.findServerConnection(new ActiveConnectionHandler() { @Override public void onSuccess(Connection connection) { PlexHttpService service = getService(connection.uri); Call<PlexResponse> call = service.reportProgressToServer( media.server.accessToken, media.key, media.ratingKey, Integer.toString(time), Integer.toString(media.duration), state.toStateString(), VoiceControlForPlexApplication.getInstance().prefs.getUUID(), VoiceControlForPlexApplication.getInstance().getString(R.string.app_name), "Android", VoiceControlForPlexApplication.getInstance().getString(R.string.app_name) ); call.enqueue(new Callback<PlexResponse>() { @Override public void onResponse(Response<PlexResponse> response, Retrofit retrofit) { // Logger.d("Done reporting"); } @Override public void onFailure(Throwable t) { // TODO: Handle } }); } @Override public void onFailure(int statusCode) { // TODO: Handle } }); } public static void setStreamActive(final PlexMedia media, final Stream stream, final Runnable onFinish) { media.server.findServerConnection(new ActiveConnectionHandler() { @Override public void onSuccess(Connection connection) { PlexHttpService service = getService(connection); Call<PlexResponse> call = null; if(stream.streamType == Stream.SUBTITLE) { call = service.setSubtitleStreamActive(stream.partId, stream.id, media.server.accessToken); } else if(stream.streamType == Stream.AUDIO) { call = service.setAudioStreamActive(stream.partId, stream.id, media.server.accessToken); } if(call != null) { call.enqueue(new Callback<PlexResponse>() { @Override public void onResponse(Response<PlexResponse> response, Retrofit retrofit) { Logger.d("set stream done"); if(onFinish != null && response.body().code == 200) onFinish.run(); } @Override public void onFailure(Throwable t) { } }); } } @Override public void onFailure(int statusCode) { } }); } public static void getChildren(final PlexDirectory directory, final PlexServer server, final PlexHttpMediaContainerHandler handler) { server.findServerConnection(new ActiveConnectionHandler() { @Override public void onSuccess(Connection connection) { PlexHttpService service = getService(connection); Call<MediaContainer> call = service.getChildren(directory.ratingKey, server.accessToken); call.enqueue(new Callback<MediaContainer>() { @Override public void onResponse(Response<MediaContainer> response, Retrofit retrofit) { MediaContainer mc = response.body(); if(handler != null) handler.onSuccess(mc); } @Override public void onFailure(Throwable t) { if(handler != null) handler.onFailure(t); } }); } @Override public void onFailure(int statusCode) { if(handler != null) handler.onFailure(new Throwable()); } }); } public static void getOnDeck(final PlexServer server, final String section, final PlexHttpMediaContainerHandler handler) { server.findServerConnection(new ActiveConnectionHandler() { @Override public void onSuccess(Connection connection) { PlexHttpService service = getService(connection); service.getOnDeck(section, server.accessToken) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(mc -> handler.onSuccess(mc), e -> handler.onFailure(new Throwable())); } @Override public void onFailure(int statusCode) { if(handler != null) handler.onFailure(new Throwable()); } }); } public static void getRecentlyAdded(final PlexServer server, final String section, final PlexHttpMediaContainerHandler handler) { server.findServerConnection(new ActiveConnectionHandler() { @Override public void onSuccess(Connection connection) { PlexHttpService service = getService(connection); service.getRecentlyAdded(section, server.accessToken) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(mc -> handler.onSuccess(mc), e -> handler.onFailure(new Throwable())); } @Override public void onFailure(int statusCode) { if(handler != null) handler.onFailure(new Throwable()); } }); } }