Giter Site home page Giter Site logo

cordova-plugin-chromecast's People

Contributors

anthonylavado avatar codehole7 avatar dkanada avatar emilsas avatar froghut avatar grahamkennery avatar joshuaboniface avatar judeosborn avatar justaman avatar kibotu avatar lewazo avatar lindsay-needs-sleep avatar matbee-eth avatar mmocny avatar pgcan avatar rejhgadellaa avatar sel-en-ium avatar vaporexpress avatar vitorsemeano avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cordova-plugin-chromecast's Issues

1.0.0 refactor

The largest changes of this refactor are:

  • iOS, Android, and Chrome Desktop all work the same now! (With a few caveats.)
    All 3 will pass the same set of tests included in the plugin.

  • iOS and Android have scanForRoutes, stopScan, and selectRoute to replace getRouteListElement

  • iOS and Android still do not implement the full chromecast API, but a few more have been added, and existing ones have been made to match chrome desktop behavior.

Bug Fixes:

  • Fixes #17 (cancel should return error)
  • Fixes #22 for Android (getRouteListElement() returns empty object) non-issue now
  • Fixes #22 for iOS (getRouteListElement() returns empty object) non-issue now
  • Fixes #23 Metadata not set
  • Fixes #26 #27 (compile/dependancy error)
  • Fixes #32 (duplicate routes are displayed)
  • Fixes #36 (Because that is this issue :p)
  • Fixes #43 (crash on requestSession)
  • Fixes #41 remove deprecated sendJavascript usage
  • Fixes #47 iOS crash
  • Fixes #48 selectRoute crash
  • Fixes #49 selectRoute silent fail
  • Fixes #50 Switch to Mocha for tests
  • Play audio files miloproductionsinc#3

Improvements:

  • Upgraded to CAF v3, this got rid of a huge amount of deprecation notices.
  • Added eslint tests to ensure uniform js code stlying
  • Added a style checker for java
  • Updated documentation on: how to run style checkers, how to run tests.
  • Added a pull request template which instructions prospective pull requesters to run eslint and the tests
  • Repaired and added to the existing tests enforcing chrome desktop SDK behavior.
  • On app initialize you will receive any previously started+active session. (This includes navigating to a new page, or force killing the app and re-loading.)
  • Implemented startRouteScan and stopRouteScan to replace getRoutes (see why)
  • fixed selectRoute for Android 4.4.2
  • Allow native error responses to send a json object "code" and "description" parameters in addition to just the error code string.
  • Implement basic queue functionality
  • Tests can run on chrome desktop browser as well (with a couple stubs to enable missing functionality)
    • This is super useful because you can write tests and check that they work correctly on chrome first. Which is very important when chrome's implementation is what we are implementing.
  • Added a basic code example
  • Updated readme with all kinds of instructions (differences between chrome, api list, plugin-specific api list, api usage example, support+quirks, setup, how to test, how to run formatting test)
  • Added pull request template
  • Added scripts to assist in enforcing code formatting for JS and Java

Auto-tests:

  • initialize
    • receiverListener
    • sessionListener
  • leaveSession
  • stopSession
  • loadMedia Video
  • media.setVolume
  • media.play
  • media.pause
  • media.stop
  • session.setReceiverVolumeLevel
  • session.setReceiverMuted
  • requestSession
  • cordova.startRouteScan
  • cordova.stopRouteScan
  • cordova.selectRoute
  • MediaMetadata (All types with most of their special params)
  • loadMedia Audio (Did not test playback controls)
  • loadMedia Image
  • load queue
  • jump to queue item

