mirror of
https://github.com/jellyfin/jellyfin-android.git
synced 2026-09-03 05:10:27 +03:00
Cleanup and refactor some cast-related code
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user