Cleanup and refactor some cast-related code

This commit is contained in:
Maxr1998
2020-07-25 22:44:50 +02:00
parent 6c1968e9e9
commit f14e512d11
5 changed files with 564 additions and 670 deletions
@@ -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<SessionProvider> getAdditionalSessionProviders(Context context) {
return null;
}
}
@@ -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<SessionProvider>? {
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
}
}
}
@@ -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<RouteInfo> 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<RouteInfo> 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<JSONObject, Void> sendErrorResult = new Function<JSONObject, Void>() {
@Override
public Void apply(JSONObject message) {
if (!sentResult[0]) {
sentResult[0] = true;
stopRouteScan(scan, null);
callback.onError(message);
}
return null;
}
};
Function<JSONObject, Void> 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<CastSession> {
private static class SessionListener implements SessionManagerListener<CastSession> {
@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);
}
}
}
}
@@ -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<Status>() {
@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<MediaChannelResult>() {
@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<MediaChannelResult> cb = new ResultCallback<MediaChannelResult>() {
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<MediaChannelResult> cb = new ResultCallback<MediaChannelResult>() {
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<Integer> lookingForIndexes = new ArrayList<Integer>();
/** 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<Integer> 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<MediaChannelResult>() {
@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<MediaChannelResult>() {
@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);
}
}
@@ -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<MediaRouter.RouteInfo> 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;
}
}
}