Skip to content

Commit

Permalink
fix: remove sharedPreferences and NSUserDefaults for storing the app id
Browse files Browse the repository at this point in the history
* Remove sharedPreferences and NSUserDefaults for storing the app id

* Update readme and add warning for appId in JS API
  • Loading branch information
ChristiaanScheermeijer authored Jun 18, 2024
1 parent 6f484d1 commit eff2480
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 128 deletions.
97 changes: 95 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,37 @@

# Installation

> Use a commit hash to prevent dependency hijacking
To install this fork, you can use the full GitHub URL with commit hash. Always try using a commit hash to prevent
installing malicious code.

```
cordova plugin add https://github.com/Videodock/cordova-plugin-chromecast.git#d82f07e26a53df6322043403e4dc98bb2eeb4e39
```

If you have trouble installing the plugin or running the project for iOS, from `/platforms/ios/` try running:
## Android

Add the following metadata to your AndroidManifest.xml inside the `<application>` tag.

Replace `_APP_ID_` with your receiver application id.

```xml
<manifest>
<application>
<activity>
...
</activity>
<meta-data
android:name="com.google.android.gms.cast.framework.RECEIVER_APPLICATION_ID"
android:value="_APP_ID_" />
</application>
</manifest>
```


## iOS

If you have trouble installing the plugin or running the project for iOS, from `/platforms/ios/` try running:

```bash
sudo gem install cocoapods
pod repo update
Expand Down Expand Up @@ -50,6 +74,66 @@ The "*Description" key strings will be used when asking the user for permission
</platform>
```

1. In AppDelegate.m (or AppDelegate.swift) add

```
#import <GoogleCast/GoogleCast.h>
```

```swift
import GoogleCast
```

and insert the following in the `application:didFinishLaunchingWithOptions` method, ideally at the beginning:

```
NSString *receiverAppID = kGCKDefaultMediaReceiverApplicationID; // or @"ABCD1234"
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc] initWithApplicationID:receiverAppID];
GCKCastOptions* options = [[GCKCastOptions alloc] initWithDiscoveryCriteria:criteria];
[GCKCastContext setSharedInstanceWithOptions:options];
```

```swift
let receiverAppID = kGCKDefaultMediaReceiverApplicationID // or "ABCD1234"
let criteria = GCKDiscoveryCriteria(applicationID: receiverAppID)
let options = GCKCastOptions(discoveryCriteria: criteria)
GCKCastContext.setSharedInstanceWith(options)
```

If using a custom receiver, replace kGCKDefaultMediaReceiverApplicationID with your receiver app id.

You can also automatically set the receiver ID by parsing the Bonjour Services string. This is useful when you have
multiple Info.plist files for different environments.

```swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
initializeCastSender()
return true
}

func initializeCastSender() {
if let value = Bundle.init(for: AppDelegate.self).infoDictionary?["NSBonjourServices"] as? [String] {

if let rule = value.first(where: { $0.hasSuffix("._googlecast._tcp") }) {
let startIndex = rule.index(rule.startIndex, offsetBy: 1)
let endIndex = rule.index(rule.startIndex, offsetBy: 9)
let receiverAppID = String(rule[startIndex..<endIndex]);

let criteria = GCKDiscoveryCriteria(applicationID: receiverAppID)
let options = GCKCastOptions(discoveryCriteria: criteria)

GCKCastContext.setSharedInstanceWith(options)

print("Initialized Cast SDK with appId: \(receiverAppID)")
} else {
print("Couldn't initialize Cast SDK, NSBonjourServices is defined, but couldn't find _<appId>._googlecast._tcp in the Array");
}
} else {
print("Couldn't initialize Cast SDK, NSBonjourServices is missing from the Info.plist");
}
}
```

