From f14e512d116208d32a45627494e667d1c86880b3 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Sat, 25 Jul 2020 22:37:25 +0200 Subject: [PATCH] Cleanup and refactor some cast-related code --- .../android/cast/CastOptionsProvider.java | 34 - .../android/cast/CastOptionsProvider.kt | 30 + .../android/cast/ChromecastConnection.java | 345 ++++----- .../android/cast/ChromecastSession.java | 685 +++++++++--------- .../android/cast/ChromecastUtilities.java | 140 ++-- 5 files changed, 564 insertions(+), 670 deletions(-) delete mode 100644 app/src/main/java/org/jellyfin/android/cast/CastOptionsProvider.java create mode 100644 app/src/main/java/org/jellyfin/android/cast/CastOptionsProvider.kt diff --git a/app/src/main/java/org/jellyfin/android/cast/CastOptionsProvider.java b/app/src/main/java/org/jellyfin/android/cast/CastOptionsProvider.java deleted file mode 100644 index ded0e6db..00000000 --- a/app/src/main/java/org/jellyfin/android/cast/CastOptionsProvider.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.jellyfin.android.cast; - -import java.util.List; - -import com.google.android.gms.cast.framework.CastOptions; -import com.google.android.gms.cast.framework.OptionsProvider; -import com.google.android.gms.cast.framework.SessionProvider; - -import android.content.Context; - -public final class CastOptionsProvider implements OptionsProvider { - - /** The app id. */ - private static String appId; - - /** - * Sets the app ID. - * @param applicationId appId - */ - public static void setAppId(String applicationId) { - appId = applicationId; - } - - @Override - public CastOptions getCastOptions(Context context) { - return new CastOptions.Builder() - .setReceiverApplicationId(appId) - .build(); - } - @Override - public List getAdditionalSessionProviders(Context context) { - return null; - } -} diff --git a/app/src/main/java/org/jellyfin/android/cast/CastOptionsProvider.kt b/app/src/main/java/org/jellyfin/android/cast/CastOptionsProvider.kt new file mode 100644 index 00000000..6cb31d12 --- /dev/null +++ b/app/src/main/java/org/jellyfin/android/cast/CastOptionsProvider.kt @@ -0,0 +1,30 @@ +package org.jellyfin.android.cast + +import android.content.Context +import com.google.android.gms.cast.framework.CastOptions +import com.google.android.gms.cast.framework.OptionsProvider +import com.google.android.gms.cast.framework.SessionProvider + +class CastOptionsProvider : OptionsProvider { + override fun getCastOptions(context: Context): CastOptions { + return CastOptions.Builder().setReceiverApplicationId(appId).build() + } + + override fun getAdditionalSessionProviders(context: Context): List? { + return null + } + + companion object { + /** The app id. */ + private var appId: String? = null + + /** + * Sets the app ID. + * @param applicationId appId + */ + @JvmStatic + fun setAppId(applicationId: String?) { + appId = applicationId + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/jellyfin/android/cast/ChromecastConnection.java b/app/src/main/java/org/jellyfin/android/cast/ChromecastConnection.java index cfd73058..8163b481 100644 --- a/app/src/main/java/org/jellyfin/android/cast/ChromecastConnection.java +++ b/app/src/main/java/org/jellyfin/android/cast/ChromecastConnection.java @@ -2,7 +2,6 @@ package org.jellyfin.android.cast; import android.app.Activity; import android.app.AlertDialog; -import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; @@ -22,10 +21,12 @@ import com.google.android.gms.cast.framework.CastStateListener; import com.google.android.gms.cast.framework.SessionManager; import com.google.android.gms.cast.framework.SessionManagerListener; +import org.jellyfin.android.R; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class ChromecastConnection { @@ -196,119 +197,107 @@ public class ChromecastConnection { * or callback.onError if an error occurred */ public void selectRoute(final String routeId, SelectRouteCallback callback) { - activity.runOnUiThread(new Runnable() { - public void run() { - if (getSession() != null && getSession().isConnected()) { - callback.onError(ChromecastUtilities.createError("session_error", - "Leave or stop current session before attempting to join new session.")); - return; - } + activity.runOnUiThread(() -> { + if (getSession() != null && getSession().isConnected()) { + callback.onError(ChromecastUtilities.createError("session_error", + "Leave or stop current session before attempting to join new session.")); + return; + } - // We need this hack so that we can access these values in callbacks without having - // to store it as a global variable, just always access first element - final boolean[] foundRoute = {false}; - final boolean[] sentResult = {false}; - final int[] retries = {0}; + // We need this hack so that we can access these values in callbacks without having + // to store it as a global variable, just always access first element + final boolean[] foundRoute = {false}; + final boolean[] sentResult = {false}; + final int[] retries = {0}; - // We need to start an active scan because getMediaRouter().getRoutes() may be out - // of date. Also, maintaining a list of known routes doesn't work. It is possible - // to have a route in your "known" routes list, but is not in - // getMediaRouter().getRoutes() which will result in "Ignoring attempt to select - // removed route: ", even if that route *should* be available. This state could - // happen because routes are periodically "removed" and "added", and if the last - // time media router was scanning ended when the route was temporarily removed the - // getRoutes() fn will have no record of the route. We need the active scan to - // avoid this situation as well. PS. Just running the scan non-stop is a poor idea - // since it will drain battery power quickly. - ScanCallback scan = new ScanCallback() { - @Override - void onRouteUpdate(List routes) { - // Look for the matching route - for (RouteInfo route : routes) { - if (!foundRoute[0] && route.getId().equals(routeId)) { - // Found the route! - foundRoute[0] = true; - // try-catch for issue: - // https://github.com/jellyfin/cordova-plugin-chromecast/issues/48 - try { - // Try selecting the route! - getMediaRouter().selectRoute(route); - } catch (NullPointerException e) { - // Let it try to find the route again - foundRoute[0] = false; - } + // We need to start an active scan because getMediaRouter().getRoutes() may be out + // of date. Also, maintaining a list of known routes doesn't work. It is possible + // to have a route in your "known" routes list, but is not in + // getMediaRouter().getRoutes() which will result in "Ignoring attempt to select + // removed route: ", even if that route *should* be available. This state could + // happen because routes are periodically "removed" and "added", and if the last + // time media router was scanning ended when the route was temporarily removed the + // getRoutes() fn will have no record of the route. We need the active scan to + // avoid this situation as well. PS. Just running the scan non-stop is a poor idea + // since it will drain battery power quickly. + ScanCallback scan = new ScanCallback() { + @Override + void onRouteUpdate(List routes) { + // Look for the matching route + for (RouteInfo route : routes) { + if (!foundRoute[0] && route.getId().equals(routeId)) { + // Found the route! + foundRoute[0] = true; + // try-catch for issue: + // https://github.com/jellyfin/cordova-plugin-chromecast/issues/48 + try { + // Try selecting the route! + getMediaRouter().selectRoute(route); + } catch (NullPointerException e) { + // Let it try to find the route again + foundRoute[0] = false; } } } - }; + } + }; - Runnable retry = new Runnable() { - @Override - public void run() { - // Reset foundRoute - foundRoute[0] = false; - // Feed current routes into scan so that it can retry. - // If route is there, it will try to join, - // if not, it should wait for the scan to find the route - scan.onRouteUpdate(getMediaRouter().getRoutes()); - } - }; + Runnable retry = () -> { + // Reset foundRoute + foundRoute[0] = false; + // Feed current routes into scan so that it can retry. + // If route is there, it will try to join, + // if not, it should wait for the scan to find the route + scan.onRouteUpdate(getMediaRouter().getRoutes()); + }; - Function sendErrorResult = new Function() { - @Override - public Void apply(JSONObject message) { - if (!sentResult[0]) { - sentResult[0] = true; - stopRouteScan(scan, null); - callback.onError(message); - } - return null; - } - }; + Function sendErrorResult = message -> { + if (!sentResult[0]) { + sentResult[0] = true; + stopRouteScan(scan, null); + callback.onError(message); + } + return null; + }; - listenForConnection(new ConnectionCallback() { - @Override - public void onJoin(JSONObject jsonSession) { - sentResult[0] = true; - stopRouteScan(scan, null); - callback.onJoin(jsonSession); - } + listenForConnection(new ConnectionCallback() { + @Override + public void onJoin(JSONObject jsonSession) { + sentResult[0] = true; + stopRouteScan(scan, null); + callback.onJoin(jsonSession); + } - @Override - public boolean onSessionStartFailed(int errorCode) { - if (errorCode == 7 || errorCode == 15) { - // It network or timeout error retry - retry.run(); - return false; - } else { - sendErrorResult.apply(ChromecastUtilities.createError("session_error", - "Failed to start session with error code: " + errorCode)); - return true; - } + @Override + public boolean onSessionStartFailed(int errorCode) { + if (errorCode == 7 || errorCode == 15) { + // It network or timeout error retry + retry.run(); + return false; + } else { + sendErrorResult.apply(ChromecastUtilities.createError("session_error", + "Failed to start session with error code: " + errorCode)); + return true; } + } - @Override - public boolean onSessionEndedBeforeStart(int errorCode) { - if (retries[0] < 10) { - retries[0]++; - retry.run(); - return false; - } else { - sendErrorResult.apply(ChromecastUtilities.createError("session_error", - "Failed to to join existing route (" + routeId + ") " + retries[0] + 1 + " times before giving up.")); - return true; - } + @Override + public boolean onSessionEndedBeforeStart(int errorCode) { + if (retries[0] < 10) { + retries[0]++; + retry.run(); + return false; + } else { + sendErrorResult.apply(ChromecastUtilities.createError("session_error", + "Failed to to join existing route (" + routeId + ") " + retries[0] + 1 + " times before giving up.")); + return true; } - }); + } + }); - startRouteScan(15000L, scan, new Runnable() { - @Override - public void run() { - sendErrorResult.apply(ChromecastUtilities.createError("timeout", - "Failed to join route (" + routeId + ") after 15s and " + (retries[0] + 1) + " tries.")); - } - }); - } + startRouteScan(15000L, scan, () -> + sendErrorResult.apply(ChromecastUtilities.createError("timeout", "Failed to join route (" + routeId + ") after 15s and " + (retries[0] + 1) + " tries.")) + ); }); } @@ -332,50 +321,37 @@ public class ChromecastConnection { * or callback.error if an error occurred or if the dialog was dismissed */ public void requestSession(RequestSessionCallback callback) { - activity.runOnUiThread(new Runnable() { - public void run() { - CastSession session = getSession(); - if (session == null) { - // show the "choose a connection" dialog + activity.runOnUiThread(() -> { + CastSession session = getSession(); + if (session == null) { + // show the "choose a connection" dialog - // Add the connection listener callback - listenForConnection(callback); + // Add the connection listener callback + listenForConnection(callback); - // Create the dialog - // TODO accept theme as a config.xml option - MediaRouteChooserDialog builder = new MediaRouteChooserDialog(activity, androidx.appcompat.R.style.Theme_AppCompat_NoActionBar); - builder.setRouteSelector(new MediaRouteSelector.Builder() - .addControlCategory(CastMediaControlIntent.categoryForCast(appId)) - .build()); - builder.setCanceledOnTouchOutside(true); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - getSessionManager().removeSessionManagerListener(newConnectionListener, CastSession.class); - callback.onCancel(); - } - }); - builder.show(); - } else { - // We are are already connected, so show the "connection options" Dialog - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - if (session.getCastDevice() != null) { - builder.setTitle(session.getCastDevice().getFriendlyName()); - } - builder.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - callback.onCancel(); - } - }); - builder.setPositiveButton("Stop Casting", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - endSession(true, null); - } - }); - builder.show(); + // Create the dialog + // TODO accept theme as a config.xml option + MediaRouteChooserDialog builder = new MediaRouteChooserDialog(activity, R.style.AppTheme); + builder.setRouteSelector(new MediaRouteSelector.Builder() + .addControlCategory(CastMediaControlIntent.categoryForCast(appId)) + .build()); + builder.setCanceledOnTouchOutside(true); + builder.setOnCancelListener(dialog -> { + getSessionManager().removeSessionManagerListener(newConnectionListener, CastSession.class); + callback.onCancel(); + }); + builder.show(); + } else { + // We are are already connected, so show the "connection options" Dialog + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + if (session.getCastDevice() != null) { + builder.setTitle(session.getCastDevice().getFriendlyName()); } + builder.setOnDismissListener(dialog -> callback.onCancel()); + builder.setPositiveButton("Stop Casting", (dialog, which) -> + endSession(true, null) + ); + builder.show(); } }); } @@ -425,43 +401,38 @@ public class ChromecastConnection { */ public void startRouteScan(Long timeout, ScanCallback callback, Runnable onTimeout) { // Add the callback in active scan mode - activity.runOnUiThread(new Runnable() { - public void run() { - callback.setMediaRouter(getMediaRouter()); + activity.runOnUiThread(() -> { + callback.setMediaRouter(getMediaRouter()); - if (timeout != null && timeout == 0) { - // Send out the one time routes - callback.onFilteredRouteUpdate(); - return; - } - - // Add the callback in active scan mode - getMediaRouter().addCallback(new MediaRouteSelector.Builder() - .addControlCategory(CastMediaControlIntent.categoryForCast(appId)) - .build(), - callback, - MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); - - // Send out the initial routes after the callback has been added. - // This is important because if the callback calls stopRouteScan only once, and it - // happens during this call of "onFilterRouteUpdate", there must actually be an - // added callback to remove to stop the scan. + if (timeout != null && timeout == 0) { + // Send out the one time routes callback.onFilteredRouteUpdate(); + return; + } - if (timeout != null) { - // remove the callback after timeout ms, and notify caller - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - // And stop the scan for routes - getMediaRouter().removeCallback(callback); - // Notify - if (onTimeout != null) { - onTimeout.run(); - } - } - }, timeout); - } + // Add the callback in active scan mode + getMediaRouter().addCallback(new MediaRouteSelector.Builder() + .addControlCategory(CastMediaControlIntent.categoryForCast(appId)) + .build(), + callback, + MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); + + // Send out the initial routes after the callback has been added. + // This is important because if the callback calls stopRouteScan only once, and it + // happens during this call of "onFilterRouteUpdate", there must actually be an + // added callback to remove to stop the scan. + callback.onFilteredRouteUpdate(); + + if (timeout != null) { + // remove the callback after timeout ms, and notify caller + new Handler().postDelayed(() -> { + // And stop the scan for routes + getMediaRouter().removeCallback(callback); + // Notify + if (onTimeout != null) { + onTimeout.run(); + } + }, timeout); } }); } @@ -477,13 +448,11 @@ public class ChromecastConnection { completionCallback.run(); return; } - activity.runOnUiThread(new Runnable() { - public void run() { - callback.stop(); - getMediaRouter().removeCallback(callback); - if (completionCallback != null) { - completionCallback.run(); - } + activity.runOnUiThread(() -> { + callback.stop(); + getMediaRouter().removeCallback(callback); + if (completionCallback != null) { + completionCallback.run(); } }); } @@ -518,7 +487,7 @@ public class ChromecastConnection { * Create this empty class so that we don't have to override every function * each time we need a SessionManagerListener. */ - private class SessionListener implements SessionManagerListener { + private static class SessionListener implements SessionManagerListener { @Override public void onSessionStarting(CastSession castSession) { } @@ -660,10 +629,7 @@ public class ChromecastConnection { continue; } } - if (!route.isDefault() - && !route.getDescription().equals("Google Cast Multizone Member") - && route.getPlaybackType() == RouteInfo.PLAYBACK_TYPE_REMOTE - ) { + if (!route.isDefault() && !Objects.equals(route.getDescription(), "Google Cast Multizone Member") && route.getPlaybackType() == RouteInfo.PLAYBACK_TYPE_REMOTE) { outRoutes.add(route); } } @@ -699,5 +665,4 @@ public class ChromecastConnection { onReceiverAvailableUpdate(state != CastState.NO_DEVICES_AVAILABLE); } } - -} +} \ No newline at end of file diff --git a/app/src/main/java/org/jellyfin/android/cast/ChromecastSession.java b/app/src/main/java/org/jellyfin/android/cast/ChromecastSession.java index c1f679fa..53821cb8 100644 --- a/app/src/main/java/org/jellyfin/android/cast/ChromecastSession.java +++ b/app/src/main/java/org/jellyfin/android/cast/ChromecastSession.java @@ -1,11 +1,8 @@ package org.jellyfin.android.cast; -import java.io.IOException; -import java.util.ArrayList; +import android.app.Activity; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; +import androidx.annotation.NonNull; import com.google.android.gms.cast.ApplicationMetadata; import com.google.android.gms.cast.Cast; @@ -19,36 +16,55 @@ import com.google.android.gms.cast.framework.media.MediaQueue; import com.google.android.gms.cast.framework.media.RemoteMediaClient; import com.google.android.gms.cast.framework.media.RemoteMediaClient.MediaChannelResult; import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.api.Status; -import android.app.Activity; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; -import androidx.annotation.NonNull; +import java.io.IOException; +import java.util.ArrayList; /* * All of the Chromecast session specific functions should start here. */ public class ChromecastSession { - /** The current context. */ + /** + * The current context. + */ private Activity activity; - /** A registered callback that we will un-register and re-register each time the session changes. */ + /** + * A registered callback that we will un-register and re-register each time the session changes. + */ private Listener clientListener; - /** The current session. */ + /** + * The current session. + */ private CastSession session; - /** The current session's client for controlling playback. */ + /** + * The current session's client for controlling playback. + */ private RemoteMediaClient client; - /** Indicates whether we are requesting media or not. **/ + /** + * Indicates whether we are requesting media or not. + */ private boolean requestingMedia = false; - /** Handles and used to trigger queue updates. **/ + /** + * Handles and used to trigger queue updates. + */ private MediaQueueController mediaQueueCallback; - /** Stores a callback that should be called when the queue is loaded. **/ + /** + * Stores a callback that should be called when the queue is loaded. + */ private Runnable queueReloadCallback; - /** Stores a callback that should be called when the queue status is updated. **/ + /** + * Stores a callback that should be called when the queue status is updated. + */ private Runnable queueStatusUpdatedCallback; /** * ChromecastSession constructor. - * @param act the current activity + * + * @param act the current activity * @param listener callback that will notify of certain events */ public ChromecastSession(Activity act, @NonNull Listener listener) { @@ -58,155 +74,149 @@ public class ChromecastSession { /** * Sets the session object the will be used for other commands in this class. + * * @param castSession the session to use */ public void setSession(CastSession castSession) { - activity.runOnUiThread(new Runnable() { - public void run() { - if (castSession == null) { - client = null; - return; - } - if (castSession.equals(session)) { - // Don't client and listeners if session did not change - return; - } - session = castSession; - client = session.getRemoteMediaClient(); - if (client == null) { - return; - } - setupQueue(); - client.registerCallback(new RemoteMediaClient.Callback() { - private Integer prevItemId; - @Override - public void onStatusUpdated() { - MediaStatus status = client.getMediaStatus(); - if (requestingMedia - || queueStatusUpdatedCallback != null - || queueReloadCallback != null) { + activity.runOnUiThread(() -> { + if (castSession == null) { + client = null; + return; + } + if (castSession.equals(session)) { + // Don't client and listeners if session did not change + return; + } + session = castSession; + client = session.getRemoteMediaClient(); + if (client == null) { + return; + } + setupQueue(); + client.registerCallback(new RemoteMediaClient.Callback() { + private Integer prevItemId; + + @Override + public void onStatusUpdated() { + MediaStatus status = client.getMediaStatus(); + if (requestingMedia + || queueStatusUpdatedCallback != null + || queueReloadCallback != null) { + return; + } + + if (status != null) { + if (prevItemId == null) { + prevItemId = status.getCurrentItemId(); + } + boolean shouldSkipUpdate = false; + if (status.getPlayerState() == MediaStatus.PLAYER_STATE_LOADING) { + // It appears the queue has advanced to the next item + // So send an update to indicate the previous has finished + clientListener.onMediaUpdate(createMediaObject(MediaStatus.IDLE_REASON_FINISHED)); + shouldSkipUpdate = true; + } + if (prevItemId != null && prevItemId != status.getCurrentItemId() && mediaQueueCallback.getCurrentItemIndex() != -1) { + // The currentItem has changed, so update the current queue items + setQueueReloadCallback(() -> prevItemId = status.getCurrentItemId()); + mediaQueueCallback.refreshQueueItems(); + shouldSkipUpdate = true; + } + if (shouldSkipUpdate) { return; } + } + // Send update + clientListener.onMediaUpdate(createMediaObject()); + } - if (status != null) { - if (prevItemId == null) { - prevItemId = status.getCurrentItemId(); - } - boolean shouldSkipUpdate = false; - if (status.getPlayerState() == MediaStatus.PLAYER_STATE_LOADING) { - // It appears the queue has advanced to the next item - // So send an update to indicate the previous has finished - clientListener.onMediaUpdate(createMediaObject(MediaStatus.IDLE_REASON_FINISHED)); - shouldSkipUpdate = true; - } - if (prevItemId != null && prevItemId != status.getCurrentItemId() && mediaQueueCallback.getCurrentItemIndex() != -1) { - // The currentItem has changed, so update the current queue items - setQueueReloadCallback(new Runnable() { - @Override - public void run() { - prevItemId = status.getCurrentItemId(); - } - }); - mediaQueueCallback.refreshQueueItems(); - shouldSkipUpdate = true; - } - if (shouldSkipUpdate) { - return; - } - } - // Send update - clientListener.onMediaUpdate(createMediaObject()); + @Override + public void onQueueStatusUpdated() { + if (queueStatusUpdatedCallback != null) { + queueStatusUpdatedCallback.run(); + setQueueStatusUpdatedCallback(null); } - @Override - public void onQueueStatusUpdated() { - if (queueStatusUpdatedCallback != null) { - queueStatusUpdatedCallback.run(); - setQueueStatusUpdatedCallback(null); - } - } - }); - session.addCastListener(new Cast.Listener() { - @Override - public void onApplicationStatusChanged() { - clientListener.onSessionUpdate(createSessionObject()); - } - @Override - public void onApplicationMetadataChanged(ApplicationMetadata appMetadata) { - clientListener.onSessionUpdate(createSessionObject()); - } - @Override - public void onApplicationDisconnected(int i) { - clientListener.onSessionEnd( - ChromecastUtilities.createSessionObject(session, "stopped")); - } - @Override - public void onActiveInputStateChanged(int i) { - clientListener.onSessionUpdate(createSessionObject()); - } - @Override - public void onStandbyStateChanged(int i) { - clientListener.onSessionUpdate(createSessionObject()); - } - @Override - public void onVolumeChanged() { - clientListener.onSessionUpdate(createSessionObject()); - } - }); - } + } + }); + session.addCastListener(new Cast.Listener() { + @Override + public void onApplicationStatusChanged() { + clientListener.onSessionUpdate(createSessionObject()); + } + + @Override + public void onApplicationMetadataChanged(ApplicationMetadata appMetadata) { + clientListener.onSessionUpdate(createSessionObject()); + } + + @Override + public void onApplicationDisconnected(int i) { + clientListener.onSessionEnd( + ChromecastUtilities.createSessionObject(session, "stopped")); + } + + @Override + public void onActiveInputStateChanged(int i) { + clientListener.onSessionUpdate(createSessionObject()); + } + + @Override + public void onStandbyStateChanged(int i) { + clientListener.onSessionUpdate(createSessionObject()); + } + + @Override + public void onVolumeChanged() { + clientListener.onSessionUpdate(createSessionObject()); + } + }); }); } /** * Adds a message listener if one does not already exist. + * * @param namespace namespace */ public void addMessageListener(String namespace) { if (client == null || session == null) { return; } - activity.runOnUiThread(new Runnable() { - public void run() { - try { - session.setMessageReceivedCallbacks(namespace, clientListener); - } catch (IOException e) { - e.printStackTrace(); - } + activity.runOnUiThread(() -> { + try { + session.setMessageReceivedCallbacks(namespace, clientListener); + } catch (IOException e) { + e.printStackTrace(); } }); } /** * Sends a message to a specified namespace. + * * @param namespace namespace - * @param message the message to send - * @param callback called with success or error + * @param message the message to send + * @param callback called with success or error */ public void sendMessage(String namespace, String message, CallbackContext callback) { if (client == null || session == null) { callback.errorString("session_error"); return; } - activity.runOnUiThread(new Runnable() { - public void run() { - session.sendMessage(namespace, message).setResultCallback(new ResultCallback() { - @Override - public void onResult(Status result) { - if (!result.isSuccess()) { - callback.success(); - } else { - callback.errorString(result.toString()); - } - } - }); - + activity.runOnUiThread(() -> session.sendMessage(namespace, message).setResultCallback(result -> { + if (!result.isSuccess()) { + callback.success(); + } else { + callback.errorString(result.toString()); } - }); + })); } -/* ------------------------------------ MEDIA FNs ------------------------------------------- */ + /* ------------------------------------ MEDIA FNs ------------------------------------------- */ /** * Loads media over the media API. + * * @param contentId - The URL of the content * @param customData - CustomData * @param contentType - The MIME type of the content @@ -216,45 +226,36 @@ public class ChromecastSession { * @param currentTime - Where in the video to begin playing from * @param metadata - Metadata * @param textTrackStyle - The text track style - * @param callback called with success or error + * @param callback called with success or error */ public void loadMedia(String contentId, JSONObject customData, String contentType, long duration, String streamType, boolean autoPlay, double currentTime, JSONObject metadata, JSONObject textTrackStyle, CallbackContext callback) { if (client == null || session == null) { callback.errorString("session_error"); return; } - activity.runOnUiThread(new Runnable() { - public void run() { - MediaInfo mediaInfo = ChromecastUtilities.createMediaInfo(contentId, customData, contentType, duration, streamType, metadata, textTrackStyle); - MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() - .setMediaInfo(mediaInfo) - .setAutoplay(autoPlay) - .setCurrentTime((long) currentTime * 1000) - .build(); + activity.runOnUiThread(() -> { + MediaInfo mediaInfo = ChromecastUtilities.createMediaInfo(contentId, customData, contentType, duration, streamType, metadata, textTrackStyle); + MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() + .setMediaInfo(mediaInfo) + .setAutoplay(autoPlay) + .setCurrentTime((long) currentTime * 1000) + .build(); - requestingMedia = true; - setQueueReloadCallback(new Runnable() { - @Override - public void run() { - callback.success(createMediaObject()); - } - }); - client.load(loadRequest).setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull MediaChannelResult result) { - requestingMedia = false; - if (!result.getStatus().isSuccess()) { - callback.errorString("session_error"); - setQueueReloadCallback(null); - } - } - }); - } + requestingMedia = true; + setQueueReloadCallback(() -> callback.success(createMediaObject())); + client.load(loadRequest).setResultCallback(result -> { + requestingMedia = false; + if (!result.getStatus().isSuccess()) { + callback.errorString("session_error"); + setQueueReloadCallback(null); + } + }); }); } /** * Media API - Calls play on the current media. + * * @param callback called with success or error */ public void mediaPlay(CallbackContext callback) { @@ -262,16 +263,13 @@ public class ChromecastSession { callback.errorString("session_error"); return; } - activity.runOnUiThread(new Runnable() { - public void run() { - client.play() - .setResultCallback(getResultCallback(callback, "Failed to play.")); - } - }); + activity.runOnUiThread(() -> client.play() + .setResultCallback(getResultCallback(callback, "Failed to play."))); } /** * Media API - Calls pause on the current media. + * * @param callback called with success or error */ public void mediaPause(CallbackContext callback) { @@ -279,52 +277,48 @@ public class ChromecastSession { callback.errorString("session_error"); return; } - activity.runOnUiThread(new Runnable() { - public void run() { - client.pause() - .setResultCallback(getResultCallback(callback, "Failed to pause.")); - } - }); + activity.runOnUiThread(() -> client.pause() + .setResultCallback(getResultCallback(callback, "Failed to pause."))); } /** * Media API - Seeks the current playing media. + * * @param seekPosition - Seconds to seek to * @param resumeState - Resume state once seeking is complete: PLAYBACK_PAUSE or PLAYBACK_START - * @param callback called with success or error + * @param callback called with success or error */ public void mediaSeek(long seekPosition, String resumeState, CallbackContext callback) { if (client == null || session == null) { callback.errorString("session_error"); return; } - activity.runOnUiThread(new Runnable() { - public void run() { - int resState; - switch (resumeState) { - case "PLAYBACK_START": - resState = MediaSeekOptions.RESUME_STATE_PLAY; - break; - case "PLAYBACK_PAUSE": - resState = MediaSeekOptions.RESUME_STATE_PAUSE; - break; - default: - resState = MediaSeekOptions.RESUME_STATE_UNCHANGED; - } - - client.seek(new MediaSeekOptions.Builder() - .setPosition(seekPosition) - .setResumeState(resState) - .build() - ).setResultCallback(getResultCallback(callback, "Failed to seek.")); + activity.runOnUiThread(() -> { + int resState; + switch (resumeState) { + case "PLAYBACK_START": + resState = MediaSeekOptions.RESUME_STATE_PLAY; + break; + case "PLAYBACK_PAUSE": + resState = MediaSeekOptions.RESUME_STATE_PAUSE; + break; + default: + resState = MediaSeekOptions.RESUME_STATE_UNCHANGED; } + + client.seek(new MediaSeekOptions.Builder() + .setPosition(seekPosition) + .setResumeState(resState) + .build() + ).setResultCallback(getResultCallback(callback, "Failed to seek.")); }); } /** * Media API - Sets the volume on the current playing media object, NOT ON THE CHROMECAST DIRECTLY. - * @param level the level to set the volume to - * @param muted if true set the media to muted, else, unmute + * + * @param level the level to set the volume to + * @param muted if true set the media to muted, else, unmute * @param callback called with success or error */ public void mediaSetVolume(Double level, Boolean muted, CallbackContext callback) { @@ -332,67 +326,68 @@ public class ChromecastSession { callback.errorString("session_error"); return; } - activity.runOnUiThread(new Runnable() { - public void run() { - // Figure out the number of callbacks we expect to receive - int calls = 0; - if (level != null) { - calls++; - } - if (muted != null) { - calls++; - } - if (calls == 0) { - // No change - callback.success(); - return; - } + activity.runOnUiThread(() -> { + // Figure out the number of callbacks we expect to receive + int calls = 0; + if (level != null) { + calls++; + } + if (muted != null) { + calls++; + } + if (calls == 0) { + // No change + callback.success(); + return; + } - // We need this callback so that we can wait for a variable number of calls to come back - final int expectedCalls = calls; - ResultCallback cb = new ResultCallback() { - private int callsCompleted = 0; - private String finalErr = null; - private void completionCall() { - callsCompleted++; - if (callsCompleted >= expectedCalls) { - // Both the setvolume an setMute have returned - if (finalErr != null) { - callback.errorString(finalErr); - } else { - callback.success(); - } + // We need this callback so that we can wait for a variable number of calls to come back + final int expectedCalls = calls; + ResultCallback cb = new ResultCallback() { + private int callsCompleted = 0; + private String finalErr = null; + + private void completionCall() { + callsCompleted++; + if (callsCompleted >= expectedCalls) { + // Both the setvolume an setMute have returned + if (finalErr != null) { + callback.errorString(finalErr); + } else { + callback.success(); } } - @Override - public void onResult(@NonNull MediaChannelResult result) { - if (!result.getStatus().isSuccess()) { - if (finalErr == null) { - finalErr = "Failed to set media volume/mute state:\n"; - } - JSONObject errorResult = result.getCustomData(); - if (errorResult != null) { - finalErr += "\n" + errorResult; - } - } - completionCall(); - } - }; + } - if (level != null) { - client.setStreamVolume(level) - .setResultCallback(cb); - } - if (muted != null) { - client.setStreamMute(muted) - .setResultCallback(cb); + @Override + public void onResult(@NonNull MediaChannelResult result) { + if (!result.getStatus().isSuccess()) { + if (finalErr == null) { + finalErr = "Failed to set media volume/mute state:\n"; + } + JSONObject errorResult = result.getCustomData(); + if (errorResult != null) { + finalErr += "\n" + errorResult; + } + } + completionCall(); } + }; + + if (level != null) { + client.setStreamVolume(level) + .setResultCallback(cb); + } + if (muted != null) { + client.setStreamMute(muted) + .setResultCallback(cb); } }); } /** * Media API - Stops and unloads the current playing media. + * * @param callback called with success or error */ public void mediaStop(CallbackContext callback) { @@ -400,36 +395,31 @@ public class ChromecastSession { callback.errorString("session_error"); return; } - activity.runOnUiThread(new Runnable() { - public void run() { - client.stop() - .setResultCallback(getResultCallback(callback, "Failed to stop.")); - } - }); + activity.runOnUiThread(() -> client.stop() + .setResultCallback(getResultCallback(callback, "Failed to stop."))); } /** * Handle track changed. + * * @param activeTracksIds active track ids - * @param textTrackStyle track style - * @param callback called with success or error + * @param textTrackStyle track style + * @param callback called with success or error */ public void mediaEditTracksInfo(long[] activeTracksIds, JSONObject textTrackStyle, CallbackContext callback) { if (client == null || session == null) { callback.errorString("session_error"); return; } - activity.runOnUiThread(new Runnable() { - public void run() { - client.setActiveMediaTracks(activeTracksIds) - .setResultCallback(getResultCallback(callback, "Failed to set active media tracks.")); - client.setTextTrackStyle(ChromecastUtilities.parseTextTrackStyle(textTrackStyle)) - .setResultCallback(getResultCallback(callback, "Failed to set text track style.")); - } + activity.runOnUiThread(() -> { + client.setActiveMediaTracks(activeTracksIds) + .setResultCallback(getResultCallback(callback, "Failed to set active media tracks.")); + client.setTextTrackStyle(ChromecastUtilities.parseTextTrackStyle(textTrackStyle)) + .setResultCallback(getResultCallback(callback, "Failed to set text track style.")); }); } -/* ------------------------------------ QUEUE FNs ------------------------------------------- */ + /* ------------------------------------ QUEUE FNs ------------------------------------------- */ private void setQueueReloadCallback(Runnable callback) { this.queueReloadCallback = callback; @@ -450,11 +440,17 @@ public class ChromecastSession { } private class MediaQueueController extends MediaQueue.Callback { - /** The MediaQueue object. **/ - private MediaQueue queue; - /** Contains the item indexes that we need before sending out an update. **/ - private ArrayList lookingForIndexes = new ArrayList(); - /** Keeps track of the queueItems. **/ + /** + * The MediaQueue object. + **/ + private final MediaQueue queue; + /** + * Contains the item indexes that we need before sending out an update. + **/ + private ArrayList lookingForIndexes = new ArrayList<>(); + /** + * Keeps track of the queueItems. + **/ private JSONArray queueItems; MediaQueueController(MediaQueue q) { @@ -484,9 +480,11 @@ public class ChromecastSession { } checkLookingForIndexes(); } + private int getCurrentItemIndex() { return queue.indexOfItemWithId(client.getMediaStatus().getCurrentItemId()); } + /** * Works to get all items listed in lookingForIndexes. * After all have been found, send out an update. @@ -513,6 +511,7 @@ public class ChromecastSession { updateFinished(); } } + private void updateFinished() { // Update the queueItems ChromecastUtilities.setQueueItems(queueItems); @@ -531,17 +530,15 @@ public class ChromecastSession { return; } if (queueReloadCallback == null) { - setQueueReloadCallback(new Runnable() { - @Override - public void run() { - // This was externally loaded - clientListener.onMediaLoaded(createMediaObject()); - } + setQueueReloadCallback(() -> { + // This was externally loaded + clientListener.onMediaLoaded(createMediaObject()); }); } refreshQueueItems(); } } + @Override public void itemsUpdatedAtIndexes(int[] ints) { synchronized (queue) { @@ -561,68 +558,67 @@ public class ChromecastSession { checkLookingForIndexes(); } } + @Override public void itemsInsertedInRange(int startIndex, int insertCount) { synchronized (queue) { refreshQueueItems(); } } + @Override public void itemsRemovedAtIndexes(int[] ints) { synchronized (queue) { refreshQueueItems(); } } - }; + } /** * Loads a queue of media to the Chromecast. + * * @param queueLoadRequest chrome.cast.media.QueueLoadRequest - * @param callback called with success or error + * @param callback called with success or error */ public void queueLoad(JSONObject queueLoadRequest, CallbackContext callback) { if (client == null || session == null) { callback.errorString("session_error"); return; } - activity.runOnUiThread(new Runnable() { - public void run() { - try { - JSONArray qItems = queueLoadRequest.getJSONArray("items"); - MediaQueueItem[] items = new MediaQueueItem[qItems.length()]; - for (int i = 0; i < qItems.length(); i++) { - items[i] = ChromecastUtilities.createMediaQueueItem(qItems.getJSONObject(i)); - } - - int startIndex = queueLoadRequest.getInt("startIndex"); - int repeatMode = ChromecastUtilities.getAndroidRepeatMode(queueLoadRequest.getString("repeatMode")); - long playPosition = Double.valueOf(items[startIndex].getStartTime() * 1000).longValue(); - JSONObject customData = null; - try { - customData = queueLoadRequest.getJSONObject("customData"); - } catch (JSONException e) { - } - - setQueueReloadCallback(() -> callback.success(createMediaObject())); - client.queueLoad(items, startIndex, repeatMode, playPosition, customData).setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull MediaChannelResult result) { - if (!result.getStatus().isSuccess()) { - callback.errorString("session_error"); - setQueueReloadCallback(null); - } - } - }); - } catch (JSONException e) { - callback.error(ChromecastUtilities.createError("invalid_parameter", e.getMessage())); + activity.runOnUiThread(() -> { + try { + JSONArray qItems = queueLoadRequest.getJSONArray("items"); + MediaQueueItem[] items = new MediaQueueItem[qItems.length()]; + for (int i = 0; i < qItems.length(); i++) { + items[i] = ChromecastUtilities.createMediaQueueItem(qItems.getJSONObject(i)); } + + int startIndex = queueLoadRequest.getInt("startIndex"); + int repeatMode = ChromecastUtilities.getAndroidRepeatMode(queueLoadRequest.getString("repeatMode")); + long playPosition = Double.valueOf(items[startIndex].getStartTime() * 1000).longValue(); + JSONObject customData = null; + try { + customData = queueLoadRequest.getJSONObject("customData"); + } catch (JSONException ignored) { + } + + setQueueReloadCallback(() -> callback.success(createMediaObject())); + client.queueLoad(items, startIndex, repeatMode, playPosition, customData).setResultCallback(result -> { + if (!result.getStatus().isSuccess()) { + callback.errorString("session_error"); + setQueueReloadCallback(null); + } + }); + } catch (JSONException e) { + callback.error(ChromecastUtilities.createError("invalid_parameter", e.getMessage())); } }); } /** * Plays the item with itemId in the queue. - * @param itemId The ID of the item to jump to. + * + * @param itemId The ID of the item to jump to. * @param callback called with .success or .error depending on the result */ public void queueJumpToItem(Integer itemId, CallbackContext callback) { @@ -631,41 +627,30 @@ public class ChromecastSession { return; } - activity.runOnUiThread(new Runnable() { - public void run() { - setQueueStatusUpdatedCallback(new Runnable() { - @Override - public void run() { - clientListener.onMediaUpdate(createMediaObject(MediaStatus.IDLE_REASON_INTERRUPTED)); + activity.runOnUiThread(() -> { + setQueueStatusUpdatedCallback(() -> clientListener.onMediaUpdate(createMediaObject(MediaStatus.IDLE_REASON_INTERRUPTED))); + client.queueJumpToItem(itemId, null).setResultCallback(result -> { + if (result.getStatus().isSuccess()) { + callback.success(); + } else { + setQueueStatusUpdatedCallback(null); + JSONObject errorResult = result.getCustomData(); + String error = "Failed to jump to queue item with ID: " + itemId; + if (errorResult != null) { + error += "\nError details: " + errorResult; } - }); - client.queueJumpToItem(itemId, null) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull MediaChannelResult result) { - - if (result.getStatus().isSuccess()) { - callback.success(); - } else { - setQueueStatusUpdatedCallback(null); - JSONObject errorResult = result.getCustomData(); - String error = "Failed to jump to queue item with ID: " + itemId; - if (errorResult != null) { - error += "\nError details: " + errorResult; - } - callback.errorString(error); - } - } - }); - } + callback.errorString(error); + } + }); }); } -/* ------------------------------------ SESSION FNs ------------------------------------------- */ + /* ------------------------------------ SESSION FNs ------------------------------------------- */ /** * Sets the receiver volume level. - * @param volume volume to set the receiver to + * + * @param volume volume to set the receiver to * @param callback called with success or error */ public void setVolume(double volume, CallbackContext callback) { @@ -673,21 +658,20 @@ public class ChromecastSession { callback.errorString("session_error"); return; } - activity.runOnUiThread(new Runnable() { - public void run() { - try { - session.setVolume(volume); - callback.success(); - } catch (IOException e) { - callback.errorString("CHANNEL_ERROR"); - } + activity.runOnUiThread(() -> { + try { + session.setVolume(volume); + callback.success(); + } catch (IOException e) { + callback.errorString("CHANNEL_ERROR"); } }); } /** * Mutes the receiver. - * @param muted if true mute, else, unmute + * + * @param muted if true mute, else, unmute * @param callback called with success or error */ public void setMute(boolean muted, CallbackContext callback) { @@ -695,22 +679,21 @@ public class ChromecastSession { callback.errorString("session_error"); return; } - activity.runOnUiThread(new Runnable() { - public void run() { - try { - session.setMute(muted); - callback.success(); - } catch (IOException e) { - callback.errorString("CHANNEL_ERROR"); - } + activity.runOnUiThread(() -> { + try { + session.setMute(muted); + callback.success(); + } catch (IOException e) { + callback.errorString("CHANNEL_ERROR"); } }); } -/* ------------------------------------ HELPERS ---------------------------------------------- */ + /* ------------------------------------ HELPERS ---------------------------------------------- */ /** * Returns a resultCallback that wraps the callback and calls the onMediaUpdate listener. + * * @param callback client callback * @param errorMsg error message if failure * @return a callback for use in PendingResult.setResultCallback() @@ -734,8 +717,11 @@ public class ChromecastSession { return ChromecastUtilities.createSessionObject(session); } - /** Last sent media object. **/ + /** + * Last sent media object. + **/ private JSONObject lastMediaObject; + private JSONObject createMediaObject() { return createMediaObject(null); } @@ -746,7 +732,7 @@ public class ChromecastSession { lastMediaObject.put("playerState", ChromecastUtilities.getMediaPlayerState(MediaStatus.PLAYER_STATE_IDLE)); lastMediaObject.put("idleReason", ChromecastUtilities.getMediaIdleReason(idleReason)); return lastMediaObject; - } catch (JSONException e) { + } catch (JSONException ignored) { } } JSONObject out = ChromecastUtilities.createMediaObject(session); @@ -756,8 +742,11 @@ public class ChromecastSession { interface Listener extends Cast.MessageReceivedCallback { void onMediaLoaded(JSONObject jsonMedia); + void onMediaUpdate(JSONObject jsonMedia); + void onSessionUpdate(JSONObject jsonSession); + void onSessionEnd(JSONObject jsonSession); } } diff --git a/app/src/main/java/org/jellyfin/android/cast/ChromecastUtilities.java b/app/src/main/java/org/jellyfin/android/cast/ChromecastUtilities.java index 3808264e..d35253de 100644 --- a/app/src/main/java/org/jellyfin/android/cast/ChromecastUtilities.java +++ b/app/src/main/java/org/jellyfin/android/cast/ChromecastUtilities.java @@ -1,5 +1,6 @@ package org.jellyfin.android.cast; +import android.annotation.SuppressLint; import android.graphics.Color; import android.net.Uri; @@ -120,7 +121,6 @@ final class ChromecastUtilities { case MediaTrack.SUBTYPE_SUBTITLES: return "SUBTITLES"; case MediaTrack.SUBTYPE_NONE: - return null; default: return null; } @@ -152,10 +152,9 @@ final class ChromecastUtilities { return "MONOSPACED_SERIF"; case TextTrackStyle.FONT_FAMILY_SANS_SERIF: return "SANS_SERIF"; - case TextTrackStyle.FONT_FAMILY_SERIF: - return "SERIF"; case TextTrackStyle.FONT_FAMILY_SMALL_CAPITALS: return "SMALL_CAPITALS"; + case TextTrackStyle.FONT_FAMILY_SERIF: default: return "SERIF"; } @@ -163,8 +162,6 @@ final class ChromecastUtilities { static String getFontStyle(TextTrackStyle textTrackStyle) { switch (textTrackStyle.getFontStyle()) { - case TextTrackStyle.FONT_STYLE_NORMAL: - return "NORMAL"; case TextTrackStyle.FONT_STYLE_BOLD: return "BOLD"; case TextTrackStyle.FONT_STYLE_BOLD_ITALIC: @@ -172,6 +169,7 @@ final class ChromecastUtilities { case TextTrackStyle.FONT_STYLE_ITALIC: return "ITALIC"; case TextTrackStyle.FONT_STYLE_UNSPECIFIED: + case TextTrackStyle.FONT_STYLE_NORMAL: default: return "NORMAL"; } @@ -406,9 +404,8 @@ final class ChromecastUtilities { if (!textTrackSytle.isNull("foregroundColor")) { out.setForegroundColor(Color.parseColor(textTrackSytle.getString("foregroundColor"))); } - } catch (JSONException e) { + } catch (JSONException ignored) { } - return out; } @@ -421,7 +418,7 @@ final class ChromecastUtilities { if (state != null) { try { s.put("status", state); - } catch (JSONException e) { + } catch (JSONException ignored) { } } return s; @@ -429,24 +426,18 @@ final class ChromecastUtilities { static JSONObject createSessionObject(CastSession session) { JSONObject out = new JSONObject(); - try { ApplicationMetadata metadata = session.getApplicationMetadata(); - out.put("appId", metadata.getApplicationId()); - try { + if (metadata != null) { + out.put("appId", metadata.getApplicationId()); out.put("appImages", createImagesArray(metadata.getImages())); - } catch (NullPointerException e) { + out.put("displayName", metadata.getName()); + out.put("media", createMediaArray(session)); + out.put("receiver", createReceiverObject(session)); + out.put("sessionId", session.getSessionId()); } - out.put("displayName", metadata.getName()); - out.put("media", createMediaArray(session)); - out.put("receiver", createReceiverObject(session)); - out.put("sessionId", session.getSessionId()); - - } catch (JSONException e) { - } catch (NullPointerException e) { - } catch (IllegalStateException e) { + } catch (JSONException | NullPointerException | IllegalStateException ignored) { } - return out; } @@ -468,15 +459,10 @@ final class ChromecastUtilities { out.put("label", session.getCastDevice().getDeviceId()); JSONObject volume = new JSONObject(); - try { - volume.put("level", session.getVolume()); - volume.put("muted", session.isMute()); - } catch (JSONException e) { - } + volume.put("level", session.getVolume()); + volume.put("muted", session.isMute()); out.put("volume", volume); - - } catch (JSONException e) { - } catch (NullPointerException e) { + } catch (JSONException | NullPointerException ignored) { } return out; } @@ -494,8 +480,6 @@ final class ChromecastUtilities { return createMediaObject(session, queueItems); } - ; - static JSONObject createMediaObject(CastSession session, JSONArray items) { JSONObject out = new JSONObject(); @@ -533,7 +517,7 @@ final class ChromecastUtilities { volume.put("muted", mediaStatus.isMute()); out.put("volume", volume); out.put("activeTrackIds", createActiveTrackIds(mediaStatus.getActiveTrackIds())); - } catch (JSONException e) { + } catch (JSONException ignored) { } catch (NullPointerException e) { return null; } @@ -626,8 +610,7 @@ final class ChromecastUtilities { out.put(jsonTrack); } - } catch (JSONException e) { - } catch (NullPointerException e) { + } catch (JSONException | NullPointerException ignored) { } return out; @@ -651,8 +634,7 @@ final class ChromecastUtilities { out.put("tracks", createMediaInfoTracks(mediaInfo)); out.put("textTrackStyle", ChromecastUtilities.createTextTrackObject(mediaInfo.getTextTrackStyle())); - } catch (JSONException e) { - } catch (NullPointerException e) { + } catch (JSONException | NullPointerException ignored) { } return out; @@ -667,7 +649,7 @@ final class ChromecastUtilities { try { // Must be in own try catch out.put("images", createImagesArray(metadata.getImages())); - } catch (Exception e) { + } catch (Exception ignored) { } out.put("metadataType", metadata.getMediaType()); out.put("type", metadata.getMediaType()); @@ -735,9 +717,8 @@ final class ChromecastUtilities { out.put("windowColor", getHexColor(textTrackStyle.getWindowColor())); out.put("windowRoundedCornerRadius", textTrackStyle.getWindowCornerRadius()); out.put("windowType", getWindowType(textTrackStyle)); - } catch (JSONException e) { + } catch (JSONException ignored) { } - return out; } @@ -747,6 +728,7 @@ final class ChromecastUtilities { * @param routes the routes to convert * @return a JSON Array of JSON representations of the routes */ + @SuppressLint("RestrictedApi") static JSONArray createRoutesArray(List routes) { JSONArray routesArray = new JSONArray(); for (MediaRouter.RouteInfo route : routes) { @@ -762,8 +744,7 @@ final class ChromecastUtilities { } routesArray.put(obj); - } catch (JSONException e) { - // ignore + } catch (JSONException ignored) { } } return routesArray; @@ -774,7 +755,7 @@ final class ChromecastUtilities { try { out.put("code", code); out.put("description", message); - } catch (JSONException e) { + } catch (JSONException ignored) { } return out; } @@ -800,72 +781,38 @@ final class ChromecastUtilities { activeTrackIds[i] = trackIds.getLong(i); } builder.setActiveTrackIds(activeTrackIds); - } catch (JSONException e) { + } catch (JSONException ignored) { } try { builder.setAutoplay(mediaQueueItem.getBoolean("autoplay")); - } catch (JSONException e) { - } - JSONObject customData = new JSONObject(); - try { - customData.getJSONObject("customData"); - } catch (JSONException e) { + } catch (JSONException ignored) { } try { builder.setPlaybackDuration(mediaQueueItem.getDouble("playbackDuration")); - } catch (JSONException e) { + } catch (JSONException ignored) { } try { builder.setPreloadTime(mediaQueueItem.getDouble("preloadTime")); - } catch (JSONException e) { + } catch (JSONException ignored) { } try { builder.setStartTime(mediaQueueItem.getDouble("startTime")); - } catch (JSONException e) { + } catch (JSONException ignored) { } return builder.build(); } static MediaInfo createMediaInfo(JSONObject mediaInfo) { - // Set defaults - String contentId = ""; - JSONObject customData = new JSONObject(); - String contentType = "unknown"; - long duration = 0; - String streamType = "unknown"; - JSONObject metadata = new JSONObject(); - JSONObject textTrackStyle = new JSONObject(); - - // Try to get the actual values - try { - contentId = mediaInfo.getString("contentId"); - } catch (JSONException e) { - } - try { - customData = mediaInfo.getJSONObject("customData"); - } catch (JSONException e) { - } - try { - contentType = mediaInfo.getString("contentType"); - } catch (JSONException e) { - } - try { - duration = mediaInfo.getLong("duration"); - } catch (JSONException e) { - } - try { - streamType = mediaInfo.getString("streamType"); - } catch (JSONException e) { - } - try { - metadata = mediaInfo.getJSONObject("metadata"); - } catch (JSONException e) { - } - try { - textTrackStyle = mediaInfo.getJSONObject("textTrackStyle"); - } catch (JSONException e) { - } - + String contentId = mediaInfo.optString("contentId", ""); + JSONObject customData = mediaInfo.optJSONObject("customData"); + if (customData == null) customData = new JSONObject(); + String contentType = mediaInfo.optString("contentType", "unknown"); + long duration = mediaInfo.optLong("duration"); + String streamType = mediaInfo.optString("streamType", "unknown"); + JSONObject metadata = mediaInfo.optJSONObject("metadata"); + if (metadata == null) metadata = new JSONObject(); + JSONObject textTrackStyle = mediaInfo.optJSONObject("textTrackStyle"); + if (textTrackStyle == null) textTrackStyle = new JSONObject(); return createMediaInfo(contentId, customData, contentType, duration, streamType, metadata, textTrackStyle); } @@ -914,10 +861,10 @@ final class ChromecastUtilities { try { Uri imageURI = Uri.parse(imageObj.getString("url")); mediaMetadata.addImage(new WebImage(imageURI)); - } catch (Exception e) { + } catch (Exception ignored) { } } - } catch (JSONException e) { + } catch (JSONException ignored) { } // Dynamically add other parameters @@ -982,13 +929,10 @@ final class ChromecastUtilities { convertedKey = "cordova-plugin-chromecast_metadata_key=" + key; } mediaMetadata.putString(convertedKey, metadata.getString(key)); - } catch (JSONException e) { - e.printStackTrace(); - } catch (IllegalArgumentException e) { + } catch (JSONException | IllegalArgumentException e) { e.printStackTrace(); } } return mediaMetadata; } - -} +} \ No newline at end of file