Manual Tests:

  • Start session, reload page, call initialize, do we get session again?
  • Start session, force kill app, restart app, do we get session again?
  • Start session, join session with additional device, leave session on original, session still continues?, force kill and restart original device, original device should not rejoin the session automatically.
  • for session Leave: probably need to join the session from an additional device,then leave the session, restart app, does it rejoin the session? (it shouldn't) (pre-req rejoin on restart must work)
  • for loadmedia from external sender: start session on device, join session on other device, loadmedia from other device, is session.addMediaListener triggered?
  • for external exit: Start session, kill session with other device, requestSession should show the the join dialog, not stopCasting

Support for iframe

Hi there

Is there a way getting this to work in an iframe?

Regards
Flemming

Problem with seek and push notification

Hi,
everytime i seek using the seek function, everything works as expected except from the push notification: it restarts from 00.00 everytime you seek.
Is that a known issue?
Thanks

Listen for the X icon action from the cast notification

Hi again! I'm almost done!
Here is the thing...I'm able to successfully listen to the event from the STOP CASTING button using:

this._session.addUpdateListener(function listenerSession() {
   if (this._session.status === window.chrome.cast.SessionStatus.STOPPED) {  /* here *\ }

photo5789456323069457380

But I'm not able when I touch the X in the cast notification. As far as I understand that stop the session too.

photo5789456323069457381

Thanks !!

Duplicate devices in Choose a Chromecast dialog

If you have a bunch of Google Home devices on your network and have them in a Group in the Google Home app, you'll see those devices duplicated in the Choose a Chromecast dialog in the Jellyfin Android app.

As you can see from my screenshot below, I have three Google Home speakers

  • Salon
  • Chambre
  • Cuisine

And two Chromecasts

  • Télé salon
  • Télé chambre

Now, Salon and Chambre are members of a group called Maison. So when you select Maison, music is played in sync on both speakers. So that's the expected behaviour.

Now, as you may have noticed, both speakers appears two times. This is because they are part of a group. If I remove one from the Maison group, it will only show once.

I made some changes and will have a PR for this soon.

Screenshot_20190901-195348

Error phonegap build GoogleCast/GoogleCast.h

The phonegap builder (for iOS) exits on the following error:

In file included from /project/xxx/Plugins/cordova-plugin-chromecast/MLPChromecast.m:5:
/project/xxx/Plugins/cordova-plugin-chromecast/MLPChromecast.h:6:9: fatal error: 'GoogleCast/GoogleCast.h' file not found
#import <GoogleCast/GoogleCast.h>

I import the plugin using:
<plugin source="git" spec="https://github.com/jellyfin/cordova-plugin-chromecast.git" />
in the config.xml

do you have an example of how to work with Media.seek?

the following functions that comes in the example as
_media.pause ({}, function () {
});
,

_media.play({}, function () {
});

and

_media.stop({}, function () {
});

works properly, but could not get the

_media.seek(

Detect active session when re opening app + cast notification

This is isn't much of an issue, but it's a small bug I haven't been able to fix.
I'm building a feature to stream youtube video to a custom receiver. I've successfully managed to cast youtube videos to my custom receiver. However, whenever I start a cast session and start streaming the video to a chromecast and close my app to do something else in my phone, the cast session is still active (which is the desired behaviour), but whenever I re-open my app I can't determine if a session is active already before initializing the cast SDK.

I'm using Angular + Ionic. Here is my code, (Its not very elegant, but does the job haha)

// cast.service.ts

@Injectable({
  providedIn: 'root',
})
export class CastService {
  videoId: string;
  startSeconds: number;
  receiverId = 'my-receiver';
  namespace = 'urn:x-cast:com.custom-namespace';
  castEnabled = false;
  isCasting = false;
  castSession: any;

  constructor() {}

  setParams({ videoId, startSeconds }: { videoId: string; startSeconds: number }) {
    this.videoId = videoId;
    this.startSeconds = startSeconds;
  }

  getCast() {
    return chrome.cast;
  }

  initCast() {
    const apiConfig = new chrome.cast.ApiConfig(
      new chrome.cast.SessionRequest(this.receiverId),
      (session: any) => {
        console.log(session);
        this.castSession = session;
      },
      function (receiverAvailable: any) {
        console.log(receiverAvailable);
      }
    );

    chrome.cast.initialize(
      apiConfig,
      () => {
        this.castEnabled = true;
        alert(JSON.stringify(this.castSession, null, 2));
      },
      (err: any) => {
        alert(JSON.stringify(err, null, 2));
      }
    );
  }

  requestSession(callback: Function) {
    // TODO Refactor callback into a cast status observer to subscribe on player
    chrome.cast.requestSession(
      (session: any) => {
        this.castSession = session;
        this.loadYoutubeVideo();
        callback();
        // alert(JSON.stringify(this.castSession));
      },
      (err: any) => {
        this.isCasting = false;
        this.castSession = null;
        callback();

        // if (err.code === 'cancel') return;
        // alert(JSON.stringify(err, null, 2));
      }
    );
  }

  stopSession() {
    this.castSession.stop(
      () => {
        this.isCasting = false;
        this.castSession = null;
      },
      (err: any) => {
        alert(JSON.stringify(err, null, 2));
      }
    );
  }

  private loadYoutubeVideo() {
    this.castSession.sendMessage(this.namespace, {
      command: 'INIT_COMMUNICATION_CONSTANTS',
      videoId: this.videoId,
      startSeconds: 0,
    });
    this.isCasting = true;
  }
}
// youtube-player.component.ts

@Component({
  selector: 'app-youtube-player',
  templateUrl: './youtube-player.component.html',
  styleUrls: ['./youtube-player.component.scss'],
})
export class YoutubePlayerComponent implements OnInit {
  @Input() src: string;
  @ViewChild('player', { static: true }) player: ElementRef;
  loading: boolean = true;
  castButton: HTMLButtonElement;
  cast: any;

  constructor(public renderer: Renderer2, public castService: CastService) {}

  ngOnInit() {
    this.castService.setParams({ videoId: this.src, startSeconds: 0 });
    document.addEventListener('deviceready', () => {
      // Trying to determine if cast is already active
      this.cast = this.castService.getCast();
      alert(JSON.stringify(this.cast.SessionStatus, null, 2));

      // Initialize cast sdk
      this.castService.initCast();
    });

    this.renderer.setAttribute(this.player.nativeElement, 'data-plyr-provider', 'youtube');
    this.renderer.setAttribute(this.player.nativeElement, 'data-plyr-embed-id', this.src);

    const player = new Plyr(this.player.nativeElement, {
      controls: () => playerControls,
    });

    player.on('ready', (event) => {
      this.loading = false;
      this.castButton = <HTMLButtonElement>document.getElementById('castButton');
      this.castButton.addEventListener('click', (e) => {
        this.castService.requestSession(() => {
          this.castButton.blur(); // Loose focus to update DOM

          if (this.castService.isCasting) {
            this.castButton.innerHTML = castConnectedIcon;
          } else {
            this.castButton.innerHTML = castIcon;
          }
        });
      });

      if (!this.castService.castEnabled) {
        this.castButton.classList.add('disabled');
      } else {
        this.castButton.classList.remove('disabled');
        this.castButton.innerHTML = castIcon;
      }
    });
  }
}

Also, is it possible to show a push notification whenever I start casting with this plugin?

Cannot sendMessage() to cast URL

Hello and thanks for this amazing plugin.

I have set it up and got the example with the video and pause/stop working in few minutes.

Unfortunately, that is not the use I would like to achieve: I would need to open a remote URL on the screen. I have found this project that uses Chromecast APIs and works like a charm on their web-example using Chrome browser:
Project: https://github.com/DeMille/url-cast-receiver
Example: https://demille.github.io/url-cast-receiver/

Basically, I would like to achieve that using this plugin.
I have tried to adapt their implementation inside my test cordova app, but I always get issues when calling the requestSession method: the error callback gets triggered, and the error I get is: "namespace 'urn:x-cast:com.url.cast' not founded unverified"

I have tried both using the recommended App ID and namespace from the project, and also tried using one generated by me, but always get that error. After that, it looks like I can no longer close the session, not even force closing the app, so the only way to be able to scan again my Chromecast TV is to uninstall the app and re-install again, but I believe this is only a side effect of the main issue.

Thanks for any hint!

(1.0.0) (Android) selectRoute - Fails silently

This is an intermittent issue. Possibly happens for version 0.x as well.

I am mostly creating this issue so that all of these notes can be stored here rather than in the code.
(It took me ~12 hours to pinpoint all of this, so this information is going somewhere.)
(I believe I have basically fixed this issue.)

On occasion it will fail to join the route with this sequence of events:

  • SessionManagerListener.onSessionStarting
  • MediaRoute.Callback.onRouteUnselected
    • Note: At this point, (maybe before as well), SessionManager().getCurrentCastSession() will return a session with session.isConnected() == false (so it seems it "sort of" connected, but no feedback on chromecast TV)
  • SessionManagerListener.onSessionEnding
  • SessionManagerListener.onSessionEnded (errCode == 0 (success))

It never hits:

  • SessionManagerListener.onSessionStarted
    • which is our success indicator
  • SessionManagerListener.onSessionStartFailed
    • which is our normal failure indicator

Observations

Possibly related to #48

Appears to be a timing issue.

  • Only seems to be a problem on my Android 4.4 device, not my 7.0.
    • My 4.4 device is much slower, so if it is a timing issue, this may exacerbate the problem.
  • We get the routes from an active scan to join, so they should be available, but MediaRouter logs one of the following out whenever this situation occurs:
    • "Ignoring attempt to select removed route: "
    • "Unselecting the current route because it is no longer selectable: "
  • So it appears that the route has just quickly disappeared before we could join it
  • It is a temporary disappearance because when I have it retry the select on failure automatically it will always succeed ...eventually.
    • One time it took 9 retries with ~4.5 seconds per try, usually only takes 1
    • Edit: after changing it so that the scan was not stopped between retries it seems to only take 1 retry to succeed. (much more reasonable.)

App Get CRASHED on Android 12V. (Ionic 5)

--------- beginning of crash
2022-09-06 10:51:50.814 29922-29922/com.d.devott E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.d.devott, PID: 29922
java.lang.IllegalArgumentException: com.d.devott: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
at android.app.PendingIntent.checkFlags(PendingIntent.java:382)
at android.app.PendingIntent.getBroadcastAsUser(PendingIntent.java:673)
at android.app.PendingIntent.getBroadcast(PendingIntent.java:660)
at com.google.android.gms.internal.cast.zzah.zza(Unknown Source:32)
at com.google.android.gms.cast.framework.CastSession$zzb.onResult(Unknown Source:12)
at com.google.android.gms.common.api.internal.BasePendingResult$CallbackHandler.handleMessage(com.google.android.gms:play-services-base@@18.0.0:6)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8663)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)

Amazon Fire Tv Stick cast?

It would be awesome if this plugin could cast even to Amazon Fire Tv Stick device. Do you plan to implement something like that?
Thanks!

error: resource android:attr/dialogCornerRadius not found.

Not able to get my CORDOVA app built after adding the plugin. I am not android guy so can't understand the problem.

  1. cordova -v -> 8.1.2
  2. cordova android spec="^8.0.0"
  3. See the logs below.

BUILD FAILED in 23s
cmd: Command failed with exit code 1 Error output:
C:\Users\hp.gradle\caches\transforms-1\files-1.1\appcompat-1.0.0.aar\b5c9fb90315cdd0e79db194839b0d380\res\values-v28\values-v28.xml:9:5-12:13: AAPT: error: resource android:attr/dialogCornerRadius not found.

\platforms\android\app\build\intermediates\incremental\mergeDebugResources\merged.dir\values-v28\values-v28.xml:11: AAPT: error: resource android:attr/dialogCornerRadius not found.

C:\Users\hp.gradle\caches\transforms-1\files-1.1\appcompat-v7-24.1.1.aar\f6a78c37ce4f50869e1b2c73fc669a08\res\values\values.xml:201:5-69: AAPT: error: resource android:attr/fontVariationSettings not found.

C:\Users\hp.gradle\caches\transforms-1\files-1.1\appcompat-v7-24.1.1.aar\f6a78c37ce4f50869e1b2c73fc669a08\res\values\values.xml:201:5-69: AAPT: error: resource android:attr/ttcIndex not found.

\platforms\android\app\build\intermediates\incremental\mergeDebugResources\merged.dir\values-v28\values-v28.xml:7: error: resource android:attr/dialogCornerRadius not found.
\platforms\android\app\build\intermediates\incremental\mergeDebugResources\merged.dir\values-v28\values-v28.xml:11: error: resource android:attr/dialogCornerRadius not found.
\platforms\android\app\build\intermediates\incremental\mergeDebugResources\merged.dir\values\values.xml:271: error: resource android:attr/fontVariationSettings not found.
\platforms\android\app\build\intermediates\incremental\mergeDebugResources\merged.dir\values\values.xml:271: error: resource android:attr/ttcIndex not found.
error: failed linking references.

Failed to execute aapt
com.android.ide.common.process.ProcessException: Failed to execute aapt
at com.android.builder.core.AndroidBuilder.processResources(AndroidBuilder.java:796)
at com.android.build.gradle.tasks.ProcessAndroidResources.invokeAaptForSplit(ProcessAndroidResources.java:551)
at com.android.build.gradle.tasks.ProcessAndroidResources.doFullTaskAction(ProcessAndroidResources.java:285)
at com.android.build.gradle.internal.tasks.IncrementalTask.taskAction(IncrementalTask.java:109)
at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73)
at org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$IncrementalTaskAction.doExecute(DefaultTaskClassInfoStore.java:173)
at org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$StandardTaskAction.execute(DefaultTaskClassInfoStore.java:134)
at org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$StandardTaskAction.execute(DefaultTaskClassInfoStore.java:121)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$1.run(ExecuteActionsTaskExecuter.java:122)
at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:336)
at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:328)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:197)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:107)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:111)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:92)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:70)
at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:63)
at org.gradle.api.internal.tasks.execution.ResolveTaskOutputCachingStateExecuter.execute(ResolveTaskOutputCachingStateExecuter.java:54)
at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:58)
at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:88)
at org.gradle.api.internal.tasks.execution.ResolveTaskArtifactStateTaskExecuter.execute(ResolveTaskArtifactStateTaskExecuter.java:52)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:52)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:54)
at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:34)
at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker$1.run(DefaultTaskGraphExecuter.java:248)
at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:336)
at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:328)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:197)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:107)
at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:241)
at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:230)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker.processTask(DefaultTaskPlanExecutor.java:124)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker.access$200(DefaultTaskPlanExecutor.java:80)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:105)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:99)
at org.gradle.execution.taskgraph.DefaultTaskExecutionPlan.execute(DefaultTaskExecutionPlan.java:625)
at org.gradle.execution.taskgraph.DefaultTaskExecutionPlan.executeWithTask(DefaultTaskExecutionPlan.java:580)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker.run(DefaultTaskPlanExecutor.java:99)
at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.process(DefaultTaskPlanExecutor.java:60)
at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter.execute(DefaultTaskGraphExecuter.java:128)
at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:37)
at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
at org.gradle.execution.DefaultBuildExecuter.access$000(DefaultBuildExecuter.java:23)
at org.gradle.execution.DefaultBuildExecuter$1.proceed(DefaultBuildExecuter.java:43)
at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:46)
at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:30)
at org.gradle.initialization.DefaultGradleLauncher$ExecuteTasks.run(DefaultGradleLauncher.java:311)
at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:336)
at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:328)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:197)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:107)
at org.gradle.initialization.DefaultGradleLauncher.runTasks(DefaultGradleLauncher.java:202)
at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:132)
at org.gradle.initialization.DefaultGradleLauncher.executeTasks(DefaultGradleLauncher.java:107)
at org.gradle.internal.invocation.GradleBuildController$1.call(GradleBuildController.java:78)
at org.gradle.internal.invocation.GradleBuildController$1.call(GradleBuildController.java:75)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:152)
at org.gradle.internal.invocation.GradleBuildController.doBuild(GradleBuildController.java:100)
at org.gradle.internal.invocation.GradleBuildController.run(GradleBuildController.java:75)
at org.gradle.tooling.internal.provider.ExecuteBuildActionRunner.run(ExecuteBuildActionRunner.java:28)
at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
at org.gradle.tooling.internal.provider.ValidatingBuildActionRunner.run(ValidatingBuildActionRunner.java:32)
at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$1.run(RunAsBuildOperationBuildActionRunner.java:43)
at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:336)
at org.gradle.internal.progress.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:328)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:197)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:107)
at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:40)
at org.gradle.tooling.internal.provider.SubscribableBuildActionRunner.run(SubscribableBuildActionRunner.java:51)
at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:45)
at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:29)
at org.gradle.launcher.exec.BuildTreeScopeBuildActionExecuter.execute(BuildTreeScopeBuildActionExecuter.java:39)
at org.gradle.launcher.exec.BuildTreeScopeBuildActionExecuter.execute(BuildTreeScopeBuildActionExecuter.java:25)
at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:71)
at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:45)
at org.gradle.tooling.internal.provider.ServicesSetupBuildActionExecuter.execute(ServicesSetupBuildActionExecuter.java:51)
at org.gradle.tooling.internal.provider.ServicesSetupBuildActionExecuter.execute(ServicesSetupBuildActionExecuter.java:32)
at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:36)
at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:25)
at org.gradle.tooling.internal.provider.ParallelismConfigurationBuildActionExecuter.execute(ParallelismConfigurationBuildActionExecuter.java:43)
at org.gradle.tooling.internal.provider.ParallelismConfigurationBuildActionExecuter.execute(ParallelismConfigurationBuildActionExecuter.java:29)
at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:64)
at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:29)
at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:55)
at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:42)
at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:58)
at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:33)
at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:67)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:37)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72)
at org.gradle.util.Swapper.swap(Swapper.java:38)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:62)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:82)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)
at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
Caused by: java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for details
at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:503)
at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:482)
at com.google.common.util.concurrent.AbstractFuture$TrustedFuture.get(AbstractFuture.java:79)
at com.android.builder.core.AndroidBuilder.processResources(AndroidBuilder.java:794)
... 115 more
Caused by: java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for details
at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:503)
at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:462)
at com.google.common.util.concurrent.AbstractFuture$TrustedFuture.get(AbstractFuture.java:79)
at com.android.builder.internal.aapt.v2.QueueableAapt2.lambda$makeValidatedPackage$1(QueueableAapt2.java:179)
Caused by: com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for details
at com.android.builder.png.AaptProcess$NotifierProcessOutput.handleOutput(AaptProcess.java:463)
at com.android.builder.png.AaptProcess$NotifierProcessOutput.err(AaptProcess.java:415)
at com.android.builder.png.AaptProcess$ProcessOutputFacade.err(AaptProcess.java:332)
at com.android.utils.GrabProcessOutput$1.run(GrabProcessOutput.java:104)

metadata not correctly set on receiver / not implemented for all types

Currently on the generic metadata type is supported in the native code, while we can set the other types (movie, musictrack etc) in javascript and it gets passed through the native code checked if the metadata type is generic and if not ignores it.

However, even when the metadata type is generic it is still not populated on the receiver.

Same implementation limit and issue present on iOS and Android (so at least they're consistent!).

Need to investigate why metadata isn't being sent to the receiver and fix for the generic type.

Need to implement the other supported metadata types once the first issue is resolved.

App crashes after a while of casting

I've successfully cast the audio to cast device. Then I just let the app open for some minutes then the app has been crashed.

Logcat shows me this error.

2019-10-05 15:02:32.807 1022-1022/com.hintdesk.sachxua D/AndroidRuntime: Shutting down VM
2019-10-05 15:02:32.814 1022-1022/com.hintdesk.sachxua E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.hintdesk.sachxua, PID: 1022
java.lang.IllegalStateException: Not connected. Call connect() and wait for onConnected() to be called.
at com.google.android.gms.common.internal.BaseGmsClient.checkConnected(Unknown Source:198)
at com.google.android.gms.internal.cast.zzdd.getVolume(Unknown Source:136)
at com.google.android.gms.cast.Cast$CastApi$zza.getVolume(Unknown Source:23)
at acidhax.cordova.chromecast.ChromecastSession.createSessionObject(ChromecastSession.java:471)
at acidhax.cordova.chromecast.ChromecastSession.onConnectionSuspended(ChromecastSession.java:624)
at com.google.android.gms.common.internal.GmsClientEventManager.onUnintentionalDisconnection(Unknown Source:49)
at com.google.android.gms.common.api.internal.zaaw.zab(Unknown Source:319)
at com.google.android.gms.common.api.internal.zaah.onConnectionSuspended(Unknown Source:42)
at com.google.android.gms.common.api.internal.zabe.onConnectionSuspended(Unknown Source:106)
at com.google.android.gms.common.api.internal.zaq.onConnectionSuspended(Unknown Source:9)
at com.google.android.gms.common.internal.zaf.onConnectionSuspended(Unknown Source:4)
at com.google.android.gms.common.internal.BaseGmsClient$zzb.handleMessage(Unknown Source:39)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6718)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
2019-10-05 15:02:32.823 1193-1204/? W/ActivityManager: Force finishing activity com.hintdesk.sachxua/.MainActivity

CC License for chromecast documentation

I noticed that the function documentation in chrome.cast.js is basically just a copy and paste from the official chromecast docs.

eg. chrome.cast.media.LoadRequest
https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.LoadRequest

At the bottom of the page it says:
"Creative Commons Attribution 4.0 License,Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License."

There site policies also give instructions on how to provide attribution:
https://developers.google.com/terms/site-policies#attribution

How this would apply in this case?

(I thought there was a thread about tracking license info for modules on here but I can't seem to find it... Maybe I was dreaming?)

chrome.cast.requestSession never finishes

The tv is showing a cromecast-icon when I run chrome.cast.requestSession (I use the example-script) but the function never finishes and there is no error.

Any thoughts on what it can be?

Jasmine test framework does not work very well

Jasmine is not really appropriate for our tests at all.

  • Jasmine will allow tests to continue even if a before failed.
  • We have these huge ugly test chains because it is the only way to guarantee a certain state while testing something.
  • Jasmine has no way to terminate a test on the first expect fail, so I have been forced to chain Promises to prevent test progression if one fails. (so rather than having ~20-30 tests we have 4 giant ones.)
  • When an expect fails the stack trace is masked so I have moved to throwing an Error to get better output (basically not using expect at all anymore).
  • The tests have long time outs since they require user interaction. So if something goes wrong, but does not throw (aka an expect failure), you have to wait the entire time to get a timeout failure.
    • Worst of all, nothing tells you where it was in the chain. So the only way to figure it out is by applying breakpoints everywhere. This makes debugging very painful.

I have started work on switching the test framework to Mocha. It appears that it will work much better.

Originally this was part of Issue #36, but that was getting monolithic and I would like to have a singular location that explains the reasoning for using a non-standard test framework.

Can't compile, android dependency conflict

When I try to deploy with this plugin added I get the following error:

Running command: Cordova\platforms\android\gradlew cdvBuildDebug -b Cordova\platforms\android\build.gradle
> Task :app:preBuild UP-TO-DATE
> Task :CordovaLib:preBuild UP-TO-DATE
> Task :CordovaLib:preDebugBuild UP-TO-DATE
> Task :CordovaLib:checkDebugManifest UP-TO-DATE
> Task :CordovaLib:processDebugManifest UP-TO-DATE
> Task :app:preDebugBuild UP-TO-DATE
> Task :CordovaLib:compileDebugAidl NO-SOURCE
> Task :app:compileDebugAidl UP-TO-DATE
> Task :CordovaLib:packageDebugRenderscript NO-SOURCE
> Task :app:compileDebugRenderscript UP-TO-DATE
> Task :app:checkDebugManifest UP-TO-DATE
> Task :app:generateDebugBuildConfig UP-TO-DATE
> Task :app:prepareLintJar UP-TO-DATE
> Task :app:generateDebugSources UP-TO-DATE
> Task :CordovaLib:compileDebugRenderscript UP-TO-DATE
> Task :CordovaLib:generateDebugBuildConfig UP-TO-DATE
> Task :CordovaLib:generateDebugResValues UP-TO-DATE
> Task :CordovaLib:generateDebugResources UP-TO-DATE
> Task :CordovaLib:packageDebugResources UP-TO-DATE
> Task :CordovaLib:generateDebugRFile UP-TO-DATE
> Task :CordovaLib:prepareLintJar UP-TO-DATE
> Task :CordovaLib:generateDebugSources UP-TO-DATE
> Task :CordovaLib:javaPreCompileDebug UP-TO-DATE
> Task :CordovaLib:compileDebugJavaWithJavac UP-TO-DATE
> Task :CordovaLib:processDebugJavaRes NO-SOURCE
> Task :CordovaLib:transformClassesAndResourcesWithPrepareIntermediateJarsForDebug UP-TO-DATE
> Task :app:javaPreCompileDebug UP-TO-DATE
> Task :app:mainApkListPersistenceDebug UP-TO-DATE
> Task :app:generateDebugResValues UP-TO-DATE
> Task :app:generateDebugResources UP-TO-DATE
> Task :app:mergeDebugResources UP-TO-DATE
> Task :app:createDebugCompatibleScreenManifests UP-TO-DATE
> Task :app:processDebugManifest UP-TO-DATE
> Task :app:processDebugResources UP-TO-DATE
> Task :app:compileDebugJavaWithJavac UP-TO-DATE
> Task :app:compileDebugNdk NO-SOURCE
> Task :app:compileDebugSources UP-TO-DATE
> Task :app:mergeDebugShaders UP-TO-DATE
> Task :app:compileDebugShaders UP-TO-DATE
> Task :app:generateDebugAssets UP-TO-DATE
> Task :CordovaLib:mergeDebugShaders UP-TO-DATE
> Task :CordovaLib:compileDebugShaders UP-TO-DATE
> Task :CordovaLib:generateDebugAssets UP-TO-DATE
> Task :CordovaLib:packageDebugAssets UP-TO-DATE
> Task :app:mergeDebugAssets UP-TO-DATE
> Task :app:validateSigningDebug UP-TO-DATE
> Task :app:signingConfigWriterDebug UP-TO-DATE
> Task :app:transformClassesWithDexBuilderForDebug UP-TO-DATE

D8: Program type already present: android.support.v4.media.MediaBrowserCompat$ConnectionCallback$ConnectionCallbackInternal

FAILURE: Build failed with an exception.

* What went wrong:
> Task :app:transformDexArchiveWithExternalLibsDexMergerForDebug FAILED
35 actionable tasks: 1 executed, 34 up-to-date
Execution failed for task ':app:transformDexArchiveWithExternalLibsDexMergerForDebug'.
> com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives:
  Learn how to resolve the issue at https://developer.android.com/studio/build/dependencies#duplicate_classes.  Program type already present: android.support.v4.media.MediaBrowserCompat$ConnectionCallback$ConnectionCallbackInternal

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 4s

Looking at the dependency tree, it looks like com.google.android.gms:play-services-cast:16.1.2 is the culprit for bringing in the older android.support.v4.media library.

gradlew app:dependencies --configuration releaseRuntimeClasspath:

> Task :app:dependencies

------------------------------------------------------------
Project :app
------------------------------------------------------------

releaseRuntimeClasspath - Resolved configuration for runtime for variant: release
+--- project :CordovaLib
+--- com.google.android.gms:play-services-cast:16.1.2
|    +--- com.android.support:mediarouter-v7:26.1.0
|    |    +--- com.android.support:appcompat-v7:26.1.0
|    |    |    +--- com.android.support:support-annotations:26.1.0
|    |    |    +--- com.android.support:support-v4:26.1.0
|    |    |    |    +--- com.android.support:support-compat:26.1.0
|    |    |    |    |    +--- com.android.support:support-annotations:26.1.0
|    |    |    |    |    \--- android.arch.lifecycle:runtime:1.0.0
|    |    |    |    |         +--- android.arch.lifecycle:common:1.0.0
|    |    |    |    |         \--- android.arch.core:common:1.0.0
|    |    |    |    +--- com.android.support:support-media-compat:26.1.0
|    |    |    |    |    +--- com.android.support:support-annotations:26.1.0
|    |    |    |    |    \--- com.android.support:support-compat:26.1.0 (*)
|    |    |    |    +--- com.android.support:support-core-utils:26.1.0
|    |    |    |    |    +--- com.android.support:support-annotations:26.1.0
|    |    |    |    |    \--- com.android.support:support-compat:26.1.0 (*)
|    |    |    |    +--- com.android.support:support-core-ui:26.1.0
|    |    |    |    |    +--- com.android.support:support-annotations:26.1.0
|    |    |    |    |    \--- com.android.support:support-compat:26.1.0 (*)
|    |    |    |    \--- com.android.support:support-fragment:26.1.0
|    |    |    |         +--- com.android.support:support-compat:26.1.0 (*)
|    |    |    |         +--- com.android.support:support-core-ui:26.1.0 (*)
|    |    |    |         \--- com.android.support:support-core-utils:26.1.0 (*)
|    |    |    +--- com.android.support:support-vector-drawable:26.1.0
|    |    |    |    +--- com.android.support:support-annotations:26.1.0
|    |    |    |    \--- com.android.support:support-compat:26.1.0 (*)
|    |    |    \--- com.android.support:animated-vector-drawable:26.1.0
|    |    |         +--- com.android.support:support-vector-drawable:26.1.0 (*)
|    |    |         \--- com.android.support:support-core-ui:26.1.0 (*)
|    |    \--- com.android.support:palette-v7:26.1.0
|    |         +--- com.android.support:support-compat:26.1.0 (*)
|    |         \--- com.android.support:support-core-utils:26.1.0 (*)
|    +--- com.google.android.gms:play-services-base:16.0.1
|    |    +--- com.google.android.gms:play-services-basement:16.0.1
|    |    |    \--- com.android.support:support-v4:26.1.0 (*)
|    |    \--- com.google.android.gms:play-services-tasks:16.0.1
|    |         \--- com.google.android.gms:play-services-basement:16.0.1 (*)
|    +--- com.google.android.gms:play-services-basement:16.0.1 (*)
|    +--- com.google.android.gms:play-services-flags:16.0.1
|    |    +--- com.google.android.gms:play-services-base:16.0.1 (*)
|    |    \--- com.google.android.gms:play-services-basement:16.0.1 (*)
|    \--- com.google.android.gms:play-services-tasks:16.0.1 (*)
+--- androidx.appcompat:appcompat:1.0.0
|    +--- androidx.annotation:annotation:1.0.0
|    +--- androidx.core:core:1.0.0
|    |    +--- androidx.annotation:annotation:1.0.0
|    |    +--- androidx.collection:collection:1.0.0
|    |    |    \--- androidx.annotation:annotation:1.0.0
|    |    +--- androidx.lifecycle:lifecycle-runtime:2.0.0
|    |    |    +--- androidx.lifecycle:lifecycle-common:2.0.0
|    |    |    |    \--- androidx.annotation:annotation:1.0.0
|    |    |    +--- androidx.arch.core:core-common:2.0.0
|    |    |    |    \--- androidx.annotation:annotation:1.0.0
|    |    |    \--- androidx.annotation:annotation:1.0.0
|    |    \--- androidx.versionedparcelable:versionedparcelable:1.0.0
|    |         +--- androidx.annotation:annotation:1.0.0
|    |         \--- androidx.collection:collection:1.0.0 (*)
|    +--- androidx.collection:collection:1.0.0 (*)
|    +--- androidx.cursoradapter:cursoradapter:1.0.0
|    |    \--- androidx.annotation:annotation:1.0.0
|    +--- androidx.legacy:legacy-support-core-utils:1.0.0
|    |    +--- androidx.annotation:annotation:1.0.0
|    |    +--- androidx.core:core:1.0.0 (*)
|    |    +--- androidx.documentfile:documentfile:1.0.0
|    |    |    \--- androidx.annotation:annotation:1.0.0
|    |    +--- androidx.loader:loader:1.0.0
|    |    |    +--- androidx.annotation:annotation:1.0.0
|    |    |    +--- androidx.core:core:1.0.0 (*)
|    |    |    +--- androidx.lifecycle:lifecycle-livedata:2.0.0
|    |    |    |    +--- androidx.arch.core:core-runtime:2.0.0
|    |    |    |    |    +--- androidx.annotation:annotation:1.0.0
|    |    |    |    |    \--- androidx.arch.core:core-common:2.0.0 (*)
|    |    |    |    +--- androidx.lifecycle:lifecycle-livedata-core:2.0.0
|    |    |    |    |    +--- androidx.lifecycle:lifecycle-common:2.0.0 (*)
|    |    |    |    |    +--- androidx.arch.core:core-common:2.0.0 (*)
|    |    |    |    |    \--- androidx.arch.core:core-runtime:2.0.0 (*)
|    |    |    |    \--- androidx.arch.core:core-common:2.0.0 (*)
|    |    |    \--- androidx.lifecycle:lifecycle-viewmodel:2.0.0
|    |    |         \--- androidx.annotation:annotation:1.0.0
|    |    +--- androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
|    |    |    \--- androidx.annotation:annotation:1.0.0
|    |    \--- androidx.print:print:1.0.0
|    |         \--- androidx.annotation:annotation:1.0.0
|    +--- androidx.fragment:fragment:1.0.0
|    |    +--- androidx.core:core:1.0.0 (*)
|    |    +--- androidx.legacy:legacy-support-core-ui:1.0.0
|    |    |    +--- androidx.annotation:annotation:1.0.0
|    |    |    +--- androidx.core:core:1.0.0 (*)
|    |    |    +--- androidx.legacy:legacy-support-core-utils:1.0.0 (*)
|    |    |    +--- androidx.customview:customview:1.0.0
|    |    |    |    +--- androidx.annotation:annotation:1.0.0
|    |    |    |    \--- androidx.core:core:1.0.0 (*)
|    |    |    +--- androidx.viewpager:viewpager:1.0.0
|    |    |    |    +--- androidx.annotation:annotation:1.0.0
|    |    |    |    +--- androidx.core:core:1.0.0 (*)
|    |    |    |    \--- androidx.customview:customview:1.0.0 (*)
|    |    |    +--- androidx.coordinatorlayout:coordinatorlayout:1.0.0
|    |    |    |    +--- androidx.annotation:annotation:1.0.0
|    |    |    |    +--- androidx.core:core:1.0.0 (*)
|    |    |    |    \--- androidx.customview:customview:1.0.0 (*)
|    |    |    +--- androidx.drawerlayout:drawerlayout:1.0.0
|    |    |    |    +--- androidx.annotation:annotation:1.0.0
|    |    |    |    +--- androidx.core:core:1.0.0 (*)
|    |    |    |    \--- androidx.customview:customview:1.0.0 (*)
|    |    |    +--- androidx.slidingpanelayout:slidingpanelayout:1.0.0
|    |    |    |    +--- androidx.annotation:annotation:1.0.0
|    |    |    |    +--- androidx.core:core:1.0.0 (*)
|    |    |    |    \--- androidx.customview:customview:1.0.0 (*)
|    |    |    +--- androidx.interpolator:interpolator:1.0.0
|    |    |    |    \--- androidx.annotation:annotation:1.0.0
|    |    |    +--- androidx.swiperefreshlayout:swiperefreshlayout:1.0.0
|    |    |    |    +--- androidx.annotation:annotation:1.0.0
|    |    |    |    +--- androidx.core:core:1.0.0 (*)
|    |    |    |    \--- androidx.interpolator:interpolator:1.0.0 (*)
|    |    |    +--- androidx.asynclayoutinflater:asynclayoutinflater:1.0.0
|    |    |    |    +--- androidx.annotation:annotation:1.0.0
|    |    |    |    \--- androidx.core:core:1.0.0 (*)
|    |    |    \--- androidx.cursoradapter:cursoradapter:1.0.0 (*)
|    |    +--- androidx.legacy:legacy-support-core-utils:1.0.0 (*)
|    |    +--- androidx.annotation:annotation:1.0.0
|    |    +--- androidx.loader:loader:1.0.0 (*)
|    |    \--- androidx.lifecycle:lifecycle-viewmodel:2.0.0 (*)
|    +--- androidx.vectordrawable:vectordrawable:1.0.0
|    |    +--- androidx.annotation:annotation:1.0.0
|    |    \--- androidx.core:core:1.0.0 (*)
|    \--- androidx.vectordrawable:vectordrawable-animated:1.0.0
|         +--- androidx.vectordrawable:vectordrawable:1.0.0 (*)
|         \--- androidx.legacy:legacy-support-core-ui:1.0.0 (*)
+--- androidx.mediarouter:mediarouter:1.0.0
|    +--- androidx.media:media:1.0.0
|    |    +--- androidx.annotation:annotation:1.0.0
|    |    +--- androidx.core:core:1.0.0 (*)
|    |    \--- androidx.versionedparcelable:versionedparcelable:1.0.0 (*)
|    +--- androidx.appcompat:appcompat:1.0.0 (*)
|    +--- androidx.palette:palette:1.0.0
|    |    +--- androidx.core:core:1.0.0 (*)
|    |    \--- androidx.legacy:legacy-support-core-utils:1.0.0 (*)
|    \--- androidx.recyclerview:recyclerview:1.0.0
|         +--- androidx.annotation:annotation:1.0.0
|         +--- androidx.core:core:1.0.0 (*)
|         \--- androidx.legacy:legacy-support-core-ui:1.0.0 (*)
\--- androidx.legacy:legacy-support-v4:1.0.0
     +--- androidx.core:core:1.0.0 (*)
     +--- androidx.media:media:1.0.0 (*)
     +--- androidx.legacy:legacy-support-core-utils:1.0.0 (*)
     +--- androidx.legacy:legacy-support-core-ui:1.0.0 (*)
     \--- androidx.fragment:fragment:1.0.0 (*)

(*) - dependencies omitted (listed previously)

I'm not quite sure how this is working for you guys?

OnMessage Doesn’t Handle Single Quotes Correctly

In https://github.com/jellyfin/jellyfin-web/issues/440, it was discovered that playing a file with an apostrophe/single quote in its name would cause the Jellyfin Android app to lose control of the ChromeCast.

Through further review, it appears that this line presents an issue:
https://github.com/jellyfin/cordova-plugin-chromecast/blob/c1ba3e7b25ca1097efb21bef0baf19b0d6801971/src/android/Chromecast.java#L809

The Session ID is a GUID, and the namespace in this case is urn:x-cast:com.ConnectSDK. The message is where the concern comes in.

Since the message will contain JSON, a single quote in a value (such as media title) is valid. However, the larger string in Java is wrapped in single quotes, so this causes the string to be terminated early.

We should identify the best way to handle unexpected single quotes in the message, or wrap this in a way where single quotes won’t interfere.

Support __onGCastApiAvailable

It would be nice if we could implement chrome Api's method of signalling that the api is available.

In Chrome desktop you do:

window['__onGCastApiAvailable'] = function(isAvailable, err) {
  if (isAvailable) {
    // start using the api
  }
};

But in cordova-plugin-chromecast you do:

document.addEventListener("deviceready", function () {
    // start using the api
});

Would be nice to conform a bit more because:

  • less difference between our usage and chrome's = less documentation we have to write and maintain. :D
  • Allows identical code to be ported between web and cordova
    • (This happens to be my use-case. We use a remote web server which works from desktop and cordova.)

Discussion: Display only video receiver

Hi,

This is not an issue but rather a discussion. I'd like to know if there is a way to display only the video receivers on the receiver list that pops up?

Thanks

Change License from GPLv2

Since this project is now being used in more than just an Android app, it is pretty important that we re-license it.

I don’t know about Cordova specifics with linking this plug-in, so I will start with an understanding that this is provided with any app that uses it.

Under the general terms of the GPLv2, distribution on the Apple App Store is not technically permitted. There are various reasons, and the FSF covers some here.

Technically, we have been given permission from @acidhax upstream to use the code as we see fit (see videostream/cordova-chromecast#62) and so @joshuaboniface added the GPLv2 (see 57ad1b7) as we believed that would be our only use at the time.

In order to make wider distribution more permissible, I propose that we re-license this library under a dual license. We will most likely follow the example set by VLC and choose a dual GPLv3/MPLv2 approach, but I am open to other suggestions.

In addition, to complete this process, we will need the permission of any contributors up to this point. I will tag them in a separate comment.

How to use it on Ionic 4?

Hi ! I'm having trouble importing chrome just to use it

These are my steps:
INSTALL:
cordova plugin add https://github.com/jellyfin/cordova-plugin-chromecast.git

USE (This is probably the part Im doing wrong):
import * as Chrome from 'cordova-plugin-chromecast/www/chrome.cast';

Log:

ERROR in ./node_modules/cordova-plugin-chromecast/www/chrome.cast.js
Module not found: Error: Can't resolve 'cordova-plugin-chromecast.EventEmitter' in 'E:\Github Projects\moodreads-ionic-4\node_modules\cordova-plugin-chromecast\www'
resolve 'cordova-plugin-chromecast.EventEmitter' in 'E:\Github Projects\moodreads-ionic-4\node_modules\cordova-plugin-chromecast\www'
  Parsed request is a module
  using description file: E:\Github Projects\moodreads-ionic-4\node_modules\cordova-plugin-chromecast\package.json (relative path: ./www)
    Field 'browser' doesn't contain a valid alias configuration
    resolve as module
      looking for modules in E:/Github Projects/moodreads-ionic-4
        using description file: E:\Github Projects\moodreads-ionic-4\package.json (relative path: .)
          Field 'browser' doesn't contain a valid alias configuration
          using description file: E:\Github Projects\moodreads-ionic-4\package.json (relative path: ./cordova-plugin-chromecast.EventEmitter)
            no extension
              Field 'browser' doesn't contain a valid alias configuration
              E:\Github Projects\moodreads-ionic-4\cordova-plugin-chromecast.EventEmitter doesn't exist
            .ts
              Field 'browser' doesn't contain a valid alias configuration
              E:\Github Projects\moodreads-ionic-4\cordova-plugin-chromecast.EventEmitter.ts doesn't exist
            .tsx
              Field 'browser' doesn't contain a valid alias configuration
              E:\Github Projects\moodreads-ionic-4\cordova-plugin-chromecast.EventEmitter.tsx doesn't exist
            .mjs
              Field 'browser' doesn't contain a valid alias configuration
              E:\Github Projects\moodreads-ionic-4\cordova-plugin-chromecast.EventEmitter.mjs doesn't exist
            .js
              Field 'browser' doesn't contain a valid alias configuration
              E:\Github Projects\moodreads-ionic-4\cordova-plugin-chromecast.EventEmitter.js doesn't exist
            as directory
              E:\Github Projects\moodreads-ionic-4\cordova-plugin-chromecast.EventEmitter doesn't exist

Thanks!

media not playing when casting + casting youtube videos

Hi there! First of all, thank you for creating this plugin. I was trying to implement this plugin using @ionc/angular with cordova following th example code in the repo but whenever I select the device to cast the video it doesn't show anything on the smartTV screen but the casting icon and not the media. I'm using the default receiver appId as you do in the example, but would I have to make and register a custom receiver to be able to play the video on a smartTV or chromecast?

This is my implementation:

// cast.component.ts

@Component({
  selector: 'app-cast',
  templateUrl: 'cast.component.html',
  styleUrls: ['cast.component.scss'],
})
export class Cast implements OnInit {
  private cast: any;
  private castSession: any;
  private castMedia: any;
  isCasting: boolean = false;

  constructor() {}

  ngOnInit() {
    document.addEventListener('deviceready', () => this.initializeCast());
  }

  private initializeCast() {
    this.cast = chrome.cast;
    const castId = chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID;
    const apiConfig = new this.cast.ApiConfig(
      new this.cast.SessionRequest(castId),
      (session: any) => {
        console.log(session);
      },
      (receiverAvailable: any) => {
        console.log(receiverAvailable);
      }
    );

    this.cast.initialize(
      apiConfig,
      () => {
        console.log('Cast initialized correctly!');
      },
      (err: string) => alert('Error initializing cast.')
    );
  }

  requestSession() {
    this.cast.requestSession(
      (session: any) => {
        this.castSession = session;
        alert('Is suppossed to be casting!!!');
        this.isCasting = true;
      },
      (err: string) => alert('Error requesting session.')
    );
  }

  loadMedia() {
    const videoUrl = 'https://ia801302.us.archive.org/1/items/TheWater_201510/TheWater.mp4';
    const mediaInfo = new this.cast.media.MediaInfo(videoUrl, 'video/mp4');

    this.castSession.loadMedia(
      new this.cast.media.LoadRequest(mediaInfo),
      (media: any) => {
        console.log('Video should be playing now')
        this.castMedia = media;

        setTimeout(function () => this.pauseMedia(), 4000);
      },
      (err: string) => {
        // Failed (check that the video works in your browser)
        alert('Error loading media.');
      }
    );
  }

  pauseMedia() {
    this.castMedia.pause(
      {},
      () => {
        setTimeout(function () =>  this.stopSession(), 2000);
      },
      (err: any) => {
        // Fail
        alert(err);
      }
    );
  }

  stopCastSession() {
    this.castSession.stop(
      () => {
        this.isCasting = false;
      },
      (err: string) => {
        // Fail
        alert(err);
      }
    );
  }
}

And on the html

// cast.component.html

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title> Testing App </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Some title</ion-title>
    </ion-toolbar>
  </ion-header>

  <div id="container">
    <ion-button (click)="requestSession()">
      {{isCasting ? 'Stop Casting' : 'Cast Video'}}
    </ion-button>
  </div>
</ion-content>

YOUTUBE - This really isn't an issue, but was looking through the repo and issues but didn't find any mention to casting Youtube videos and I was wondering if this was possible with this plugin.

Chromecast not responding (iOS only)

When I send a content to Chromecast if I turn off the phone screen, when I go to de app again the sender can't send commands to de receiver, Ex: The play/pause action does not work and no notifications are received.

(1.0.0) (Android) selectRoute - Crash on select group route

This occurs intermittently when trying to join a group route on 1.0.0. Possibly occurs in 0.x as well.

09-26 12:45:47.560 11533-11533/com.testy_pants.plugin_tests E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.miloproductionsinc.plugin_tests, PID: 11533
    java.lang.NullPointerException
        at androidx.mediarouter.media.MediaRouter$GlobalMediaRouter.setSelectedRouteInternal(MediaRouter.java:2633)
        at androidx.mediarouter.media.MediaRouter$GlobalMediaRouter.selectRoute(MediaRouter.java:2120)
        at androidx.mediarouter.media.MediaRouter$GlobalMediaRouter.selectRoute(MediaRouter.java:2108)
        at androidx.mediarouter.media.MediaRouter.selectRoute(MediaRouter.java:406)
        at acidhax.cordova.chromecast.ChromecastConnection$2$1.onRouteUpdate(ChromecastConnection.java:183)
        at acidhax.cordova.chromecast.ChromecastConnection$ScanCallback.onFilteredRouteUpdate(ChromecastConnection.java:590)
        at acidhax.cordova.chromecast.ChromecastConnection$ScanCallback.access$1200(ChromecastConnection.java:539)
        at acidhax.cordova.chromecast.ChromecastConnection$5.run(ChromecastConnection.java:410)
        at android.app.Activity.runOnUiThread(Activity.java:4713)
        at acidhax.cordova.chromecast.ChromecastConnection.startRouteScan(ChromecastConnection.java:389)
        at acidhax.cordova.chromecast.ChromecastConnection$2.run(ChromecastConnection.java:245)
        at android.os.Handler.handleCallback(Handler.java:733)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:136)
        at android.app.ActivityThread.main(ActivityThread.java:5017)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:515)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
        at dalvik.system.NativeStart.main(Native Method)

Possibly slightly related to #49
Both problems seem to be caused by routes temporarily and quickly becoming unavailable before we are actually able to join.

Observations

  • I have observed it on my Android 4.4 device but not on my 7.0 device.
  • I believe this issue is timing issue (see above)
  • I believe the main reason it is only happening on my 4.4 is because it is so much slower. (eg. can take 8s to join a route.)
  • The problem is much worse when debugging.
    • When I have it hooked up the to the Android Studio debugger it will crash on the 2nd call to selectRoute on average. I don't think I have ever gotten past 4 runs.
    • With no debugging, it is at least 20+ average, sometimes I spam select route 50 times and no crash.
  • Edit: Interestingly, if we catch the exception, and just let it continue scanning for the route (aka. basically letting it retry), it will hit:
    • SessionManagerListener.onSessionEnded (errCode == 0 (success))
    • It hits this before a valid route is found again
    • It then succeeds in joining the re-found route
    • It appears that it likely goes through the same event pattern as #49

android: App crashes on chrome.cast.requestSession

I have two android devices and app crashes on method chrome.cast.requestSession call with below logs i found in logcat.

Device 1: OnePlus 7Pro, Android 9 - WORKS FINE
Device 2: OnePlus Two, Android 6.0.1 - CRASH

 --------- beginning of crash
09-12 13:24:28.581 14046-14046/no.myapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: no.myapp, PID: 14046
    java.lang.NoClassDefFoundError: acidhax.cordova.chromecast.-$$Lambda$r-7qqWexZCMew-ZX5x2dUMjX1R0
        at acidhax.cordova.chromecast.Chromecast$2.run(Chromecast.java:209)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:152)
        at android.app.ActivityThread.main(ActivityThread.java:5497)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
09-12 13:24:28.587 14046-14046/no.myapp D/AppTracker: App Event: crash

No Longer Able to Cast

For Jellyfin testing, I only have one Android device that I can use. It is currently running 6.0.1, and I cannot update it to anything newer (there are no further updates provided by the manufacturer).

Starting with release 0.9.2, I can no longer cast. When I attempt to bring up the Chromecast dialog, there is this stack trace in the logs:

09-12 01:06:08.837 24419 24419 I chromium: [INFO:CONSOLE(214)] "chromecast launching app...", source: file:///android_asset/www/components/chromecast/chromecastplayer.js?v=12 (214)
09-12 01:06:08.847 24419 24474 I art     : Rejecting re-init on previously-failed class java.lang.Class<acidhax.cordova.chromecast.-$$Lambda$r-7qqWexZCMew-ZX5x2dUMjX1R0>
09-12 01:06:08.847 24419 24474 I art     : Rejecting re-init on previously-failed class java.lang.Class<acidhax.cordova.chromecast.-$$Lambda$r-7qqWexZCMew-ZX5x2dUMjX1R0>
09-12 01:06:08.850 24419 24419 I art     : Rejecting re-init on previously-failed class java.lang.Class<acidhax.cordova.chromecast.-$$Lambda$r-7qqWexZCMew-ZX5x2dUMjX1R0>
09-12 01:06:08.850 24419 24419 D AndroidRuntime: Shutting down VM
09-12 01:06:08.852 24419 24419 E AndroidRuntime: FATAL EXCEPTION: main
09-12 01:06:08.852 24419 24419 E AndroidRuntime: Process: org.jellyfin.mobile, PID: 24419
09-12 01:06:08.852 24419 24419 E AndroidRuntime: java.lang.NoClassDefFoundError: acidhax.cordova.chromecast.-$$Lambda$r-7qqWexZCMew-ZX5x2dUMjX1R0
09-12 01:06:08.852 24419 24419 E AndroidRuntime: 	at acidhax.cordova.chromecast.Chromecast$2.run(Chromecast.java:210)
09-12 01:06:08.852 24419 24419 E AndroidRuntime: 	at android.os.Handler.handleCallback(Handler.java:739)
09-12 01:06:08.852 24419 24419 E AndroidRuntime: 	at android.os.Handler.dispatchMessage(Handler.java:95)
09-12 01:06:08.852 24419 24419 E AndroidRuntime: 	at android.os.Looper.loop(Looper.java:148)
09-12 01:06:08.852 24419 24419 E AndroidRuntime: 	at android.app.ActivityThread.main(ActivityThread.java:5417)
09-12 01:06:08.852 24419 24419 E AndroidRuntime: 	at java.lang.reflect.Method.invoke(Native Method)
09-12 01:06:08.852 24419 24419 E AndroidRuntime: 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
09-12 01:06:08.852 24419 24419 E AndroidRuntime: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
09-12 01:06:08.854   832  1420 W ActivityManager:   Force finishing activity org.jellyfin.mobile/.MainActivity

The app then closes with a message: "Unfortunately, Jellyfin has stopped working."

If I revert back to our 0.9.1 release, everything is working. This would mean that some commit since July 5th 2019 has changed something that I means I can no longer test this.

Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.

Hello, 

Thanks for this fantastic plugin.

I have set it up in my Cordova app with targetSdkVersion: 30. But now, with targetSdkVersion: 33 it throws the following error when the app tries to connect the casing device. 

  • Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent. Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.

I've updated all the plugins of my app but, couldn't find any way to solve it.

Thanks for any hint!

App crash after initiate casting on iOS: 'Invalid number value (infinite) in JSON write'

I'm getting this error after initiate cast from iOS:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid number value (infinite) in JSON write'
*** First throw call stack:
(0x213122190 0x2122f79f8 0x21302c3b0 0x213c28a84 0x213c26948 0x213c29950 0x21300d2e0 0x213c28f20 0x213c29950 0x21300d2e0 0x213c28f20 0x213ae55bc 0x213ae52e4 0x10030b940 0x1002f3bf4 0x1002f4a60 0x1002fe570 0x1002fe614 0x1003c35fc 0x10039872c 0x100398070 0x100396e50 0x10035232c 0x100365fa4 0x100365ae4 0x100352898 0x100371598 0x100db36f4 0x100db4c78 0x100dc26fc 0x2130b3b30 0x2130aea68 0x2130adfc4 0x2152af79c 0x23fa36c38 0x10029945c 0x212b6e8e0)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)

The cast works on chromecast device but app crash.

iOS tracks

Hi, I recently download the latest code (98edc9e) and the problem is that no tracks are received in javascript Media object. I started debug inside iOS code then i found that the code is commented at line 683 inside getMediaTracks's method, I think it's because of an error when creating the array of tracks. Then I try to fixed then I see the problem was [MLPCastUtilities getTextTrackSubtype:mediaTrack.textSubtype] when textSubtipe is 0 (Unknown) the method getTextTrackSubtype return nil because of default return inside switch. Thanks.

Does anyone use session.sendMessage?

I want to know if anyone uses this, and whether or not it actually works?

@anthonylavado @dkanada Jellyfin uses a custom receiver I believe, which seems to be the main reason to use sendMessage.... So just wondering if you guys are using it? (even on the old version of cordova-plugin-chromecast)

Android Receiver discrepancies after Restart

I'm seeing some strange behavior that only happens on Android that i'm completely stuck on.

I do the following, I initialize a default media receiver, and I see that the receiver is available. I cast to the device successfully, and let the user stop the cast.

When the cast is stopped, if the user closes the app, removing it from the background apps by swiping to close, then the next time they open the app, the receiver will not become available. If they close it this way again, the receiver will be available this time. I can repeat this process in a pattern. Super frustrating, is there someway to reset the chrome.cast instance or something?

I am 100% sure I have no logic to cause this. Works completely fine every time on iOS and web.

Any help would be greatly appreciated.

[Question] Anybody found an event to detect when a track has ended playing?

Not an issue, but a question:

The only post I could find is this one https://stackoverflow.com/questions/19645972/chromecast-sdk-android-is-there-a-way-to-check-whether-the-media-playing-on but I wonder if any of you have used a different approach, it looks much for what it is.

The documentation has https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media#addUpdateListener but does not provide such a action.

Thanks!

How to handle media.setPlaybackRate for desktop compatibility

Terminology

desktop/cdv devs: devs who write a hosted app that runs on both cordova apps and on desktop browsers

Why am I asking this question

This question arose when looking into PR #63.

The PR will add the function chrome.cast.media.setPlaybackRate.

Unfortunately, media.setPlaybackRate doesn't exist in the official Google Cast API for Chrome.

This is a problem for hosted cordova apps that run on both cordova apps and in desktop chrome.

Currently, you can use the exact same script to control a chromecast on cordova and on desktop chrome. Which is super convenient.
(Aside from functions under chrome.cast.cordova, these functions supply operations that are impossible in the browser, and should be isolated to that).
Adding media.setPlaybackRate would break this.

This functionality is possible in chrome desktop via:

session.sendMessage(
    "urn:x-cast:com.google.cast.media", 
    {
        mediaSessionId: session.media[0].mediaSessionId, 
        requestId: 12345, 
        type: 'SET_PLAYBACK_RATE',
        playbackRate: 1
    }, 
    function () {
        console.log('success');
    }, function (err) {
        console.log(err);
    });

Actual Question

How do we handle this situation while inconveniencing the desktop/cdv devs minimally?

Options

Here's what I can think of so far:

  1. Drop media.setPlaybackRate and only use sendMessage. Add a documentation note on how to set playback rate with sendMessage.
    • Requires users to use a really ugly piece of code.
    • Requires ugly documentation.
    • (4th imo)
  2. Keep media.setPlaybackRate and add a note that this is a cordova exclusive function.
    • Requires desktop/cdv devs to write conditional code.
    • Requires some ugly documentation, but only targeted at desktop/cdv devs
    • (2nd imo)
  3. Rename it to chrome.cast.cordova.setPlaybackRate.
    • Doesn't fit with the rest of the cordova exclusive functions.
    • Is actually possible to do on browser, so shouldn't exactly be cordova exclusive.
    • Requires desktop/cdv devs to write conditional code.
    • Requires some special documentation.
    • (3rd imo)
  4. Make a file, "cast-convenience-functions.js" (or something), that has media.setPlaybackRate which just calls the built sendMessage function. Add a note for desktop-compatibly-hosted-web-app that they must include a copy of this file on their site to be able to use media.setPlaybackRate.
    • desktop/cdv devs have to add a file to their website
    • Requires documentation targeted at desktop/cdv devs
      • But will only need to add this note once eg. media.setPlaybackRate*, * points to note they need to add file
    • desktop/cdv devs will need to update the file if they update the plugin and want to use new convenience functions.
      • completely non-cordova devs can use this file as well
    • (best imo)

Conclusion

I like option 4 the best atm.

I would prefer to keep my chromecast controlling script as clean as possible. Currently, it doesn't even mention cordova once, I would like to keep it that way.

But I would like to hear any feedback about this issue/idea. Particularly from other desktop/cdv devs. But all welcome!

'@objc' method name provides one argument name, but method has 0 parameters

I had this same issue reported on my fork of the repo. I just tried running iOS myself as well (from jellyfin's master) and got the same error.

From the original issue post:

Build failed with this error.

'@objc' method name provides one argument name, but method has 0 parameters in Chromecast.swift

@objc(checkReceiverAvailable:)

@objc(checkReceiverAvailable:) 
func checkReceiverAvailable() {
   let sessionManager = GCKCastContext.sharedInstance().sessionManager

   if self.devicesAvailable.count > 0 || (sessionManager.currentSession != nil) {
       self.sendJavascript(jsCommand: "chrome.cast._.receiverAvailable()")
   } else {
       self.sendJavascript(jsCommand: "chrome.cast._.receiverUnavailable()")
   }

 }

I got this error while using cordova 9.0.0, ios platform 5.0.1, and xcode 10.2.2.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.