## Chromecast Icon Assets
[chromecast-assets.zip](https://github.com/jellyfin/cordova-plugin-chromecast/wiki/chromecast-assets.zip)

Expand Down Expand Up @@ -86,6 +170,15 @@ document.addEventListener("deviceready", function () {
});
```

The `SessionRequest#appId` property is not used to define the receiver app and can be set to an empty string to disable the warning.
Reason for this is that the Cast SDK can be initialized on app startup. Also, for iOS, the Info.plist must be configured with your receiver ID. This makes it easy to mis align the application ID in the initialization and native config.
```js
var sessionRequest = new chrome.cast.SessionRequest('A11B22C'); // doesn't use A11B22C (triggers console warning)
var sessionRequest = new chrome.cast.SessionRequest(''); // disables console warning

var apiConfig = new chrome.cast.ApiConfig(sessionRequest);
```


### Example Usage
Here is a simple [example](doc/example.js) that loads a video, pauses it, and ends the session.
Expand Down
1 change: 1 addition & 0 deletions plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<config-file target="AndroidManifest.xml" parent="/manifest/application">
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" android:value="acidhax.cordova.chromecast.CastOptionsProvider" />
<meta-data android:name="com.google.android.gms.cast.framework.RECEIVER_APPLICATION_ID" android:value="CC1AD845" />
</config-file>

<framework src="com.google.android.gms:play-services-cast-framework:21.4.0" />
Expand Down
22 changes: 13 additions & 9 deletions src/android/CastOptionsProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,35 @@

import java.util.List;

import com.google.android.gms.cast.CastMediaControlIntent;
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;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;

public final class CastOptionsProvider implements OptionsProvider {

/** The app id. */
private static String appId;
protected String getReceiverApplicationId(Context context) {
String appId = null;
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_META_DATA);
appId = packageInfo.applicationInfo.metaData.getString("com.google.android.gms.cast.framework.RECEIVER_APPLICATION_ID");
} catch (PackageManager.NameNotFoundException e) {
}

/**
* Sets the app ID.
* @param applicationId appId
*/
public static void setAppId(String applicationId) {
appId = applicationId;
return appId != null ? appId : CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID;
}

@Override
public CastOptions getCastOptions(Context context) {
return new CastOptions.Builder()
.setReceiverApplicationId(appId)
.setReceiverApplicationId(getReceiverApplicationId(context))
.build();
}

@Override
public List<SessionProvider> getAdditionalSessionProviders(Context context) {
return null;
Expand Down
5 changes: 2 additions & 3 deletions src/android/Chromecast.java
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,13 @@ public void run() {
/**
* Initialize all of the MediaRouter stuff with the AppId.
* For now, ignore the autoJoinPolicy and defaultActionPolicy; those will come later
* @param appId The appId we're going to use for ALL session requests
* @param autoJoinPolicy tab_and_origin_scoped | origin_scoped | page_scoped
* @param defaultActionPolicy create_session | cast_this_tab
* @param callbackContext called with .success or .error depending on the result
* @return true for cordova
*/
public boolean initialize(final String appId, String autoJoinPolicy, String defaultActionPolicy, final CallbackContext callbackContext) {
connection.initialize(appId, callbackContext);
public boolean initialize(String autoJoinPolicy, String defaultActionPolicy, final CallbackContext callbackContext) {
connection.initialize(callbackContext);
return true;
}

Expand Down
64 changes: 7 additions & 57 deletions src/android/ChromecastConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

import androidx.arch.core.util.Function;
import androidx.mediarouter.app.MediaRouteChooserDialog;
import androidx.mediarouter.media.MediaControlIntent;
import androidx.mediarouter.media.MediaRouteSelector;
import androidx.mediarouter.media.MediaRouter;
import androidx.mediarouter.media.MediaRouter.RouteInfo;
Expand Down Expand Up @@ -46,24 +48,16 @@ public class ChromecastConnection {
/** The Listener callback. */
private Listener listener;

/** Initialize lifetime variable. */
private String appId;

/**
* Constructor.
* @param act the current context
* @param connectionListener client callbacks for specific events
*/
ChromecastConnection(Activity act, Listener connectionListener) {
this.activity = act;
this.settings = activity.getSharedPreferences("CORDOVA-PLUGIN-CHROMECAST_ChromecastConnection", 0);
this.appId = settings.getString("appId", CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID);
this.listener = connectionListener;
this.chromecastSession = new ChromecastSession(activity, listener);

// Set the initial appId
CastOptionsProvider.setAppId(appId);

// This is the first call to getContext which will start up the
// CastContext and prep it for searching for a session to rejoin
// Also adds the receiver update callback
Expand Down Expand Up @@ -93,25 +87,11 @@ ChromecastSession getChromecastSession() {

/**
* Must be called each time the appId changes and at least once before any other method is called.
* @param applicationId the app id to use
* @param callback called when initialization is complete
*/
public void initialize(String applicationId, CallbackContext callback) {
public void initialize(CallbackContext callback) {
activity.runOnUiThread(new Runnable() {
public void run() {
// If the app Id changed
if (applicationId == null || !applicationId.equals(appId)) {
// If app Id is valid
if (isValidAppId(applicationId)) {
// Set the new app Id
setAppId(applicationId);
} else {
// Else, just return
callback.success();
return;
}
}

// Tell the client that initialization was a success
callback.success();

Expand Down Expand Up @@ -157,38 +137,6 @@ private CastSession getSession() {
return getSessionManager().getCurrentCastSession();
}

private void setAppId(String applicationId) {
this.appId = applicationId;
this.settings.edit().putString("appId", appId).apply();
getContext().setReceiverApplicationId(appId);
}

/**
* Tests if an application receiver id is valid.
* @param applicationId - application receiver id
* @return true if valid
*/
private boolean isValidAppId(String applicationId) {
try {
ScanCallback cb = new ScanCallback() {
@Override
void onRouteUpdate(List<RouteInfo> routes) { }
};
// This will throw if the applicationId is invalid
getMediaRouter().addCallback(new MediaRouteSelector.Builder()
.addControlCategory(CastMediaControlIntent.categoryForCast(applicationId))
.build(),
cb,
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
// If no exception we passed, so remove the callback
getMediaRouter().removeCallback(cb);
return true;
} catch (IllegalArgumentException e) {
// Don't set the appId if it is not a valid receiverApplicationID
return false;
}
}

/**
* This will create a new session or seamlessly selectRoute an existing one if we created it.
* @param routeId the id of the route to selectRoute
Expand Down Expand Up @@ -343,7 +291,8 @@ public void run() {
// 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))
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.build());
builder.setCanceledOnTouchOutside(true);
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
Expand Down Expand Up @@ -431,7 +380,8 @@ public void run() {

// Add the callback in active scan mode
getMediaRouter().addCallback(new MediaRouteSelector.Builder()
.addControlCategory(CastMediaControlIntent.categoryForCast(appId))
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.build(),
callback,
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
Expand Down
Loading

0 comments on commit eff2480

Please sign in to comment.