From e823585a7b2c828aafdeaeb03d588527e2b91a55 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Thu, 22 Oct 2020 21:53:45 -0600 Subject: [PATCH 01/35] [v1.1.0-dev] styling fixes --- README.md | 20 ++++++++++---------- doc/example.js | 26 ++++++++++++++++---------- package.json | 12 +++++++----- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 776105b..2214f35 100644 --- a/README.md +++ b/README.md @@ -46,16 +46,16 @@ The most significant usage difference between the [cast API](https://developers. In **Chrome desktop** you would do: ```js window['__onGCastApiAvailable'] = function(isAvailable, err) { - if (isAvailable) { - // start using the api! - } + if (isAvailable) { + // start using the api! + } }; ``` But in **cordova-plugin-chromecast** you do: ```js document.addEventListener("deviceready", function () { - // start using the api! + // start using the api! }); ``` @@ -103,11 +103,11 @@ To make your own **custom route selector** use this: ```js // This will begin an active scan for routes chrome.cast.cordova.scanForRoutes(function (routes) { - // Here is where you should update your route selector view with the current routes - // This will called each time the routes change - // routes is an array of "Route" objects (see below) + // Here is where you should update your route selector view with the current routes + // This will called each time the routes change + // routes is an array of "Route" objects (see below) }, function (err) { - // Will return with err.code === chrome.cast.ErrorCode.CANCEL when the scan has been ended + // Will return with err.code === chrome.cast.ErrorCode.CANCEL when the scan has been ended }); // When the user selects a route @@ -116,9 +116,9 @@ chrome.cast.cordova.stopScan(); // and use the selected route.id to join the route chrome.cast.cordova.selectRoute(route.id, function (session) { - // Save the session for your use + // Save the session for your use }, function (err) { - // Failed to connect to the route + // Failed to connect to the route }); ``` diff --git a/doc/example.js b/doc/example.js index 3b9a37e..49cfd24 100644 --- a/doc/example.js +++ b/doc/example.js @@ -1,6 +1,8 @@ -document.addEventListener("deviceready", function () { +document.addEventListener('deviceready', function () { // Must wait for deviceready before using chromecast + var chrome = window.chrome; + // File globals var _session; var _media; @@ -14,14 +16,14 @@ document.addEventListener("deviceready", function () { // The session listener is only called under the following conditions: // * will be called shortly chrome.cast.initialize is run // * if the device is already connected to a cast session - // Basically, this is what allows you to re-use the same cast session + // Basically, this is what allows you to re-use the same cast session // across different pages and after app restarts - }, function receiverListener (receiverAvailable) { + }, function receiverListener (receiverAvailable) { // receiverAvailable is a boolean. // True = at least one chromecast device is available // False = No chromecast devices available // You can use this to determine if you want to show your chromecast icon - }); + }); // initialize chromecast, this must be done before using other chromecast features chrome.cast.initialize(apiConfig, function () { @@ -30,22 +32,23 @@ document.addEventListener("deviceready", function () { requestSession(); }, function (err) { // Initialize failure + console.log(err); }); } - function requestSession () { - // This will open a native dialog that will let + // This will open a native dialog that will let // the user choose a chromecast to connect to // (Or will let you disconnect if you are already connected) chrome.cast.requestSession(function (session) { // Got a session! _session = session; - // Load a video + // Load a video loadMedia(); }, function (err) { // Failed, or if err is cancel, the dialog closed + console.log(err); }); } @@ -66,21 +69,23 @@ document.addEventListener("deviceready", function () { }, function (err) { // Failed (check that the video works in your browser) + console.log(err); }); } function pauseMedia () { _media.pause({}, function () { // Success - + // Wait a couple seconds setTimeout(function () { // stop the session stopSession(); - }, 2000) + }, 2000); }, function (err) { // Fail + console.log(err); }); } @@ -90,7 +95,8 @@ document.addEventListener("deviceready", function () { // Success }, function (err) { // Fail + console.log(err); }); } -}); \ No newline at end of file +}); diff --git a/package.json b/package.json index c272b70..7c999d9 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,13 @@ { "name": "cordova-plugin-chromecast", - "version": "1.0.0", + "version": "1.1.0-dev", "scripts": { "host-chrome-tests": "node tests/www/chrome/host-tests.js", - "style-fix-js": "node node_modules/eslint/bin/eslint --fix src && node node_modules/eslint/bin/eslint --fix www && node node_modules/eslint/bin/eslint --ignore-pattern tests/www/lib --fix tests/www", - "test": "node node_modules/eslint/bin/eslint src && node node_modules/eslint/bin/eslint www && node node_modules/eslint/bin/eslint --ignore-pattern tests/www/lib tests/www && node ./node_modules/java-checkstyle/bin/index.js ./src/android/ -c ./check_style.xml", - "style": "npm run style-fix-js && npm run test" + "style-js": "npx eslint --ignore-pattern tests/www/lib www tests/www doc/example.js", + "style-js-fix": "npx eslint --ignore-pattern tests/www/lib/ --fix www/ tests/www/ doc/example.js", + "style-java": "npx java-checkstyle -c ./check_style.xml ./src/android/", + "style": "npm run style-js-fix && npm run style-java", + "test": "npm run style-js && npm run style-java" }, "author": "", "license": "dual GPLv3/MPLv2", @@ -20,7 +22,7 @@ "eslint-plugin-promise": "~3.5.0", "eslint-plugin-standard": "~3.0.1", "express": "^4.17.1", - "java-checkstyle": "0.0.1", + "java-checkstyle": "0.1.0", "path": "^0.12.7" } } From 1e867599ff40ef7fe6f144d2b0ae0da1a6bad945 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sun, 25 Oct 2020 19:14:29 -0600 Subject: [PATCH 02/35] [issue #72] Documentation changes: - add an iOS troubleshooting tip - remove documentation for unimplemented `setCustomReceivers` - reformat supported API section - Add link to tests for example usage --- README.md | 85 ++++++++++++++++++------------ src/android/ChromecastSession.java | 7 ++- tests/www/js/tests_auto.js | 1 - www/chrome.cast.js | 10 ---- 4 files changed, 58 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 2214f35..2c7a57f 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,13 @@ cordova plugin add https://github.com/jellyfin/cordova-plugin-chromecast.git ``` +If you have trouble installing the pulgin or running the project for iOS, from `/platforms/ios/` try running: +```bash +sudo gem install cocoapods +pod repo update +pod install +``` + ### Additional iOS Installation Instructions To **distribute** an iOS app with this plugin you must add usage descriptions to your project's `config.xml`. These strings will be used when asking the user for permission to use the microphone and bluetooth. @@ -60,43 +67,53 @@ document.addEventListener("deviceready", function () { ``` -### Example +### Example Usage Here is a simple [example](doc/example.js) that loads a video, pauses it, and ends the session. +If you want more detailed code examples, please ctrl+f for the function of interest in [tests_auto.js](tests/www/js/tests_auto.js). +The other test files may contain code examples of interest as well: [[tests_manual_primary_1.js](tests/www/js/tests_manual_primary_1.js), [tests_manual_primary_2.js](tests/www/js/tests_manual_primary_2.js), [tests_manual_secondary.js](tests/www/js/tests_manual_secondary.js)] + ## API -Here are the support [Chromecast API]((https://developers.google.com/cast/docs/reference/chrome#chrome.cast)) methods. Any object types required by any of these methods are also supported. (eg. chrome.cast.ApiConfig) - -[chrome.cast.initialize](https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.initialize) -[chrome.cast.requestSession](https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.requestSession) -[chrome.cast.setCustomReceivers](https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.setCustomReceivers) -[chrome.cast.Session.setReceiverVolumeLevel](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#setReceiverVolumeLevel) -[chrome.cast.Session.setReceiverMuted](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#setReceiverMuted) -[chrome.cast.Session.stop](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#stop) -[chrome.cast.Session.leave](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#leave) -[chrome.cast.Session.sendMessage](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#sendMessage) -[chrome.cast.Session.loadMedia](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#loadMedia) -[chrome.cast.Session.queueLoad](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#queueLoad) -[chrome.cast.Session.addUpdateListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#addUpdateListener) -[chrome.cast.Session.removeUpdateListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#removeUpdateListener) -[chrome.cast.Session.addMessageListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#addMessageListener) -[chrome.cast.Session.removeMessageListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#removeMessageListener) -[chrome.cast.Session.addMediaListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#addMediaListener) -[chrome.cast.Session.removeMediaListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#removeMediaListener) -[chrome.cast.media.Media.play](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#play) -[chrome.cast.media.Media.pause](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#pause) -[chrome.cast.media.Media.seek](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#seek) -[chrome.cast.media.Media.stop](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#stop) -[chrome.cast.media.Media.setVolume](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#setVolume) -[chrome.cast.media.Media.supportsCommand](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#supportsCommand) -[chrome.cast.media.Media.getEstimatedTime](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#getEstimatedTime) -[chrome.cast.media.Media.editTracksInfo](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#editTracksInfo) -[chrome.cast.media.Media.queueJumpToItem](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#queueJumpToItem) -[chrome.cast.media.Media.addUpdateListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#addUpdateListener) -[chrome.cast.media.Media.removeUpdateListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#removeUpdateListener) +Here are the supported [Chromecast API]((https://developers.google.com/cast/docs/reference/chrome#chrome.cast)) methods. Any object types required by any of these methods are also supported. (eg. [chrome.cast.ApiConfig](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.ApiConfig)). You can search [chrome.cast.js](www/chrome.cast.js) to check if an API is supported. + +* [chrome.cast.initialize](https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.initialize) +* [chrome.cast.requestSession](https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.requestSession) + +[chrome.cast.Session](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session) +Most *Properties* Supported. +Supported *Methods*: +* [setReceiverVolumeLevel](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#setReceiverVolumeLevel) +* [setReceiverMuted](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#setReceiverMuted) +* [stop](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#stop) +* [leave](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#leave) +* [sendMessage](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#sendMessage) +* [loadMedia](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#loadMedia) +* [queueLoad](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#queueLoad) +* [addUpdateListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#addUpdateListener) +* [removeUpdateListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#removeUpdateListener) +* [addMessageListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#addMessageListener) +* [removeMessageListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#removeMessageListener) +* [addMediaListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#addMediaListener) +* [removeMediaListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#removeMediaListener) + +[chrome.cast.media.Media](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media) +Most *Properties* Supported. +Supported *Methods*: +* [play](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#play) +* [pause](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#pause) +* [seek](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#seek) +* [stop](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#stop) +* [setVolume](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#setVolume) +* [supportsCommand](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#supportsCommand) +* [getEstimatedTime](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#getEstimatedTime) +* [editTracksInfo](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#editTracksInfo) +* [queueJumpToItem](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#queueJumpToItem) +* [addUpdateListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#addUpdateListener) +* [removeUpdateListener](https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media.html#removeUpdateListener) ### Specific to this plugin -We have added some additional methods unique to this plugin. +We have added some additional methods that are unique to this plugin (that *do not* exist in the chrome cast API). They can all be found in the `chrome.cast.cordova` object. To make your own **custom route selector** use this: @@ -163,6 +180,7 @@ Run `npm test` to ensure your code fits the styling. It will also find some err * If errors are found, you can try running `npm run style`, this will attempt to automatically fix the errors. ### Tests Mobile + Requirements: * A chromecast device @@ -179,7 +197,8 @@ Manual tests: * Interaction between 2 devices connected to the same session * You will need to be able to run the tests from 2 different devices (preferred) or between a device and chrome desktop browser * To use the chrome desktop browser see [Tests Chrome](#tests-chrome) - + * [What a successful manual run looks like](https://github.com/jellyfin/cordova-plugin-chromecast/wiki/img/manual-tests-success.jpg) + [Why we chose a non-standard test framework](https://github.com/jellyfin/cordova-plugin-chromecast/issues/50) ### Tests Chrome @@ -189,7 +208,7 @@ They use the google provided cast_sender.js. These are particularly useful for ensuring we are following the [official Google Cast API for Chrome](https://developers.google.com/cast/docs/reference/chrome#chrome.cast) correctly. To run the tests: * run: `npm run host-chrome-tests [port default=8432]` -* Navigate to: `http://localhost:8432/chrome/tests_chrome.html` +* Navigate to: [http://localhost:8432/chrome/tests_chrome.html](http://localhost:8432/chrome/tests_chrome.html) ## Contributing diff --git a/src/android/ChromecastSession.java b/src/android/ChromecastSession.java index cdefe04..5c7a0d5 100644 --- a/src/android/ChromecastSession.java +++ b/src/android/ChromecastSession.java @@ -42,7 +42,12 @@ public class ChromecastSession { private boolean requestingMedia = false; /** 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. + * See https://github.com/jellyfin/cordova-plugin-chromecast/wiki/img/queueReloadCallback.jpg + * For how queueReloadCallback is used with multiple devices connected to the same session, and + * the primary device loads the media. + **/ private Runnable queueReloadCallback; /** Stores a callback that should be called when the queue status is updated. **/ private Runnable queueStatusUpdatedCallback; diff --git a/tests/www/js/tests_auto.js b/tests/www/js/tests_auto.js index 74ab8a0..f000b48 100644 --- a/tests/www/js/tests_auto.js +++ b/tests/www/js/tests_auto.js @@ -73,7 +73,6 @@ assert.exists(chrome.cast.media); assert.exists(chrome.cast.initialize); assert.exists(chrome.cast.requestSession); - assert.exists(chrome.cast.setCustomReceivers); assert.exists(chrome.cast.Session); assert.exists(chrome.cast.media.PlayerState); assert.exists(chrome.cast.media.ResumeState); diff --git a/www/chrome.cast.js b/www/chrome.cast.js index ec07f16..e0b5888 100644 --- a/www/chrome.cast.js +++ b/www/chrome.cast.js @@ -561,16 +561,6 @@ chrome.cast.requestSession = function (successCallback, errorCallback, opt_sessi }); }; -/** - * Sets custom receiver list - * @param {chrome.cast.Receiver[]} receivers The new list. Must not be null. - * @param {function} successCallback - * @param {function} errorCallback - */ -chrome.cast.setCustomReceivers = function (receivers, successCallback, errorCallback) { - // TODO: Implement -}; - /** * Describes the state of a currently running Cast application. Normally, these objects should not be created by the client. * @param {string} sessionId Uniquely identifies this instance of the receiver application. From 4e29fd2038e754c6d3eb422f1440ebcd6e1058f6 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sun, 25 Oct 2020 20:48:44 -0600 Subject: [PATCH 03/35] Fix mocha obscuring errors if they occur in a promise in a before/after --- tests/www/chrome/cordova_stubs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/www/chrome/cordova_stubs.js b/tests/www/chrome/cordova_stubs.js index 7f09260..77b4e59 100644 --- a/tests/www/chrome/cordova_stubs.js +++ b/tests/www/chrome/cordova_stubs.js @@ -125,7 +125,7 @@ // This makes it so that tests actually fail in the case of // uncaught exceptions inside promise catch blocks window.addEventListener('unhandledrejection', function (event) { - runner.fail(runner.test, event.reason); + runner.fail(runner.test || runner.currentRunnable, event.reason); }); } }; From d016d2ccd1280d9d56ecc5fece3d459f3c27d428 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sun, 25 Oct 2020 21:57:06 -0600 Subject: [PATCH 04/35] Tests - Fix tests for desktop chrome behavior: - Apparently `receiverListener` is required for ApiConfig now (tests_auto.js) - receiverListener also doesn't always give unavailable status before available anymore (tests_manual_primary_2.js) - Apparently it takes sometime for session.leave to take complete effect and cause the session to be removed from the browser's memory (tests_auto.js) - Add slightly more detailed instructions for desktop (tests_manual_primary_2.js) --- tests/www/js/tests_auto.js | 13 +++++++++--- tests/www/js/tests_manual_primary_1.js | 28 ++++++++++++++++++++------ tests/www/js/tests_manual_primary_2.js | 3 ++- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/tests/www/js/tests_auto.js b/tests/www/js/tests_auto.js index f000b48..c5a4d84 100644 --- a/tests/www/js/tests_auto.js +++ b/tests/www/js/tests_auto.js @@ -337,7 +337,14 @@ var called = utils.callOrder([ { id: success, repeats: false }, { id: update, repeats: true } - ], done); + ], function () { + // TODO chrome desktop bug 2020-10-25 - recheck later + // Need to give desktop chrome cast some time to fully disconnect, otherwise + // the next test fails because it receives a session on initialize + setTimeout(function () { + done(); + }, 1000); + }); session.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); if (session.status === chrome.cast.SessionStatus.DISCONNECTED) { @@ -354,7 +361,7 @@ it('initialize should not receive a session after session.leave', function (done) { var apiConfig = new chrome.cast.ApiConfig(new chrome.cast.SessionRequest(chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID), function sessionListener (session) { assert.fail('should not receive a session (we did sessionLeave so we shouldnt be able to auto rejoin rejoin)'); - }); + }, function receiverListener () {}); chrome.cast.initialize(apiConfig, function () { done(); }, function (err) { @@ -503,7 +510,7 @@ it('initialize should not receive a session after session.stop', function (done) { var apiConfig = new chrome.cast.ApiConfig(new chrome.cast.SessionRequest(chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID), function sessionListener (session) { assert.fail('should not receive a session (we did sessionStop so we shouldnt be able to auto rejoin rejoin)'); - }); + }, function receiverListener () {}); chrome.cast.initialize(apiConfig, function () { done(); }, function (err) { diff --git a/tests/www/js/tests_manual_primary_1.js b/tests/www/js/tests_manual_primary_1.js index 0d6a1be..6ec124d 100644 --- a/tests/www/js/tests_manual_primary_1.js +++ b/tests/www/js/tests_manual_primary_1.js @@ -410,6 +410,26 @@ function (sess) { session = sess; utils.testSessionProperties(sess); + // // Ensure the media is maintained + assert.isAbove(sess.media.length, 0); + media = sess.media[0]; + assert.isUndefined(media.queueData); + assert.equal(media.media.metadata.title, mediaInfo.metadata.title); + assert.equal(media.media.metadata.subtitle, mediaInfo.metadata.subtitle); + assert.equal(media.media.metadata.releaseDate, mediaInfo.metadata.releaseDate); + // TODO figure out how to maintain the data types for custom params on the native side + // so that we don't have to do turn each actual and expected into a string + assert.equal(media.media.metadata.someTrueBoolean + '', mediaInfo.metadata.someTrueBoolean + ''); + assert.equal(media.media.metadata.someFalseBoolean + '', mediaInfo.metadata.someFalseBoolean + ''); + assert.equal(media.media.metadata.someSmallNumber + '', mediaInfo.metadata.someSmallNumber + ''); + assert.equal(media.media.metadata.someLargeNumber + '', mediaInfo.metadata.someLargeNumber + ''); + assert.equal(media.media.metadata.someSmallDecimal + '', mediaInfo.metadata.someSmallDecimal + ''); + assert.equal(media.media.metadata.someLargeDecimal + '', mediaInfo.metadata.someLargeDecimal + ''); + assert.equal(media.media.metadata.someString, mediaInfo.metadata.someString); + assert.equal(media.media.metadata.images[0].url, mediaInfo.metadata.images[0].url); + assert.equal(media.media.metadata.metadataType, chrome.cast.media.MetadataType.GENERIC); + assert.equal(media.media.metadata.type, chrome.cast.media.MetadataType.GENERIC); + assert.equal(media.playerState, chrome.cast.media.PlayerState.PLAYING); called(session_listener); }, function receiverListener (availability) { if (!finished) { @@ -448,11 +468,9 @@ utils.setAction('Initializing...'); var finished = false; // Need this so we stop testing after being finished - var unavailable = 'unavailable'; var available = 'available'; var called = utils.callOrder([ { id: success, repeats: false }, - { id: unavailable, repeats: true }, { id: available, repeats: true } ], function () { finished = true; @@ -464,7 +482,7 @@ session = sess; assert.fail('Should not receive session on initialize. We should only call this initialize when there is no existing session.'); }, function receiverListener (availability) { - if (!finished) { + if (!finished && availability === available) { called(availability); } }, chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED); @@ -552,11 +570,9 @@ utils.setAction('Initializing...'); var finished = false; // Need this so we stop testing after being finished - var unavailable = 'unavailable'; var available = 'available'; var called = utils.callOrder([ { id: success, repeats: false }, - { id: unavailable, repeats: true }, { id: available, repeats: true } ], function () { finished = true; @@ -571,7 +587,7 @@ session = sess; assert.fail('Should not receive session on initialize. We should only call this initialize when there is no existing session.'); }, function receiverListener (availability) { - if (!finished) { + if (!finished && availability === available) { called(availability); } }, chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED); diff --git a/tests/www/js/tests_manual_primary_2.js b/tests/www/js/tests_manual_primary_2.js index ea8e123..a35709b 100644 --- a/tests/www/js/tests_manual_primary_2.js +++ b/tests/www/js/tests_manual_primary_2.js @@ -149,7 +149,8 @@ }); describe('session interaction with secondary', function () { it('Create session', function (done) { - utils.setAction('On secondary click "Start Part 2".', 'Enter Session', function () { + utils.setAction('On secondary click "Start Part 2".', + 'Enter Session' + (isDesktop? '
(On desktop, you must stop & start casting from the same cast pop up)' : ''), function () { utils.startSession(function (sess) { session = sess; utils.testSessionProperties(session); From 46d9bc0d716c0eba97cf6f5217f16759f1276ec3 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Mon, 26 Oct 2020 16:38:52 -0600 Subject: [PATCH 05/35] (test) WIP re-org test files: - unify html test files between desktop chrome and mobile via test_starter.js - prep for merging primary part 1 + 2 - prep for pulling out the manual solo tests --- package.json | 4 +- tests/www/chrome/cordova_stubs.js | 22 +------ tests/www/chrome/tests_auto_chrome.html | 42 -------------- tests/www/chrome/tests_chrome.html | 49 ---------------- .../chrome/tests_manual_primary_1_chrome.html | 50 ---------------- .../chrome/tests_manual_primary_2_chrome.html | 50 ---------------- .../chrome/tests_manual_secondary_chrome.html | 49 ---------------- tests/www/html/tests.html | 40 ++++++------- tests/www/html/tests_auto.html | 23 ++++---- ..._1.html => tests_interaction_primary.html} | 24 +++----- ....html => tests_interaction_secondary.html} | 24 +++----- ...anual_primary_2.html => tests_manual.html} | 24 +++----- tests/www/js/tests_auto.js | 2 +- ...mary_2.js => tests_interaction_primary.js} | 4 +- ...dary.js => tests_interaction_secondary.js} | 0 ...ts_manual_primary_1.js => tests_manual.js} | 0 .../{js => lib}/custom_mocha_html_reporter.js | 18 +++++- tests/www/lib/test_starter.js | 58 +++++++++++++++++++ tests/www/{js => lib}/utils.js | 0 tests/www/{lib => vendor}/chai.js | 0 tests/www/{lib => vendor}/mocha.css | 0 tests/www/{lib => vendor}/mocha.js | 0 tests/www/{lib => vendor}/readme.md | 0 23 files changed, 140 insertions(+), 343 deletions(-) delete mode 100644 tests/www/chrome/tests_auto_chrome.html delete mode 100644 tests/www/chrome/tests_chrome.html delete mode 100644 tests/www/chrome/tests_manual_primary_1_chrome.html delete mode 100644 tests/www/chrome/tests_manual_primary_2_chrome.html delete mode 100644 tests/www/chrome/tests_manual_secondary_chrome.html rename tests/www/html/{tests_manual_primary_1.html => tests_interaction_primary.html} (53%) rename tests/www/html/{tests_manual_secondary.html => tests_interaction_secondary.html} (54%) rename tests/www/html/{tests_manual_primary_2.html => tests_manual.html} (53%) rename tests/www/js/{tests_manual_primary_2.js => tests_interaction_primary.js} (98%) rename tests/www/js/{tests_manual_secondary.js => tests_interaction_secondary.js} (100%) rename tests/www/js/{tests_manual_primary_1.js => tests_manual.js} (100%) rename tests/www/{js => lib}/custom_mocha_html_reporter.js (73%) create mode 100644 tests/www/lib/test_starter.js rename tests/www/{js => lib}/utils.js (100%) rename tests/www/{lib => vendor}/chai.js (100%) rename tests/www/{lib => vendor}/mocha.css (100%) rename tests/www/{lib => vendor}/mocha.js (100%) rename tests/www/{lib => vendor}/readme.md (100%) diff --git a/package.json b/package.json index 7c999d9..cf8c2f2 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "version": "1.1.0-dev", "scripts": { "host-chrome-tests": "node tests/www/chrome/host-tests.js", - "style-js": "npx eslint --ignore-pattern tests/www/lib www tests/www doc/example.js", - "style-js-fix": "npx eslint --ignore-pattern tests/www/lib/ --fix www/ tests/www/ doc/example.js", + "style-js": "npx eslint --ignore-pattern tests/www/vendor www tests/www doc/example.js", + "style-js-fix": "npx eslint --ignore-pattern tests/www/vendor/ --fix www/ tests/www/ doc/example.js", "style-java": "npx java-checkstyle -c ./check_style.xml ./src/android/", "style": "npm run style-js-fix && npm run style-java", "test": "npm run style-js && npm run style-java" diff --git a/tests/www/chrome/cordova_stubs.js b/tests/www/chrome/cordova_stubs.js index 77b4e59..1435cb3 100644 --- a/tests/www/chrome/cordova_stubs.js +++ b/tests/www/chrome/cordova_stubs.js @@ -1,5 +1,5 @@ /** - * These stub plugin specific bahaviour so we can run the auto tests on chrome + * These stub plugin specific behaviour so we can run the auto tests on chrome * desktop browser. */ (function () { @@ -110,24 +110,4 @@ successCallback(['SETUP']); }; -/* ------------------------- Start Tests ---------------------------------- */ - - // This actually starts the tests - window['__onGCastApiAvailable'] = function (isAvailable, err) { - // If error, it is probably because we are not on chrome, so just disregard - if (isAvailable) { - var runner; - if (window['cordova-plugin-chromecast-tests'].runMocha) { - runner = window['cordova-plugin-chromecast-tests'].runMocha(); - } else { - runner = mocha.run(); - } - // This makes it so that tests actually fail in the case of - // uncaught exceptions inside promise catch blocks - window.addEventListener('unhandledrejection', function (event) { - runner.fail(runner.test || runner.currentRunnable, event.reason); - }); - } - }; - }()); diff --git a/tests/www/chrome/tests_auto_chrome.html b/tests/www/chrome/tests_auto_chrome.html deleted file mode 100644 index 6e64b09..0000000 --- a/tests/www/chrome/tests_auto_chrome.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - Cordova tests - - - - - - - - - - - - - -

Auto Tests

- - -
-

Action required:

-

Starting Tests...

- -
-
- - - - - diff --git a/tests/www/chrome/tests_chrome.html b/tests/www/chrome/tests_chrome.html deleted file mode 100644 index ce3d172..0000000 --- a/tests/www/chrome/tests_chrome.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - Cordova tests - - - - - - - - - - - -

cordova-plugin-chromecast Tests

-
-

Auto Tests should be run (and passing) before attempting the manual tests.

- - - -


- Manual Tests (Primary) Part 1 is the entry point for manual tests.
- You will require 2 devices or 1 device and a desktop chrome browser.
- (See readme for instructions on how to run tests from the desktop chrome browser.)

- Click Manual Tests (Primary) Part 1 and follow the directions carefully. -

- - - - -
- - diff --git a/tests/www/chrome/tests_manual_primary_1_chrome.html b/tests/www/chrome/tests_manual_primary_1_chrome.html deleted file mode 100644 index eeeea42..0000000 --- a/tests/www/chrome/tests_manual_primary_1_chrome.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - Cordova tests - - - - - - - - - - - - - -

Manual Tests (Primary Device) Part 1

- - - -
-

Action required:

-

Starting Tests...

- -
- - - -
- - - - - - diff --git a/tests/www/chrome/tests_manual_primary_2_chrome.html b/tests/www/chrome/tests_manual_primary_2_chrome.html deleted file mode 100644 index 32dced1..0000000 --- a/tests/www/chrome/tests_manual_primary_2_chrome.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - Cordova tests - - - - - - - - - - - - - -

Manual Tests (Primary Device) Part 2

- - - -
-

Action required:

-

Starting Tests...

- -
- - - -
- - - - - - diff --git a/tests/www/chrome/tests_manual_secondary_chrome.html b/tests/www/chrome/tests_manual_secondary_chrome.html deleted file mode 100644 index 63dc6ed..0000000 --- a/tests/www/chrome/tests_manual_secondary_chrome.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - Cordova tests - - - - - - - - - - - - - -

Manual Tests (Secondary Device)

- - -
-

Action required:

-

Preparing Secondary App...

- -
- - - -
- - - - - - diff --git a/tests/www/html/tests.html b/tests/www/html/tests.html index bfad128..e7a01b8 100644 --- a/tests/www/html/tests.html +++ b/tests/www/html/tests.html @@ -7,41 +7,41 @@ - +

cordova-plugin-chromecast Tests

-
-

Auto Tests should be run (and passing) before attempting the manual tests.

+
+

Auto Tests should be run (and passing) before attempting other tests.

- + + + -


- Manual Tests (Primary) Part 1 is the entry point for manual tests.
- You will require 2 devices or 1 device and a desktop chrome browser.
- (See readme for instructions on how to run tests from the desktop chrome browser.)

- Click Manual Tests (Primary) Part 1 and follow the directions carefully. -

- - -
diff --git a/tests/www/html/tests_auto.html b/tests/www/html/tests_auto.html index 3da9452..16d83a8 100644 --- a/tests/www/html/tests_auto.html +++ b/tests/www/html/tests_auto.html @@ -7,13 +7,12 @@ - + - - - - - + + + +

Auto Tests

@@ -30,12 +29,14 @@

Auto Tests

+
+

Action required:

+

None.

+ +
+
- + diff --git a/tests/www/html/tests_manual_primary_1.html b/tests/www/html/tests_interaction_primary.html similarity index 53% rename from tests/www/html/tests_manual_primary_1.html rename to tests/www/html/tests_interaction_primary.html index 5edf6a7..d01b521 100644 --- a/tests/www/html/tests_manual_primary_1.html +++ b/tests/www/html/tests_interaction_primary.html @@ -7,16 +7,15 @@ - + - - - - - + + + + -

Manual Tests (Primary Device) Part 1

+

Interaction Tests - Primary Device

- - - + + diff --git a/tests/www/html/tests_manual_secondary.html b/tests/www/html/tests_interaction_secondary.html similarity index 54% rename from tests/www/html/tests_manual_secondary.html rename to tests/www/html/tests_interaction_secondary.html index 3708dd0..7926d74 100644 --- a/tests/www/html/tests_manual_secondary.html +++ b/tests/www/html/tests_interaction_secondary.html @@ -7,23 +7,22 @@ - + - - - - - + + + + -

Manual Tests (Secondary Device)

+

Interaction Tests - Secondary Device

- - - + + diff --git a/tests/www/html/tests_manual_primary_2.html b/tests/www/html/tests_manual.html similarity index 53% rename from tests/www/html/tests_manual_primary_2.html rename to tests/www/html/tests_manual.html index 5f694c4..3921343 100644 --- a/tests/www/html/tests_manual_primary_2.html +++ b/tests/www/html/tests_manual.html @@ -7,16 +7,15 @@ - + - - - - - + + + + -

Manual Tests (Primary Device) Part 2

+

Manual Tests

- - - + + diff --git a/tests/www/js/tests_auto.js b/tests/www/js/tests_auto.js index c5a4d84..2489c10 100644 --- a/tests/www/js/tests_auto.js +++ b/tests/www/js/tests_auto.js @@ -339,7 +339,7 @@ { id: update, repeats: true } ], function () { // TODO chrome desktop bug 2020-10-25 - recheck later - // Need to give desktop chrome cast some time to fully disconnect, otherwise + // Need to give desktop chrome cast some time to fully disconnect, otherwise // the next test fails because it receives a session on initialize setTimeout(function () { done(); diff --git a/tests/www/js/tests_manual_primary_2.js b/tests/www/js/tests_interaction_primary.js similarity index 98% rename from tests/www/js/tests_manual_primary_2.js rename to tests/www/js/tests_interaction_primary.js index a35709b..02ea627 100644 --- a/tests/www/js/tests_manual_primary_2.js +++ b/tests/www/js/tests_interaction_primary.js @@ -149,8 +149,8 @@ }); describe('session interaction with secondary', function () { it('Create session', function (done) { - utils.setAction('On secondary click "Start Part 2".', - 'Enter Session' + (isDesktop? '
(On desktop, you must stop & start casting from the same cast pop up)' : ''), function () { + utils.setAction('On secondary click "Start Part 2".', + 'Enter Session' + (isDesktop ? '
(On desktop, you must stop & start casting from the same cast pop up)' : ''), function () { utils.startSession(function (sess) { session = sess; utils.testSessionProperties(session); diff --git a/tests/www/js/tests_manual_secondary.js b/tests/www/js/tests_interaction_secondary.js similarity index 100% rename from tests/www/js/tests_manual_secondary.js rename to tests/www/js/tests_interaction_secondary.js diff --git a/tests/www/js/tests_manual_primary_1.js b/tests/www/js/tests_manual.js similarity index 100% rename from tests/www/js/tests_manual_primary_1.js rename to tests/www/js/tests_manual.js diff --git a/tests/www/js/custom_mocha_html_reporter.js b/tests/www/lib/custom_mocha_html_reporter.js similarity index 73% rename from tests/www/js/custom_mocha_html_reporter.js rename to tests/www/lib/custom_mocha_html_reporter.js index 110879b..5d5ff0c 100644 --- a/tests/www/js/custom_mocha_html_reporter.js +++ b/tests/www/lib/custom_mocha_html_reporter.js @@ -6,6 +6,9 @@ 'use strict'; /* eslint-env mocha */ + var Mocha = window.Mocha; + var utils = window['cordova-plugin-chromecast-tests'].utils; + // Save htmlReporter reference var htmlReporter = mocha._reporter; @@ -13,7 +16,7 @@ // with linking to source for quick debugging in dev tools var myReporter = function (runner, options) { // Add the error listener - runner.on('fail', function (test, err) { + runner.on(Mocha.Runner.constants.EVENT_TEST_FAIL, function (test, err) { // Need to add the full code location path // so that the debugger can link to it @@ -42,6 +45,19 @@ // Log the error console.error(lines.join('\n')); }); + + // Custom suite end + runner.on(Mocha.Runner.constants.EVENT_SUITE_END, function (test, err) { + var passed = this.stats.passes === this.stats.tests; + if (passed) { + utils.setAction('All tests passed!'); + document.getElementById('action').style.backgroundColor = '#ceffc4'; + } else { + utils.setAction('Test failed. :('); + document.getElementById('action').style.backgroundColor = '#e28282'; + } + }); + // And return the default HTML reporter htmlReporter.call(this, runner, options); }; diff --git a/tests/www/lib/test_starter.js b/tests/www/lib/test_starter.js new file mode 100644 index 0000000..28385f8 --- /dev/null +++ b/tests/www/lib/test_starter.js @@ -0,0 +1,58 @@ +(function () { + 'use strict'; + + // This starts the tests for Android/iOS + document.addEventListener('deviceready', function () { + runTests(); + }); + + // This starts the tests for desktop chrome + window['__onGCastApiAvailable'] = function (isAvailable, err) { + // If error, it is probably because we are not on desktop chrome + if (err || !isAvailable) { + // So try loading mobile + return loadMobile(); + } + // Else we are likely on chrome desktop + if (isAvailable) { + addScriptToPage('../chrome/cordova_stubs.js'); + runTests(); + } + }; + + function loadMobile () { + addScriptToPage('../../../../cordova.js'); + } + + function runTests () { + var runner = window.mocha.run(); + // This makes it so that tests actually fail in the case of + // uncaught exceptions inside promise catch blocks + window.addEventListener('unhandledrejection', function (event) { + runner.fail(runner.test || runner.currentRunnable, event.reason); + }); + } + + // Url should match below if we are testing on mobile + if (window.location.href.match(/plugins\/cordova-plugin-chromecast/)) { + loadMobile(); + } else { + // Assume we are on desktop and attempt to load the cast library + addScriptToPage( + 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=0', + function onerror () { + // If failed to load, we are probably on mobile + loadMobile(); + }); + } + + function addScriptToPage (src, errorCallback) { + var s = document.createElement('script'); + if (errorCallback) { + s.onerror = errorCallback; + } + s.setAttribute('src', src); + document.body.appendChild(s); + } + +}()); diff --git a/tests/www/js/utils.js b/tests/www/lib/utils.js similarity index 100% rename from tests/www/js/utils.js rename to tests/www/lib/utils.js diff --git a/tests/www/lib/chai.js b/tests/www/vendor/chai.js similarity index 100% rename from tests/www/lib/chai.js rename to tests/www/vendor/chai.js diff --git a/tests/www/lib/mocha.css b/tests/www/vendor/mocha.css similarity index 100% rename from tests/www/lib/mocha.css rename to tests/www/vendor/mocha.css diff --git a/tests/www/lib/mocha.js b/tests/www/vendor/mocha.js similarity index 100% rename from tests/www/lib/mocha.js rename to tests/www/vendor/mocha.js diff --git a/tests/www/lib/readme.md b/tests/www/vendor/readme.md similarity index 100% rename from tests/www/lib/readme.md rename to tests/www/vendor/readme.md From 581ddbb11f8bbc2026aab07766a8b116e76bc26d Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Mon, 26 Oct 2020 21:16:55 -0600 Subject: [PATCH 06/35] (test) WIP Move promary interaction tests from tests_manual.js to tests_interaction_primary.js --- tests/www/js/tests_interaction_primary.js | 187 +++++++++++++++++++- tests/www/js/tests_manual.js | 203 +--------------------- 2 files changed, 188 insertions(+), 202 deletions(-) diff --git a/tests/www/js/tests_interaction_primary.js b/tests/www/js/tests_interaction_primary.js index 02ea627..8c70405 100644 --- a/tests/www/js/tests_interaction_primary.js +++ b/tests/www/js/tests_interaction_primary.js @@ -26,7 +26,7 @@ timeout: 180000 }); - describe('Manual Tests - Primary Device - Part 2', function () { + describe('Interaction Tests - Primary Device', function () { // callOrder constants that are re-used frequently var success = 'success'; var session; @@ -41,6 +41,191 @@ } }, 100); }); + describe('External Sender Sends Commands', function () { + before('ensure initialized', function (done) { + this.timeout(15000); + utils.setAction('Initializing...'); + + var finished = false; // Need this so we stop testing after being finished + var available = 'available'; + var called = utils.callOrder([ + { id: success, repeats: false }, + { id: available, repeats: true } + ], function () { + finished = true; + if (session) { + assert.equal(session.status, chrome.cast.SessionStatus.STOPPED); + } + done(); + }); + var apiConfig = new chrome.cast.ApiConfig( + new chrome.cast.SessionRequest(chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID), + function (sess) { + session = sess; + assert.fail('Should not receive session on initialize. We should only call this initialize when there is no existing session.'); + }, function receiverListener (availability) { + if (!finished && availability === available) { + called(availability); + } + }, chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED); + chrome.cast.initialize(apiConfig, function () { + called(success); + }, function (err) { + assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); + }); + }); + it('Join external session', function (done) { + if (isDesktop) { + // This is a hack because desktop chrome is incapable of + // joining a session. So we have to create the session + // from chrome first and then join from the app. + return utils.startSession(function (sess) { + session = sess; + showInstructions(done); + }); + } + // Else + showInstructions(function () { + utils.startSession(function (sess) { + session = sess; + utils.testSessionProperties(session); + done(); + }); + }); + function showInstructions (callback) { + utils.setAction('Ensure you have only 1 physical chromecast device on your network (castGroups are fine).
' + + '
1. On a secondary device (or desktop chrome browser),' + + ' navigate to Manual Tests (Secondary)
' + + '2. Follow instructions on secondary app.', + 'Continue', + function () { + callback(); + }); + } + }); + it('External loadMedia should trigger mediaListener', function (done) { + utils.setAction('On secondary click "Load Media"'); + var finished = false; + session.addMediaListener(function listener (m) { + if (finished) { + return; + } + utils.setAction('Tests running...'); + media = m; + var interval = setInterval(function () { + if (media.media.tracks != null && media.media.tracks !== undefined) { + clearInterval(interval); + utils.testMediaProperties(media); + finished = true; + done(); + } + }, 400); + }); + }); + it('External media stop should trigger media updateListener', function (done) { + utils.setAction('On secondary click "Stop Media"'); + media.addUpdateListener(function listener (isAlive) { + if (media.playerState === chrome.cast.media.PlayerState.IDLE) { + media.removeUpdateListener(listener); + assert.equal(media.idleReason, chrome.cast.media.IdleReason.CANCELLED); + assert.isFalse(isAlive); + done(); + } + }); + }); + it('External queueLoad should trigger mediaListener', function (done) { + utils.setAction('On secondary click "Load Queue"'); + var finished = false; + session.addMediaListener(function listener (m) { + if (finished) { + return; + } + finished = true; + media = m; + var interval = setInterval(function () { + if (media.currentItemId > -1 && media.media.tracks) { + clearInterval(interval); + finished = true; + utils.testMediaProperties(media); + var items = media.items; + var startTime = 40; + assert.isTrue(items[0].autoplay); + assert.equal(items[0].startTime, startTime); + assert.equal(items[0].media.contentId, videoUrl); + assert.isTrue(items[1].autoplay); + assert.equal(items[1].startTime, startTime * 2); + assert.equal(items[1].media.contentId, audioUrl); + done(); + } + }, 400); + }); + }); + it('Jump to different queue item should trigger media.addUpdateListener and not session.addMediaListener', function (done) { + utils.setAction('On secondary click "Queue Jump"'); + var called = utils.callOrder([ + { id: stopped, repeats: true }, + { id: update, repeats: true } + ], done); + var currentItemId = media.currentItemId; + var mediaListener = function (media) { + assert.fail('session.addMediaListener should only be called when an external sender loads media. ' + + '(We are the one loading. We are not external to ourself.'); + }; + session.addMediaListener(mediaListener); + media.addUpdateListener(function listener (isAlive) { + assert.isTrue(isAlive); + if (media.playerState === chrome.cast.media.PlayerState.IDLE) { + assert.oneOf(media.idleReason, + [chrome.cast.media.IdleReason.INTERRUPTED, chrome.cast.media.IdleReason.FINISHED]); + called(stopped); + } + if (media.currentItemId !== currentItemId) { + session.removeMediaListener(mediaListener); + media.removeUpdateListener(listener); + utils.testMediaProperties(media); + called(update); + } + }); + }); + it('session.leave should leave the session', function (done) { + utils.setAction('Follow instructions on secondary.', 'Leave Session', function () { + // Set up the expected calls + var called = utils.callOrder([ + { id: success, repeats: false }, + { id: update, repeats: true } + ], function () { + utils.setAction('1. On secondary, click "Check Session"
2. Then follow directions on secondary!'); + done(); + }); + var finished = false; + session.addUpdateListener(function listener (isAlive) { + if (finished) { + return; + } + assert.isTrue(isAlive); + if (session.status === chrome.cast.SessionStatus.DISCONNECTED) { + finished = true; + called(update); + } + }); + session.leave(function () { + called(success); + }, function (err) { + assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); + }); + }); + }); + after('Ensure we have left the session', function (done) { + if (!session) { + return done(); + } + session.leave(function () { + done(); + }, function () { + done(); + }); + }); + }); describe('App restart and reload/change page simulation', function () { var cookieName = 'primary-p2_restart-reload'; var runningNum = parseInt(utils.getValue(cookieName) || '0'); diff --git a/tests/www/js/tests_manual.js b/tests/www/js/tests_manual.js index 6ec124d..f993fae 100644 --- a/tests/www/js/tests_manual.js +++ b/tests/www/js/tests_manual.js @@ -26,10 +26,9 @@ timeout: 180000 }); - describe('Manual Tests - Primary Device - Part 1', function () { + describe('Manual Tests', function () { var imageUrl = 'https://ia800705.us.archive.org/1/items/GoodHousekeeping193810/Good%20Housekeeping%201938-10.jpg'; var videoUrl = 'https://ia801302.us.archive.org/1/items/TheWater_201510/TheWater.mp4'; - var audioUrl = 'https://ia800306.us.archive.org/26/items/1939RadioNews/1939-10-24-CBS-Elmer-Davis-Reports-City-Of-Flint-Still-Missing.mp3'; // callOrder constants that are re-used frequently var success = 'success'; @@ -279,7 +278,7 @@ window.location.reload(); } this.timeout(0); // no timeout - utils.setAction('Force kill and restart the app, and navigate back to Manual Tests (Primary) Part 1.' + utils.setAction('Force kill and restart the app, and navigate back to Manual Tests.' + '
Note: Android 4.4 does not support this feature, so just refresh the page.'); break; case testNum: @@ -564,204 +563,6 @@ }); }); - describe('External Sender Sends Commands', function () { - before('ensure initialized', function (done) { - this.timeout(15000); - utils.setAction('Initializing...'); - - var finished = false; // Need this so we stop testing after being finished - var available = 'available'; - var called = utils.callOrder([ - { id: success, repeats: false }, - { id: available, repeats: true } - ], function () { - finished = true; - if (session) { - assert.equal(session.status, chrome.cast.SessionStatus.STOPPED); - } - done(); - }); - var apiConfig = new chrome.cast.ApiConfig( - new chrome.cast.SessionRequest(chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID), - function (sess) { - session = sess; - assert.fail('Should not receive session on initialize. We should only call this initialize when there is no existing session.'); - }, function receiverListener (availability) { - if (!finished && availability === available) { - called(availability); - } - }, chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED); - chrome.cast.initialize(apiConfig, function () { - called(success); - }, function (err) { - assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); - }); - }); - it('Join external session', function (done) { - if (isDesktop) { - // This is a hack because desktop chrome is incapable of - // joining a session. So we have to create the session - // from chrome first and then join from the app. - return utils.startSession(function (sess) { - session = sess; - showInstructions(done); - }); - } - // Else - showInstructions(function () { - utils.startSession(function (sess) { - session = sess; - utils.testSessionProperties(session); - done(); - }); - }); - function showInstructions (callback) { - utils.setAction('Ensure you have only 1 physical chromecast device on your network (castGroups are fine).
' - + '
1. On a secondary device (or desktop chrome browser),' - + ' navigate to Manual Tests (Secondary)
' - + '2. Follow instructions on secondary app.', - 'Continue', - function () { - callback(); - }); - } - }); - it('External loadMedia should trigger mediaListener', function (done) { - utils.setAction('On secondary click "Load Media"'); - var finished = false; - session.addMediaListener(function listener (m) { - if (finished) { - return; - } - utils.setAction('Tests running...'); - media = m; - var interval = setInterval(function () { - if (media.media.tracks != null && media.media.tracks !== undefined) { - clearInterval(interval); - utils.testMediaProperties(media); - finished = true; - done(); - } - }, 400); - }); - }); - it('External media stop should trigger media updateListener', function (done) { - utils.setAction('On secondary click "Stop Media"'); - media.addUpdateListener(function listener (isAlive) { - if (media.playerState === chrome.cast.media.PlayerState.IDLE) { - media.removeUpdateListener(listener); - assert.equal(media.idleReason, chrome.cast.media.IdleReason.CANCELLED); - assert.isFalse(isAlive); - done(); - } - }); - }); - it('External queueLoad should trigger mediaListener', function (done) { - utils.setAction('On secondary click "Load Queue"'); - var finished = false; - session.addMediaListener(function listener (m) { - if (finished) { - return; - } - finished = true; - media = m; - var interval = setInterval(function () { - if (media.currentItemId > -1 && media.media.tracks) { - clearInterval(interval); - finished = true; - utils.testMediaProperties(media); - var items = media.items; - var startTime = 40; - assert.isTrue(items[0].autoplay); - assert.equal(items[0].startTime, startTime); - assert.equal(items[0].media.contentId, videoUrl); - assert.isTrue(items[1].autoplay); - assert.equal(items[1].startTime, startTime * 2); - assert.equal(items[1].media.contentId, audioUrl); - done(); - } - }, 400); - }); - }); - it('Jump to different queue item should trigger media.addUpdateListener and not session.addMediaListener', function (done) { - utils.setAction('On secondary click "Queue Jump"'); - var called = utils.callOrder([ - { id: stopped, repeats: true }, - { id: update, repeats: true } - ], done); - var currentItemId = media.currentItemId; - var mediaListener = function (media) { - assert.fail('session.addMediaListener should only be called when an external sender loads media. ' - + '(We are the one loading. We are not external to ourself.'); - }; - session.addMediaListener(mediaListener); - media.addUpdateListener(function listener (isAlive) { - assert.isTrue(isAlive); - if (media.playerState === chrome.cast.media.PlayerState.IDLE) { - assert.oneOf(media.idleReason, - [chrome.cast.media.IdleReason.INTERRUPTED, chrome.cast.media.IdleReason.FINISHED]); - called(stopped); - } - if (media.currentItemId !== currentItemId) { - session.removeMediaListener(mediaListener); - media.removeUpdateListener(listener); - utils.testMediaProperties(media); - called(update); - } - }); - }); - it('session.leave should leave the session', function (done) { - utils.setAction('Follow instructions on secondary.', 'Leave Session', function () { - // Set up the expected calls - var called = utils.callOrder([ - { id: success, repeats: false }, - { id: update, repeats: true } - ], function () { - done(); - }); - var finished = false; - session.addUpdateListener(function listener (isAlive) { - if (finished) { - return; - } - assert.isTrue(isAlive); - if (session.status === chrome.cast.SessionStatus.DISCONNECTED) { - finished = true; - called(update); - } - }); - session.leave(function () { - called(success); - }, function (err) { - assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); - }); - }); - }); - after('Ensure we have left the session', function (done) { - if (!session) { - return done(); - } - session.leave(function () { - done(); - }, function () { - done(); - }); - }); - }); - }); - window['cordova-plugin-chromecast-tests'] = window['cordova-plugin-chromecast-tests'] || {}; - window['cordova-plugin-chromecast-tests'].runMocha = function () { - var runner = mocha.run(); - runner.on('suite end', function (suite) { - var passed = this.stats.passes === runner.total; - if (passed) { - utils.setAction('1. On secondary, click "Check Session"
Then follow directions on secondary!'); - document.getElementById('action').style.backgroundColor = '#ceffc4'; - } - }); - return runner; - }; - }()); From 8468f6e7e4bd4615fb08a9dbb3367aae1517e560 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Mon, 26 Oct 2020 21:23:46 -0600 Subject: [PATCH 07/35] (test) WIP - Fix custom_mocha_html_reporter.js test suite end success/failure detection --- tests/www/lib/custom_mocha_html_reporter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/www/lib/custom_mocha_html_reporter.js b/tests/www/lib/custom_mocha_html_reporter.js index 5d5ff0c..a7d9eb6 100644 --- a/tests/www/lib/custom_mocha_html_reporter.js +++ b/tests/www/lib/custom_mocha_html_reporter.js @@ -48,11 +48,11 @@ // Custom suite end runner.on(Mocha.Runner.constants.EVENT_SUITE_END, function (test, err) { - var passed = this.stats.passes === this.stats.tests; + var passed = this.stats.passes === this.total; if (passed) { utils.setAction('All tests passed!'); document.getElementById('action').style.backgroundColor = '#ceffc4'; - } else { + } else if (this.stats.failures) { utils.setAction('Test failed. :('); document.getElementById('action').style.backgroundColor = '#e28282'; } From 28d8c50295ef8c6b704d2099e2c30cf2dc422700 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Mon, 26 Oct 2020 22:07:00 -0600 Subject: [PATCH 08/35] (test) WIP - Fix isDesktop, needs to be dynamically retrieved since it is determined later in runtime now --- tests/www/js/tests_interaction_primary.js | 11 ++++---- tests/www/js/tests_interaction_secondary.js | 3 +-- tests/www/js/tests_manual.js | 5 ++-- tests/www/lib/test_starter.js | 29 ++++++++++++--------- tests/www/lib/utils.js | 4 +++ 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/tests/www/js/tests_interaction_primary.js b/tests/www/js/tests_interaction_primary.js index 8c70405..a5b7bc6 100644 --- a/tests/www/js/tests_interaction_primary.js +++ b/tests/www/js/tests_interaction_primary.js @@ -15,7 +15,6 @@ var assert = window.chai.assert; var utils = window['cordova-plugin-chromecast-tests'].utils; - var isDesktop = window['cordova-plugin-chromecast-tests'].isDesktop || false; mocha.setup({ bail: true, @@ -75,7 +74,7 @@ }); }); it('Join external session', function (done) { - if (isDesktop) { + if (utils.isDesktop()) { // This is a hack because desktop chrome is incapable of // joining a session. So we have to create the session // from chrome first and then join from the app. @@ -255,7 +254,7 @@ new chrome.cast.SessionRequest(chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID), function (sess) { session = sess; - if (!isDesktop) { + if (!utils.isDesktop()) { assert.fail('should not receive a session (make sure there is no active cast session when starting the tests)'); } }, function receiverListener (availability) { @@ -277,7 +276,7 @@ case instructionNum: // Show instructions for app restart utils.storeValue(cookieName, testNum); - if (isDesktop) { + if (utils.isDesktop()) { // If desktop, just reload the page (because restart doesn't work) window.location.reload(); } @@ -308,7 +307,7 @@ new chrome.cast.SessionRequest(chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID), function (sess) { session = sess; - if (!isDesktop) { + if (!utils.isDesktop()) { assert.fail('should not receive a session (make sure there is no active cast session when starting the tests)'); } }, function receiverListener (availability) { @@ -335,7 +334,7 @@ describe('session interaction with secondary', function () { it('Create session', function (done) { utils.setAction('On secondary click "Start Part 2".', - 'Enter Session' + (isDesktop ? '
(On desktop, you must stop & start casting from the same cast pop up)' : ''), function () { + 'Enter Session' + (utils.isDesktop() ? '
(On desktop, you must stop & start casting from the same cast pop up)' : ''), function () { utils.startSession(function (sess) { session = sess; utils.testSessionProperties(session); diff --git a/tests/www/js/tests_interaction_secondary.js b/tests/www/js/tests_interaction_secondary.js index c983fb8..5ed05e4 100644 --- a/tests/www/js/tests_interaction_secondary.js +++ b/tests/www/js/tests_interaction_secondary.js @@ -15,7 +15,6 @@ var assert = window.chai.assert; var utils = window['cordova-plugin-chromecast-tests'].utils; - var isDesktop = window['cordova-plugin-chromecast-tests'].isDesktop || false; mocha.setup({ bail: true, @@ -344,7 +343,7 @@ }); }); it('Join session', function (done) { - if (isDesktop) { + if (utils.isDesktop()) { // This is a hack because desktop chrome is incapable of // joining a session. So we have to create the session // from chrome first and then join from the app. diff --git a/tests/www/js/tests_manual.js b/tests/www/js/tests_manual.js index f993fae..4b3b8a8 100644 --- a/tests/www/js/tests_manual.js +++ b/tests/www/js/tests_manual.js @@ -15,7 +15,6 @@ var assert = window.chai.assert; var utils = window['cordova-plugin-chromecast-tests'].utils; - var isDesktop = window['cordova-plugin-chromecast-tests'].isDesktop || false; mocha.setup({ bail: true, @@ -273,7 +272,7 @@ case instructionNum: // Show instructions for app restart utils.storeValue(cookieName, testNum); - if (isDesktop) { + if (utils.isDesktop()) { // If desktop, just reload the page (because restart doesn't work) window.location.reload(); } @@ -538,7 +537,7 @@ } }); utils.setAction('1. Click "Open Dialog".
2. Select "Stop Casting" in the stop casting dialog.' - + (isDesktop ? '
3. Click outside of the stop casting dialog to dismiss it.' : ''), + + (utils.isDesktop() ? '
3. Click outside of the stop casting dialog to dismiss it.' : ''), 'Open Dialog', function () { chrome.cast.requestSession(function (session) { diff --git a/tests/www/lib/test_starter.js b/tests/www/lib/test_starter.js index 28385f8..80e0dc7 100644 --- a/tests/www/lib/test_starter.js +++ b/tests/www/lib/test_starter.js @@ -20,18 +20,8 @@ } }; - function loadMobile () { - addScriptToPage('../../../../cordova.js'); - } - - function runTests () { - var runner = window.mocha.run(); - // This makes it so that tests actually fail in the case of - // uncaught exceptions inside promise catch blocks - window.addEventListener('unhandledrejection', function (event) { - runner.fail(runner.test || runner.currentRunnable, event.reason); - }); - } + // Assume we are on Desktop to start + window['cordova-plugin-chromecast-tests'].isDesktop = true; // Url should match below if we are testing on mobile if (window.location.href.match(/plugins\/cordova-plugin-chromecast/)) { @@ -46,6 +36,21 @@ }); } + function loadMobile () { + // The assumption that we were on desktop chrome was wrong apparently + window['cordova-plugin-chromecast-tests'].isDesktop = false; + addScriptToPage('../../../../cordova.js'); + } + + function runTests () { + var runner = window.mocha.run(); + // This makes it so that tests actually fail in the case of + // uncaught exceptions inside promise catch blocks + window.addEventListener('unhandledrejection', function (event) { + runner.fail(runner.test || runner.currentRunnable, event.reason); + }); + } + function addScriptToPage (src, errorCallback) { var s = document.createElement('script'); if (errorCallback) { diff --git a/tests/www/lib/utils.js b/tests/www/lib/utils.js index 6cafdc7..d8bb53e 100644 --- a/tests/www/lib/utils.js +++ b/tests/www/lib/utils.js @@ -31,6 +31,10 @@ localStorage.clear(); }; + utils.isDesktop = function () { + return window['cordova-plugin-chromecast-tests'].isDesktop || false; + }; + /** * Displays the action information. */ From 659ea4328785e48852c6911e14079899bab408d4 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Mon, 26 Oct 2020 22:27:06 -0600 Subject: [PATCH 09/35] (test) Finish making the re-organized Interaction Tests work together --- tests/www/js/tests_interaction_primary.js | 65 +++++++++++---------- tests/www/js/tests_interaction_secondary.js | 11 ++-- tests/www/lib/custom_mocha_html_reporter.js | 3 +- 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/tests/www/js/tests_interaction_primary.js b/tests/www/js/tests_interaction_primary.js index a5b7bc6..da4a6bd 100644 --- a/tests/www/js/tests_interaction_primary.js +++ b/tests/www/js/tests_interaction_primary.js @@ -26,9 +26,19 @@ }); describe('Interaction Tests - Primary Device', function () { + var videoUrl = 'https://ia801302.us.archive.org/1/items/TheWater_201510/TheWater.mp4'; + var audioUrl = 'https://ia800306.us.archive.org/26/items/1939RadioNews/1939-10-24-CBS-Elmer-Davis-Reports-City-Of-Flint-Still-Missing.mp3'; + // callOrder constants that are re-used frequently var success = 'success'; + var stopped = 'stopped'; + var update = 'update'; + var session; + var media; + + var cookieName = 'primary-p2_restart-reload'; + var runningNum = parseInt(utils.getValue(cookieName) || '-1'); before('Api should be available and initialize successfully', function (done) { this.timeout(15000); @@ -41,6 +51,12 @@ }, 100); }); describe('External Sender Sends Commands', function () { + before('already passed all tests in this section', function () { + // Have we already passed the tests in this describe and should skip? + if (runningNum > -1) { + this.skip(); + } + }); before('ensure initialized', function (done) { this.timeout(15000); utils.setAction('Initializing...'); @@ -94,9 +110,9 @@ function showInstructions (callback) { utils.setAction('Ensure you have only 1 physical chromecast device on your network (castGroups are fine).
' + '
1. On a secondary device (or desktop chrome browser),' - + ' navigate to Manual Tests (Secondary)
' - + '2. Follow instructions on secondary app.', - 'Continue', + + ' navigate to Interaction Tests - Secondary Device
' + + '2. Follow instructions on secondary device.', + 'Listen for External Load Media', function () { callback(); }); @@ -193,8 +209,12 @@ { id: success, repeats: false }, { id: update, repeats: true } ], function () { - utils.setAction('1. On secondary, click "Check Session"
2. Then follow directions on secondary!'); - done(); + utils.setAction('1. On secondary, click "Check Session"
' + + '2. Then follow directions on secondary!', 'Page Reload', function () { + utils.storeValue(cookieName, 0); + window.location.href = window.location.href; + done(); + }); }); var finished = false; session.addUpdateListener(function listener (isAlive) { @@ -226,8 +246,6 @@ }); }); describe('App restart and reload/change page simulation', function () { - var cookieName = 'primary-p2_restart-reload'; - var runningNum = parseInt(utils.getValue(cookieName) || '0'); it('Should not receive a session on initialize after a page change', function (done) { this.timeout(15000); if (runningNum > 0) { @@ -236,11 +254,9 @@ } utils.setAction('Checking for session after page load, (should not find session)...'); var finished = false; // Need this so we stop testing after being finished - var unavailable = 'unavailable'; var available = 'available'; var called = utils.callOrder([ { id: success, repeats: false }, - { id: unavailable, repeats: true }, { id: available, repeats: true } ], function () { finished = true; @@ -258,7 +274,7 @@ assert.fail('should not receive a session (make sure there is no active cast session when starting the tests)'); } }, function receiverListener (availability) { - if (!finished) { + if (!finished && availability === available) { called(availability); } }, chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED); @@ -289,11 +305,9 @@ // Test initialize since we just reloaded utils.setAction('Checking for session after app restart, (should not find session)...'); var finished = false; // Need this so we stop testing after being finished - var unavailable = 'unavailable'; var available = 'available'; var called = utils.callOrder([ { id: success, repeats: false }, - { id: unavailable, repeats: true }, { id: available, repeats: true } ], function () { finished = true; @@ -311,7 +325,7 @@ assert.fail('should not receive a session (make sure there is no active cast session when starting the tests)'); } }, function receiverListener (availability) { - if (!finished) { + if (!finished && availability === available) { called(availability); } }, chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED); @@ -327,18 +341,20 @@ } }); after(function () { - // Reset tests - utils.storeValue(cookieName, 0); + // Reset runningNum as we are done with it + utils.clearStoredValues(); }); }); describe('session interaction with secondary', function () { it('Create session', function (done) { - utils.setAction('On secondary click "Start Part 2".', - 'Enter Session' + (utils.isDesktop() ? '
(On desktop, you must stop & start casting from the same cast pop up)' : ''), function () { + utils.setAction('On secondary click "Leave Session".', + 'Enter Session' + + (utils.isDesktop() ? '
(Desktop: Stop & Start casting from the same cast pop up)' : ''), + function () { utils.startSession(function (sess) { session = sess; utils.testSessionProperties(session); - utils.setAction('On secondary click "Continue".'); + utils.setAction('On secondary click "Join/Start Session".'); done(); }); }); @@ -366,17 +382,4 @@ }); - window['cordova-plugin-chromecast-tests'] = window['cordova-plugin-chromecast-tests'] || {}; - window['cordova-plugin-chromecast-tests'].runMocha = function () { - var runner = mocha.run(); - runner.on('suite end', function (suite) { - var passed = this.stats.passes === runner.total; - if (passed) { - utils.setAction('All manual tests passed! [Assuming you did Part 1 as well :) ]'); - document.getElementById('action').style.backgroundColor = '#ceffc4'; - } - }); - return runner; - }; - }()); diff --git a/tests/www/js/tests_interaction_secondary.js b/tests/www/js/tests_interaction_secondary.js index 5ed05e4..10212dc 100644 --- a/tests/www/js/tests_interaction_secondary.js +++ b/tests/www/js/tests_interaction_secondary.js @@ -124,7 +124,7 @@ }); }); it('session.loadMedia should be able to load a remote video and handle GenericMediaMetadata', function (done) { - utils.setAction('On primary click "Continue"', 'Load Media', function () { + utils.setAction('On primary click "Listen for External Load Media"', 'Load Media', function () { var mediaInfo = new chrome.cast.media.MediaInfo(videoUrl, 'video/mp4'); mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata(); mediaInfo.metadata.title = 'DaTitle'; @@ -320,9 +320,8 @@ }); it('Primary should not receive session on initialize', function (done) { this.timeout(240000); - utils.setAction('1. On primary, click "Back".' - + '
2. On primary, Select Manual Tests (Primary) Part 2.' - + '
3. Wait for instructions from primary.', 'Start Part 2', done); + utils.setAction('1. On primary, click "Page Reload".' + + '
2. Wait for instructions from primary.', 'Leave Session', done); }); it('Secondary session.leave should cause session to end (because all senders have left)', function (done) { var called = utils.waitForAllCalls([ @@ -349,11 +348,11 @@ // from chrome first and then join from the app. utils.startSession(function (sess) { session = sess; - utils.setAction('1. On primary click "Enter Session"
2. Wait for instructions from primary.', 'Continue', done); + utils.setAction('1. On primary click "Enter Session"
2. Wait for instructions from primary.', 'Join/Start Session', done); }); return; } - utils.setAction('On primary click "Enter Session"', 'Continue', function () { + utils.setAction('On primary click "Enter Session"', 'Join/Start Session', function () { utils.startSession(function (sess) { session = sess; done(); diff --git a/tests/www/lib/custom_mocha_html_reporter.js b/tests/www/lib/custom_mocha_html_reporter.js index a7d9eb6..015e418 100644 --- a/tests/www/lib/custom_mocha_html_reporter.js +++ b/tests/www/lib/custom_mocha_html_reporter.js @@ -48,7 +48,8 @@ // Custom suite end runner.on(Mocha.Runner.constants.EVENT_SUITE_END, function (test, err) { - var passed = this.stats.passes === this.total; + // Include pending as a passed test because those are skipped tests (usually) + var passed = (this.stats.passes + this.stats.pending) === this.total; if (passed) { utils.setAction('All tests passed!'); document.getElementById('action').style.backgroundColor = '#ceffc4'; From 028adbcbd0b7600003bc37f75cca26d117a227b9 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Tue, 27 Oct 2020 17:08:24 -0600 Subject: [PATCH 10/35] (android) Work-around to simulate mediaSessionId It is available on iOS and desktop chrome, but not Android for some reason. Google... >.> --- src/android/ChromecastSession.java | 15 ++++++++++++++- src/android/ChromecastUtilities.java | 14 ++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/android/ChromecastSession.java b/src/android/ChromecastSession.java index 5c7a0d5..f17b16f 100644 --- a/src/android/ChromecastSession.java +++ b/src/android/ChromecastSession.java @@ -441,6 +441,19 @@ private void setQueueReloadCallback(Runnable callback) { this.queueReloadCallback = callback; } + /** + * This is called only when new media has been loaded. + * Media has been loaded via loadMedia or queueLoad by this sender or an external sender. + */ + private void runQueueReloadCallback() { + if (this.queueReloadCallback != null) { + // TODO incrementMediaSessionId is a hack to simulate changing mediaSessionId + // (for some reason this is available on iOS and desktop chrome, but not Android.) + ChromecastUtilities.incrementMediaSessionId(); + this.queueReloadCallback.run(); + } + } + private void setQueueStatusUpdatedCallback(Runnable callback) { this.queueStatusUpdatedCallback = callback; } @@ -523,7 +536,7 @@ private void updateFinished() { // Update the queueItems ChromecastUtilities.setQueueItems(queueItems); if (queueReloadCallback != null && queue.getItemCount() > 0) { - queueReloadCallback.run(); + runQueueReloadCallback(); setQueueReloadCallback(null); } clientListener.onMediaUpdate(createMediaObject()); diff --git a/src/android/ChromecastUtilities.java b/src/android/ChromecastUtilities.java index 7039db0..017b60b 100644 --- a/src/android/ChromecastUtilities.java +++ b/src/android/ChromecastUtilities.java @@ -30,6 +30,8 @@ final class ChromecastUtilities { /** Stores a cache of the queueItems for building Media Objects. */ private static JSONArray queueItems = null; + /** We have to make up our own mediaSessionId since Android does not give us access to it. */ + private static int mediaSessionId = 0; private ChromecastUtilities() { //not called @@ -44,6 +46,14 @@ static void setQueueItems(JSONArray items) { queueItems = items; } + /** + * Should be called whenever new media/queue is detected. + * Aka: When media is loaded via loadMedia or queueLoad by this sender or an external sender. + */ + static void incrementMediaSessionId() { + mediaSessionId++; + } + static String getMediaIdleReason(int idleReason) { switch (idleReason) { case MediaStatus.IDLE_REASON_CANCELED: @@ -498,7 +508,7 @@ static JSONObject createMediaObject(CastSession session, JSONArray items) { MediaStatus mediaStatus = session.getRemoteMediaClient().getMediaStatus(); // TODO: Missing attributes are commented out. - // These are returned by the chromecast desktop SDK, we should probbaly return them too + // These are returned by the chromecast desktop SDK, we should probably return them too //out.put("breakStatus",); out.put("currentItemId", mediaStatus.getCurrentItemId()); out.put("currentTime", mediaStatus.getStreamPosition() / 1000.0); @@ -513,7 +523,7 @@ static JSONObject createMediaObject(CastSession session, JSONArray items) { //out.put("liveSeekableRange",); out.put("loadingItemId", mediaStatus.getLoadingItemId()); out.put("media", createMediaInfoObject(session.getRemoteMediaClient().getMediaInfo())); - out.put("mediaSessionId", 1); + out.put("mediaSessionId", mediaSessionId); out.put("playbackRate", mediaStatus.getPlaybackRate()); out.put("playerState", ChromecastUtilities.getMediaPlayerState(mediaStatus.getPlayerState())); out.put("preloadedItemId", mediaStatus.getPreloadedItemId()); From 40ae8a61a27831f6f7ee42afa33c7badc6c68fac Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Tue, 27 Oct 2020 17:11:46 -0600 Subject: [PATCH 11/35] [no change] Update readme and fix text in interaction test --- README.md | 4 ++-- tests/www/js/tests_interaction_primary.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2c7a57f..b8569f6 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ cordova plugin add https://github.com/jellyfin/cordova-plugin-chromecast.git ``` -If you have trouble installing the pulgin or running the project for iOS, from `/platforms/ios/` try running: +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 @@ -36,7 +36,7 @@ These strings will be used when asking the user for permission to use the microp # Supports **Android** 4.4+ (7.x highest confirmed) (may support lower, untested) -**iOS** 9.0+ (13.2.1 highest confirmed) +**iOS** 9.0+ (14.1 highest confirmed) ## Quirks * Android 4.4 (maybe 5.x and 6.x) are not able automatically rejoin/resume a chromecast session after an app restart. diff --git a/tests/www/js/tests_interaction_primary.js b/tests/www/js/tests_interaction_primary.js index da4a6bd..8e6ca0e 100644 --- a/tests/www/js/tests_interaction_primary.js +++ b/tests/www/js/tests_interaction_primary.js @@ -297,7 +297,7 @@ window.location.reload(); } this.timeout(0); // no timeout - utils.setAction('Force kill and restart the app, and navigate back to Manual Tests (Primary) Part 2.' + utils.setAction('Force kill and restart the app, and navigate back to Interaction Tests - Primary Device.' + '
Note: Android 4.4 does not support this feature, so just refresh the page.'); break; case testNum: @@ -348,8 +348,8 @@ describe('session interaction with secondary', function () { it('Create session', function (done) { utils.setAction('On secondary click "Leave Session".', - 'Enter Session' - + (utils.isDesktop() ? '
(Desktop: Stop & Start casting from the same cast pop up)' : ''), + 'Enter Session' + + (utils.isDesktop() ? '
(Desktop: Stop & Start casting from the same cast pop up)' : ''), function () { utils.startSession(function (sess) { session = sess; From 6e8d8dbcdb79b65819525aa1cffae097d9590b4a Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Fri, 30 Oct 2020 22:28:52 -0600 Subject: [PATCH 12/35] Add Audiobook chapter metadata --- src/android/ChromecastUtilities.java | 2 +- src/ios/MLPCastUtilities.m | 2 +- www/chrome.cast.js | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/android/ChromecastUtilities.java b/src/android/ChromecastUtilities.java index 017b60b..5ea5030 100644 --- a/src/android/ChromecastUtilities.java +++ b/src/android/ChromecastUtilities.java @@ -360,7 +360,6 @@ static String getMetadataType(String androidName) { case MediaMetadata.KEY_ALBUM_TITLE: case MediaMetadata.KEY_ARTIST: case MediaMetadata.KEY_BOOK_TITLE: - case MediaMetadata.KEY_CHAPTER_NUMBER: case MediaMetadata.KEY_CHAPTER_TITLE: case MediaMetadata.KEY_COMPOSER: case MediaMetadata.KEY_LOCATION_NAME: @@ -369,6 +368,7 @@ static String getMetadataType(String androidName) { case MediaMetadata.KEY_SUBTITLE: case MediaMetadata.KEY_TITLE: return "string"; // 1 in MediaMetadata + case MediaMetadata.KEY_CHAPTER_NUMBER: case MediaMetadata.KEY_DISC_NUMBER: case MediaMetadata.KEY_EPISODE_NUMBER: case MediaMetadata.KEY_HEIGHT: diff --git a/src/ios/MLPCastUtilities.m b/src/ios/MLPCastUtilities.m index 8df3abc..4b9da62 100644 --- a/src/ios/MLPCastUtilities.m +++ b/src/ios/MLPCastUtilities.m @@ -195,7 +195,7 @@ +(NSString*)getMetadataType:(NSString*)iOSName { return @"date"; } if ([iOSName isEqualToString:kGCKMetadataKeyChapterNumber]) { - return @"string"; + return @"int"; } if ([iOSName isEqualToString:kGCKMetadataKeyChapterTitle]) { return @"string"; diff --git a/www/chrome.cast.js b/www/chrome.cast.js index e0b5888..0d6cc5c 100644 --- a/www/chrome.cast.js +++ b/www/chrome.cast.js @@ -362,6 +362,21 @@ chrome.cast = { this.customData = null; }, + /** + * An audiobook chapter description. + * @property {string} bookTitle Audiobook title. + * @property {number} chapterNumber Chapter number, used for display purposes. + * @property {string} chapterTitle Chapter title. + * @property {chrome.cast.Image[]} images Content images. + * @property {string} subtitle Content subtitle. + * @property {string} title Content title. + * @property {chrome.cast.media.MetadataType} type The type of metadata. + */ + AudiobookChapterMediaMetadata: function GenericMediaMetadata () { + this.metadataType = this.type = chrome.cast.media.MetadataType.AUDIOBOOK_CHAPTER; + this.bookTitle = this.chapterNumber = this.chapterTitle = this.images = this.subtitle = this.title = undefined; + }, + /** * A generic media description. * @property {chrome.cast.Image[]} images Content images. From df267c7b241fa413085d0ac3e38e01f9640c2cfd Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Fri, 30 Oct 2020 23:18:02 -0600 Subject: [PATCH 13/35] If no metadata supplied the returned MediaInfo from native should also have not metadata --- src/android/ChromecastUtilities.java | 9 ++++++--- src/ios/MLPCastUtilities.m | 10 ++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/android/ChromecastUtilities.java b/src/android/ChromecastUtilities.java index 5ea5030..ec11af3 100644 --- a/src/android/ChromecastUtilities.java +++ b/src/android/ChromecastUtilities.java @@ -664,10 +664,14 @@ private static JSONObject createMediaInfoObject(MediaInfo mediaInfo) { } static JSONObject createMetadataObject(MediaMetadata metadata) { - JSONObject out = new JSONObject(); if (metadata == null) { - return out; + return null; } + Set keys = metadata.keySet(); + if (keys.size() == 0) { + return null; + } + JSONObject out = new JSONObject(); try { try { // Must be in own try catch @@ -677,7 +681,6 @@ static JSONObject createMetadataObject(MediaMetadata metadata) { out.put("metadataType", metadata.getMediaType()); out.put("type", metadata.getMediaType()); - Set keys = metadata.keySet(); String outKey; // First translate and add the Android specific keys for (String key : keys) { diff --git a/src/ios/MLPCastUtilities.m b/src/ios/MLPCastUtilities.m index 4b9da62..d07da3d 100644 --- a/src/ios/MLPCastUtilities.m +++ b/src/ios/MLPCastUtilities.m @@ -597,16 +597,18 @@ + (NSDictionary *)createMediaInfoObject:(GCKMediaInformation *)mediaInfo { } + (NSDictionary*)createMetadataObject:(GCKMediaMetadata*)metadata { - - NSMutableDictionary* outputDict = [NSMutableDictionary new]; if (!metadata) { - return [NSDictionary dictionaryWithDictionary:outputDict]; + return nil; + } + NSArray* keys = metadata.allKeys; + if ([keys count] == 0) { + return nil; } + NSMutableDictionary* outputDict = [NSMutableDictionary new]; outputDict[@"images"] = [MLPCastUtilities createImagesArray:metadata.images]; outputDict[@"metadataType"] = @(metadata.metadataType); outputDict[@"type"] = @(metadata.metadataType); - NSArray* keys = metadata.allKeys; for (NSString* key in keys) { NSString* outKey = [MLPCastUtilities getClientMetadataName:key]; if ([outKey isEqualToString:key] || [outKey isEqualToString:@"type"]) { From 374ca1abd40bb08894f2a0cadcba951b35e7a19d Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sat, 31 Oct 2020 00:36:10 -0600 Subject: [PATCH 14/35] Make originalAirDate use the broadcastDate key (it is my best guess that that is what it is for?) (chrome desktop does not appear to send the originalAirDate to the cast device because it doesn't appear to make it to android) --- src/android/ChromecastUtilities.java | 9 ++++----- src/ios/MLPCastUtilities.m | 7 ++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/android/ChromecastUtilities.java b/src/android/ChromecastUtilities.java index ec11af3..549bdfb 100644 --- a/src/android/ChromecastUtilities.java +++ b/src/android/ChromecastUtilities.java @@ -236,8 +236,6 @@ static String getAndroidMetadataName(String clientName) { return MediaMetadata.KEY_ARTIST; case "bookTitle": return MediaMetadata.KEY_BOOK_TITLE; - case "broadcastDate": - return MediaMetadata.KEY_BROADCAST_DATE; case "chapterNumber": return MediaMetadata.KEY_CHAPTER_NUMBER; case "chapterTitle": @@ -259,10 +257,11 @@ static String getAndroidMetadataName(String clientName) { return MediaMetadata.KEY_LOCATION_LONGITUDE; case "locationName": return MediaMetadata.KEY_LOCATION_NAME; + case "originalAirDate": + return MediaMetadata.KEY_BROADCAST_DATE; case "queueItemId": return MediaMetadata.KEY_QUEUE_ITEM_ID; case "releaseDate": - case "originalAirDate": return MediaMetadata.KEY_RELEASE_DATE; case "season": return MediaMetadata.KEY_SEASON_NUMBER; @@ -302,7 +301,7 @@ static String getClientMetadataName(String androidName) { case MediaMetadata.KEY_BOOK_TITLE: return "bookTitle"; case MediaMetadata.KEY_BROADCAST_DATE: - return "broadcastDate"; + return "originalAirDate"; case MediaMetadata.KEY_CHAPTER_NUMBER: return "chapterNumber"; case MediaMetadata.KEY_CHAPTER_TITLE: @@ -785,7 +784,7 @@ static JSONObject createError(String code, String message) { return out; } -/* ------------------- Create NON-JSON (non-output) Objects ---------------------------------- */ + /* ------------------- Create NON-JSON (non-output) Objects ---------------------------------- */ /** * Creates a MediaQueueItem from a JSONObject representation of a MediaQueueItem. diff --git a/src/ios/MLPCastUtilities.m b/src/ios/MLPCastUtilities.m index d07da3d..4a0a5be 100644 --- a/src/ios/MLPCastUtilities.m +++ b/src/ios/MLPCastUtilities.m @@ -278,9 +278,6 @@ +(NSString*)getiOSMetadataName:(NSString*)clientName { if ([clientName isEqualToString:@"bookTitle"]) { return kGCKMetadataKeyBookTitle; } - if ([clientName isEqualToString:@"broadcastDate"]) { - return kGCKMetadataKeyBroadcastDate; - } if ([clientName isEqualToString:@"chapterNumber"]) { return kGCKMetadataKeyChapterNumber; } @@ -321,7 +318,7 @@ +(NSString*)getiOSMetadataName:(NSString*)clientName { return kGCKMetadataKeyReleaseDate; } if ([clientName isEqualToString:@"originalAirDate"]) { - return kGCKMetadataKeyReleaseDate; + return kGCKMetadataKeyBroadcastDate; } if ([clientName isEqualToString:@"season"]) { return kGCKMetadataKeySeasonNumber; @@ -373,7 +370,7 @@ +(NSString*)getClientMetadataName:(NSString*)iOSName { return @"bookTitle"; } if ([iOSName isEqualToString:kGCKMetadataKeyBroadcastDate]) { - return @"broadcastDate"; + return @"originalAirDate"; } if ([iOSName isEqualToString:kGCKMetadataKeyChapterNumber]) { return @"chapterNumber"; From a1f1e3c59d9c98507f225f771f4a1425feb44903 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sat, 31 Oct 2020 01:04:46 -0600 Subject: [PATCH 15/35] (test) Extract file: mediaGenerateAndAssert.js to reduce code duplication. --- tests/www/html/tests_auto.html | 1 + tests/www/html/tests_interaction_primary.html | 1 + .../www/html/tests_interaction_secondary.html | 1 + tests/www/html/tests_manual.html | 1 + tests/www/js/tests_auto.js | 222 +++++------------- tests/www/js/tests_interaction_primary.js | 36 +-- tests/www/js/tests_interaction_secondary.js | 104 ++------ tests/www/js/tests_manual.js | 89 +------ tests/www/lib/mediaGenerateAndAssert.js | 180 ++++++++++++++ 9 files changed, 299 insertions(+), 336 deletions(-) create mode 100644 tests/www/lib/mediaGenerateAndAssert.js diff --git a/tests/www/html/tests_auto.html b/tests/www/html/tests_auto.html index 16d83a8..d9dbe6e 100644 --- a/tests/www/html/tests_auto.html +++ b/tests/www/html/tests_auto.html @@ -12,6 +12,7 @@ + diff --git a/tests/www/html/tests_interaction_primary.html b/tests/www/html/tests_interaction_primary.html index d01b521..293016a 100644 --- a/tests/www/html/tests_interaction_primary.html +++ b/tests/www/html/tests_interaction_primary.html @@ -12,6 +12,7 @@ + diff --git a/tests/www/html/tests_interaction_secondary.html b/tests/www/html/tests_interaction_secondary.html index 7926d74..30f49fb 100644 --- a/tests/www/html/tests_interaction_secondary.html +++ b/tests/www/html/tests_interaction_secondary.html @@ -12,6 +12,7 @@ + diff --git a/tests/www/html/tests_manual.html b/tests/www/html/tests_manual.html index 3921343..f408fc1 100644 --- a/tests/www/html/tests_manual.html +++ b/tests/www/html/tests_manual.html @@ -12,6 +12,7 @@ + diff --git a/tests/www/js/tests_auto.js b/tests/www/js/tests_auto.js index 2489c10..5e521b5 100644 --- a/tests/www/js/tests_auto.js +++ b/tests/www/js/tests_auto.js @@ -28,12 +28,9 @@ var assert = window.chai.assert; var utils = window['cordova-plugin-chromecast-tests'].utils; + var mediaUtils = window['cordova-plugin-chromecast-tests'].mediaUtils; describe('cordova-plugin-chromecast', function () { - var imageUrl = 'https://ia800705.us.archive.org/1/items/GoodHousekeeping193810/Good%20Housekeeping%201938-10.jpg'; - var videoUrl = 'https://ia801302.us.archive.org/1/items/TheWater_201510/TheWater.mp4'; - var audioUrl = 'https://ia800306.us.archive.org/26/items/1939RadioNews/1939-10-24-CBS-Elmer-Davis-Reports-City-Of-Flint-Still-Missing.mp3'; - // callOrder constants that are re-used frequently var success = 'success'; var update = 'update'; @@ -542,7 +539,18 @@ var mediaListener = function (media) { assert.fail('session.addMediaListener should only be called when an external sender loads media'); }; - before(function (done) { + var mediaInfo; + before('Create mediaInfo with custom metadata fields', function () { + mediaInfo = mediaUtils.getMediaInfoItem('VIDEO', chrome.cast.media.MetadataType.GENERIC); + mediaInfo.metadata.someTrueBoolean = true; + mediaInfo.metadata.someFalseBoolean = false; + mediaInfo.metadata.someSmallNumber = 15; + mediaInfo.metadata.someLargeNumber = 1234567890123456; + mediaInfo.metadata.someSmallDecimal = 15.15; + mediaInfo.metadata.someLargeDecimal = 1234567.123456789; + mediaInfo.metadata.someString = 'SomeString'; + }); + before('Get session', function (done) { // need to have a valid session to run these tests session = null; var scanState = 'running'; @@ -592,26 +600,11 @@ }); describe('Media (non-queues)', function () { it('session.loadMedia should be able to load a remote video and handle GenericMediaMetadata', function (done) { - var mediaInfo = new chrome.cast.media.MediaInfo(videoUrl, 'video/mp4'); - mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata(); - mediaInfo.metadata.title = 'DaTitle'; - mediaInfo.metadata.subtitle = 'DaSubtitle'; - mediaInfo.metadata.releaseDate = new Date().valueOf(); - mediaInfo.metadata.someTrueBoolean = true; - mediaInfo.metadata.someFalseBoolean = false; - mediaInfo.metadata.someSmallNumber = 15; - mediaInfo.metadata.someLargeNumber = 1234567890123456; - mediaInfo.metadata.someSmallDecimal = 15.15; - mediaInfo.metadata.someLargeDecimal = 1234567.123456789; - mediaInfo.metadata.someString = 'SomeString'; - mediaInfo.metadata.images = [new chrome.cast.Image(imageUrl)]; session.loadMedia(new chrome.cast.media.LoadRequest(mediaInfo), function (m) { media = m; utils.testMediaProperties(media); assert.isUndefined(media.queueData); - assert.equal(media.media.metadata.title, mediaInfo.metadata.title); - assert.equal(media.media.metadata.subtitle, mediaInfo.metadata.subtitle); - assert.equal(media.media.metadata.releaseDate, mediaInfo.metadata.releaseDate); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); // TODO figure out how to maintain the data types for custom params on the native side // so that we don't have to do turn each actual and expected into a string assert.equal(media.media.metadata.someTrueBoolean + '', mediaInfo.metadata.someTrueBoolean + ''); @@ -621,13 +614,11 @@ assert.equal(media.media.metadata.someSmallDecimal + '', mediaInfo.metadata.someSmallDecimal + ''); assert.equal(media.media.metadata.someLargeDecimal + '', mediaInfo.metadata.someLargeDecimal + ''); assert.equal(media.media.metadata.someString, mediaInfo.metadata.someString); - assert.equal(media.media.metadata.images[0].url, mediaInfo.metadata.images[0].url); - assert.equal(media.media.metadata.metadataType, chrome.cast.media.MetadataType.GENERIC); - assert.equal(media.media.metadata.type, chrome.cast.media.MetadataType.GENERIC); assert.notExists(media.idleReason); media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); utils.testMediaProperties(media); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); assert.oneOf(media.playerState, [ chrome.cast.media.PlayerState.PLAYING, chrome.cast.media.PlayerState.BUFFERING]); @@ -658,6 +649,7 @@ media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); assert.instanceOf(media.volume, chrome.cast.Volume); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); if (media.volume.level === vol) { media.removeUpdateListener(listener); called(update); @@ -667,6 +659,7 @@ media.setVolume(request, function () { assert.instanceOf(media.volume, chrome.cast.Volume); assert.equal(media.volume.level, vol); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); called(success); }, function (err) { assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); @@ -684,6 +677,7 @@ media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); assert.instanceOf(media.volume, chrome.cast.Volume); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); if (media.volume.muted === muted) { media.removeUpdateListener(listener); called(update); @@ -693,6 +687,7 @@ media.setVolume(request, function () { assert.instanceOf(media.volume, chrome.cast.Volume); assert.equal(media.volume.muted, muted); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); called(success); }, function (err) { assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); @@ -717,6 +712,7 @@ media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); assert.instanceOf(media.volume, chrome.cast.Volume); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); if (media.volume.level === vol && media.volume.muted === request.volume.muted) { media.removeUpdateListener(listener); @@ -728,6 +724,7 @@ assert.instanceOf(media.volume, chrome.cast.Volume); assert.equal(media.volume.level, vol); assert.equal(media.volume.muted, muted); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); called(success); }, function (err) { assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); @@ -741,6 +738,7 @@ media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); assert.notEqual(media.playerState, chrome.cast.media.PlayerState.IDLE); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); if (media.playerState === chrome.cast.media.PlayerState.PAUSED) { media.removeUpdateListener(listener); called(update); @@ -748,6 +746,7 @@ }); media.pause(null, function () { assert.equal(media.playerState, chrome.cast.media.PlayerState.PAUSED); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); called(success); }, function (err) { assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); @@ -761,6 +760,7 @@ media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); assert.notEqual(media.playerState, chrome.cast.media.PlayerState.IDLE); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); if (media.playerState === chrome.cast.media.PlayerState.PLAYING) { media.removeUpdateListener(listener); called(update); @@ -770,6 +770,7 @@ assert.oneOf(media.playerState, [ chrome.cast.media.PlayerState.PLAYING, chrome.cast.media.PlayerState.BUFFERING]); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); called(success); }, function (err) { assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); @@ -784,6 +785,7 @@ request.currentTime = media.media.duration / 2; media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); if (media.getEstimatedTime() > request.currentTime - 1 && media.getEstimatedTime() < request.currentTime + 1) { media.removeUpdateListener(listener); @@ -792,6 +794,7 @@ }); media.seek(request, function () { assert.closeTo(media.getEstimatedTime(), request.currentTime, 1); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); called(success); }, function (err) { assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); @@ -805,6 +808,7 @@ var request = new chrome.cast.media.SeekRequest(); request.currentTime = media.media.duration; media.addUpdateListener(function listener (isAlive) { + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); if (media.playerState === chrome.cast.media.PlayerState.IDLE) { media.removeUpdateListener(listener); assert.equal(media.idleReason, chrome.cast.media.IdleReason.FINISHED); @@ -813,6 +817,7 @@ } }); media.seek(request, function () { + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); called(success); }, function (err) { assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); @@ -876,29 +881,19 @@ done(); }); }); - it('session.loadMedia should be able to load videos twice in a row and handle MovieMediaMetadata and TvShowMediaMetadata correctly, and first media should be invalidated', function (done) { + it('session.loadMedia should be able to load media twice in a row and handle MovieMediaMetadata and AudiobookChapterMediaMetadata correctly, and first media should be invalidated', function (done) { var firstMedia; - var mediaInfo = new chrome.cast.media.MediaInfo(videoUrl, 'video/mp4'); - mediaInfo.metadata = new chrome.cast.media.MovieMediaMetadata(); - mediaInfo.metadata.title = 'DaTitle'; - mediaInfo.metadata.subtitle = 'DaSubtitle'; - mediaInfo.metadata.studio = 'DaStudio'; - mediaInfo.metadata.myMadeUpMetadata = 'DaMadeUpMetadata'; - mediaInfo.metadata.images = [new chrome.cast.Image(imageUrl)]; + var mediaInfo = mediaUtils.getMediaInfoItem('VIDEO', chrome.cast.media.MetadataType.MOVIE); + session.loadMedia(new chrome.cast.media.LoadRequest(mediaInfo), function (m) { media = m; firstMedia = m; utils.testMediaProperties(media); - assert.equal(media.media.metadata.title, mediaInfo.metadata.title); - assert.equal(media.media.metadata.subtitle, mediaInfo.metadata.subtitle); - assert.equal(media.media.metadata.studio, mediaInfo.metadata.studio); - assert.equal(media.media.metadata.myMadeUpMetadata, mediaInfo.metadata.myMadeUpMetadata); - assert.equal(media.media.metadata.images[0].url, mediaInfo.metadata.images[0].url); - assert.equal(media.media.metadata.metadataType, chrome.cast.media.MetadataType.MOVIE); - assert.equal(media.media.metadata.type, chrome.cast.media.MetadataType.MOVIE); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); utils.testMediaProperties(media); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); assert.oneOf(media.playerState, [ chrome.cast.media.PlayerState.PLAYING, chrome.cast.media.PlayerState.BUFFERING]); @@ -912,15 +907,7 @@ }); function loadSecond () { - var mediaInfo = new chrome.cast.media.MediaInfo(videoUrl, 'video/mp4'); - mediaInfo.metadata = new chrome.cast.media.TvShowMediaMetadata(); - mediaInfo.metadata.title = 'DaTitle'; - mediaInfo.metadata.subtitle = 'DaSubtitle'; - mediaInfo.metadata.originalAirDate = new Date().valueOf(); - mediaInfo.metadata.episode = 15; - mediaInfo.metadata.season = 2; - mediaInfo.metadata.seriesTitle = 'DaSeries'; - mediaInfo.metadata.images = [new chrome.cast.Image(imageUrl)]; + var mediaInfo = mediaUtils.getMediaInfoItem('AUDIO', chrome.cast.media.MetadataType.AUDIOBOOK_CHAPTER); session.loadMedia(new chrome.cast.media.LoadRequest(mediaInfo), function (m) { media = m; // Test old media is invalid (should not equal new media and should give error on pause) @@ -935,18 +922,11 @@ // Now verify the new media utils.testMediaProperties(media); - assert.equal(media.media.metadata.title, mediaInfo.metadata.title); - assert.equal(media.media.metadata.subtitle, mediaInfo.metadata.subtitle); - assert.equal(media.media.metadata.originalAirDate, mediaInfo.metadata.originalAirDate); - assert.equal(media.media.metadata.episode, mediaInfo.metadata.episode); - assert.equal(media.media.metadata.season, mediaInfo.metadata.season); - assert.equal(media.media.metadata.seriesTitle, mediaInfo.metadata.seriesTitle); - assert.equal(media.media.metadata.images[0].url, mediaInfo.metadata.images[0].url); - assert.equal(media.media.metadata.metadataType, chrome.cast.media.MetadataType.TV_SHOW); - assert.equal(media.media.metadata.type, chrome.cast.media.MetadataType.TV_SHOW); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); utils.testMediaProperties(media); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); assert.oneOf(media.playerState, [ chrome.cast.media.PlayerState.PLAYING, chrome.cast.media.PlayerState.BUFFERING]); @@ -961,35 +941,17 @@ }); } }); - it('session.loadMedia should be able to load remote audio and return the MusicTrackMediaMetadata', function (done) { - var mediaInfo = new chrome.cast.media.MediaInfo(audioUrl, 'audio/mpeg'); - mediaInfo.metadata = new chrome.cast.media.MusicTrackMediaMetadata(); - mediaInfo.metadata.albumArtist = 'DaAlmbumArtist'; - mediaInfo.metadata.albumName = 'DaAlbum'; - mediaInfo.metadata.artist = 'DaArtist'; - mediaInfo.metadata.composer = 'DaComposer'; - mediaInfo.metadata.title = 'DaTitle'; - mediaInfo.metadata.songName = 'DaSongName'; - mediaInfo.metadata.releaseDate = new Date().valueOf(); - mediaInfo.metadata.images = [new chrome.cast.Image(imageUrl)]; - mediaInfo.metadata.myMadeUpMetadata = 15; + it('session.loadMedia should be able to load remote video with no metadata', function (done) { + var mediaInfo = mediaUtils.getMediaInfoItem('VIDEO'); session.loadMedia(new chrome.cast.media.LoadRequest(mediaInfo), function (m) { media = m; utils.testMediaProperties(media); - assert.equal(media.media.metadata.albumArtist, mediaInfo.metadata.albumArtist); - assert.equal(media.media.metadata.albumName, mediaInfo.metadata.albumName); - assert.equal(media.media.metadata.artist, mediaInfo.metadata.artist); - assert.equal(media.media.metadata.composer, mediaInfo.metadata.composer); - assert.equal(media.media.metadata.title, mediaInfo.metadata.title); - assert.equal(media.media.metadata.songName, mediaInfo.metadata.songName); - assert.equal(media.media.metadata.releaseDate, mediaInfo.metadata.releaseDate); - assert.equal(media.media.metadata.images[0].url, mediaInfo.metadata.images[0].url); - assert.equal(media.media.metadata.myMadeUpMetadata, mediaInfo.metadata.myMadeUpMetadata); - assert.equal(media.media.metadata.metadataType, chrome.cast.media.MetadataType.MUSIC_TRACK); - assert.equal(media.media.metadata.type, chrome.cast.media.MetadataType.MUSIC_TRACK); + assert.oneOf(media.media.metadata, [null, undefined]); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); utils.testMediaProperties(media); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); assert.oneOf(media.playerState, [ chrome.cast.media.PlayerState.PLAYING, chrome.cast.media.PlayerState.BUFFERING]); @@ -1003,36 +965,15 @@ }); }); it('session.loadMedia should be able to load remote image and return the PhotoMediaMetadata', function (done) { - var mediaInfo = new chrome.cast.media.MediaInfo(imageUrl, 'image/jpeg'); - mediaInfo.metadata = new chrome.cast.media.PhotoMediaMetadata(); - mediaInfo.metadata.title = 'DaTitle'; - mediaInfo.metadata.artist = 'DaArtist'; - mediaInfo.metadata.location = 'DaLocation'; - mediaInfo.metadata.latitude = 102.13; - mediaInfo.metadata.longitude = 101.12; - mediaInfo.metadata.height = 100; - mediaInfo.metadata.width = 100; - mediaInfo.metadata.myMadeUpMetadata = 15; - mediaInfo.metadata.creationDateTime = new Date().valueOf(); - mediaInfo.metadata.images = [new chrome.cast.Image(imageUrl)]; + var mediaInfo = mediaUtils.getMediaInfoItem('IMAGE', chrome.cast.media.MetadataType.PHOTO); session.loadMedia(new chrome.cast.media.LoadRequest(mediaInfo), function (m) { media = m; utils.testMediaProperties(media); - assert.equal(media.media.metadata.title, mediaInfo.metadata.title); - assert.equal(media.media.metadata.artist, mediaInfo.metadata.artist); - assert.equal(media.media.metadata.location, mediaInfo.metadata.location); - assert.equal(media.media.metadata.latitude, mediaInfo.metadata.latitude); - assert.equal(media.media.metadata.longitude, mediaInfo.metadata.longitude); - assert.equal(media.media.metadata.height, mediaInfo.metadata.height); - assert.equal(media.media.metadata.width, mediaInfo.metadata.width); - assert.equal(media.media.metadata.myMadeUpMetadata, mediaInfo.metadata.myMadeUpMetadata); - assert.equal(media.media.metadata.creationDateTime, mediaInfo.metadata.creationDateTime); - assert.equal(media.media.metadata.images[0].url, mediaInfo.metadata.images[0].url); - assert.equal(media.media.metadata.metadataType, chrome.cast.media.MetadataType.PHOTO); - assert.equal(media.media.metadata.type, chrome.cast.media.MetadataType.PHOTO); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); utils.testMediaProperties(media); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); if (media.playerState === chrome.cast.media.PlayerState.PAUSED) { media.removeUpdateListener(listener); done(); @@ -1042,7 +983,7 @@ assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); }); }); - it('media.stop should end video playback', function (done) { + it('media.stop should end media playback', function (done) { var called = utils.waitForAllCalls([ { id: success, repeats: false }, { id: update, repeats: true } @@ -1070,32 +1011,7 @@ var startTime = 40; var jumpItemId; var request; - var assertVideoItem = function (media) { - assert.equal(media.contentId, videoUrl); - assert.equal(media.metadata.title, videoItem.metadata.title); - assert.equal(media.metadata.subtitle, videoItem.metadata.subtitle); - assert.equal(media.metadata.originalAirDate, videoItem.metadata.originalAirDate); - assert.equal(media.metadata.episode, videoItem.metadata.episode); - assert.equal(media.metadata.season, videoItem.metadata.season); - assert.equal(media.metadata.seriesTitle, videoItem.metadata.seriesTitle); - assert.equal(media.metadata.images[0].url, videoItem.metadata.images[0].url); - assert.equal(media.metadata.metadataType, chrome.cast.media.MetadataType.TV_SHOW); - assert.equal(media.metadata.type, chrome.cast.media.MetadataType.TV_SHOW); - }; - var assertAudioItem = function (media) { - assert.equal(media.contentId, audioUrl); - assert.equal(media.metadata.albumArtist, audioItem.metadata.albumArtist); - assert.equal(media.metadata.albumName, audioItem.metadata.albumName); - assert.equal(media.metadata.artist, audioItem.metadata.artist); - assert.equal(media.metadata.composer, audioItem.metadata.composer); - assert.equal(media.metadata.title, audioItem.metadata.title); - assert.equal(media.metadata.songName, audioItem.metadata.songName); - assert.equal(media.metadata.releaseDate, audioItem.metadata.releaseDate); - assert.equal(media.metadata.images[0].url, audioItem.metadata.images[0].url); - assert.equal(media.metadata.myMadeUpMetadata, audioItem.metadata.myMadeUpMetadata); - assert.equal(media.metadata.metadataType, chrome.cast.media.MetadataType.MUSIC_TRACK); - assert.equal(media.metadata.type, chrome.cast.media.MetadataType.MUSIC_TRACK); - }; + var assertQueueProperties = function (media) { utils.testMediaProperties(media); assert.equal(media.repeatMode, request.repeatMode); @@ -1105,28 +1021,8 @@ utils.testQueueItems(media.items); }; before(function () { - videoItem = new chrome.cast.media.MediaInfo(videoUrl, 'video/mp4'); - videoItem.metadata = new chrome.cast.media.TvShowMediaMetadata(); - videoItem.metadata.title = 'DaTitle'; - videoItem.metadata.subtitle = 'DaSubtitle'; - videoItem.metadata.originalAirDate = new Date().valueOf(); - videoItem.metadata.episode = 15; - videoItem.metadata.season = 2; - videoItem.metadata.seriesTitle = 'DaSeries'; - videoItem.metadata.images = [new chrome.cast.Image(imageUrl)]; - - audioItem = new chrome.cast.media.MediaInfo(audioUrl, 'audio/mpeg'); - audioItem.metadata = new chrome.cast.media.MusicTrackMediaMetadata(); - audioItem.metadata.albumArtist = 'DaAlmbumArtist'; - audioItem.metadata.albumName = 'DaAlbum'; - audioItem.metadata.artist = 'DaArtist'; - audioItem.metadata.composer = 'DaComposer'; - audioItem.metadata.title = 'DaTitle'; - audioItem.metadata.songName = 'DaSongName'; - audioItem.metadata.myMadeUpMetadata = '15'; - audioItem.metadata.releaseDate = new Date().valueOf(); - audioItem.metadata.images = [new chrome.cast.Image(imageUrl)]; - + videoItem = mediaUtils.getMediaInfoItem('VIDEO', chrome.cast.media.MetadataType.TV_SHOW); + audioItem = mediaUtils.getMediaInfoItem('AUDIO', chrome.cast.media.MetadataType.MUSIC_TRACK); var item; var queue = []; @@ -1160,7 +1056,7 @@ session.queueLoad(request, function (m) { media = m; assertQueueProperties(media); - assertAudioItem(media.media); + mediaUtils.assertMediaInfoItemEquals(media.media, audioItem); assert.closeTo(media.getEstimatedTime(), startTime * 2, 5); // Items should contain the last 2 items in the queue @@ -1168,12 +1064,12 @@ var i = utils.getCurrentItemIndex(media); - assertAudioItem(media.items[i - 1].media); + mediaUtils.assertMediaInfoItemEquals(media.items[i - 1].media, audioItem); assert.equal(media.items[i - 1].orderId, request.startIndex - 1); assert.isTrue(media.items[i - 1].autoplay); assert.equal(media.items[i - 1].startTime, startTime * 2); - assertAudioItem(media.items[i].media); + mediaUtils.assertMediaInfoItemEquals(media.items[i].media, audioItem); assert.equal(media.items[i].orderId, request.startIndex); assert.isTrue(media.items[i].autoplay); assert.equal(media.items[i].startTime, startTime * 2); @@ -1220,19 +1116,19 @@ media.removeUpdateListener(listener); assertQueueProperties(media); - assertVideoItem(media.media); + mediaUtils.assertMediaInfoItemEquals(media.media, videoItem); assert.closeTo(media.getEstimatedTime(), startTime, 5); // Items should contain the first 2 items in the queue assert.equal(media.items.length, 2); var i = utils.getCurrentItemIndex(media); - assertVideoItem(media.items[i].media); + mediaUtils.assertMediaInfoItemEquals(media.items[i].media, videoItem); assert.equal(media.items[i].orderId, 0); assert.isTrue(media.items[i].autoplay); assert.equal(media.items[i].startTime, startTime); - assertVideoItem(media.items[i + 1].media); + mediaUtils.assertMediaInfoItemEquals(media.items[i + 1].media, videoItem); assert.equal(media.items[i + 1].orderId, 1); assert.isTrue(media.items[i + 1].autoplay); assert.equal(media.items[i + 1].startTime, startTime); @@ -1298,24 +1194,24 @@ media.removeUpdateListener(listener); assertQueueProperties(media); - assertVideoItem(media.media); + mediaUtils.assertMediaInfoItemEquals(media.media, videoItem); assert.closeTo(media.getEstimatedTime(), startTime, 5); // Items should contain the first 3 items in the queue (1 before and 1 after current item) assert.equal(media.items.length, 3); var i = utils.getCurrentItemIndex(media); - assertVideoItem(media.items[i - 1].media); + mediaUtils.assertMediaInfoItemEquals(media.items[i - 1].media, videoItem); assert.equal(media.items[i - 1].orderId, 0); assert.isTrue(media.items[i - 1].autoplay); assert.equal(media.items[i - 1].startTime, startTime); - assertVideoItem(media.items[i].media); + mediaUtils.assertMediaInfoItemEquals(media.items[i].media, videoItem); assert.equal(media.items[i].orderId, 1); assert.isTrue(media.items[i].autoplay); assert.equal(media.items[i].startTime, startTime); - assertAudioItem(media.items[i + 1].media); + mediaUtils.assertMediaInfoItemEquals(media.items[i + 1].media, audioItem); assert.equal(media.items[i + 1].orderId, 2); assert.isTrue(media.items[i + 1].autoplay); assert.equal(media.items[i + 1].startTime, startTime * 2); diff --git a/tests/www/js/tests_interaction_primary.js b/tests/www/js/tests_interaction_primary.js index 8e6ca0e..a859f25 100644 --- a/tests/www/js/tests_interaction_primary.js +++ b/tests/www/js/tests_interaction_primary.js @@ -15,6 +15,7 @@ var assert = window.chai.assert; var utils = window['cordova-plugin-chromecast-tests'].utils; + var mediaUtils = window['cordova-plugin-chromecast-tests'].mediaUtils; mocha.setup({ bail: true, @@ -26,9 +27,6 @@ }); describe('Interaction Tests - Primary Device', function () { - var videoUrl = 'https://ia801302.us.archive.org/1/items/TheWater_201510/TheWater.mp4'; - var audioUrl = 'https://ia800306.us.archive.org/26/items/1939RadioNews/1939-10-24-CBS-Elmer-Davis-Reports-City-Of-Flint-Still-Missing.mp3'; - // callOrder constants that are re-used frequently var success = 'success'; var stopped = 'stopped'; @@ -36,10 +34,21 @@ var session; var media; + var videoItem; + var audioItem; var cookieName = 'primary-p2_restart-reload'; var runningNum = parseInt(utils.getValue(cookieName) || '-1'); + before('setup constants', function () { + // This must be identical to the before('setup constants'.. in tests_interaction_secondary.js + videoItem = mediaUtils.getMediaInfoItem('VIDEO', chrome.cast.media.MetadataType.TV_SHOW, new Date(2020, 10, 31)); + audioItem = mediaUtils.getMediaInfoItem('AUDIO', chrome.cast.media.MetadataType.MUSIC_TRACK, new Date(2020, 10, 31)); + // TODO desktop chrome does not send all metadata attributes for some reason, + // So delete the metadata so that assertMediaInfoEquals does not compare it + videoItem.metadata = null; + audioItem.metadata = null; + }); before('Api should be available and initialize successfully', function (done) { this.timeout(15000); session = null; @@ -127,19 +136,16 @@ } utils.setAction('Tests running...'); media = m; - var interval = setInterval(function () { - if (media.media.tracks != null && media.media.tracks !== undefined) { - clearInterval(interval); - utils.testMediaProperties(media); - finished = true; - done(); - } - }, 400); + utils.testMediaProperties(media); + mediaUtils.assertMediaInfoItemEquals(media.media, videoItem); + finished = true; + done(); }); }); it('External media stop should trigger media updateListener', function (done) { utils.setAction('On secondary click "Stop Media"'); media.addUpdateListener(function listener (isAlive) { + mediaUtils.assertMediaInfoItemEquals(media.media, videoItem); if (media.playerState === chrome.cast.media.PlayerState.IDLE) { media.removeUpdateListener(listener); assert.equal(media.idleReason, chrome.cast.media.IdleReason.CANCELLED); @@ -157,8 +163,9 @@ } finished = true; media = m; + mediaUtils.assertMediaInfoItemEquals(media.media, audioItem); var interval = setInterval(function () { - if (media.currentItemId > -1 && media.media.tracks) { + if (media.currentItemId > -1) { clearInterval(interval); finished = true; utils.testMediaProperties(media); @@ -166,10 +173,10 @@ var startTime = 40; assert.isTrue(items[0].autoplay); assert.equal(items[0].startTime, startTime); - assert.equal(items[0].media.contentId, videoUrl); + mediaUtils.assertMediaInfoItemEquals(items[0].media, videoItem); assert.isTrue(items[1].autoplay); assert.equal(items[1].startTime, startTime * 2); - assert.equal(items[1].media.contentId, audioUrl); + mediaUtils.assertMediaInfoItemEquals(items[1].media, audioItem); done(); } }, 400); @@ -198,6 +205,7 @@ session.removeMediaListener(mediaListener); media.removeUpdateListener(listener); utils.testMediaProperties(media); + mediaUtils.assertMediaInfoItemEquals(media.media, videoItem); called(update); } }); diff --git a/tests/www/js/tests_interaction_secondary.js b/tests/www/js/tests_interaction_secondary.js index 10212dc..d192282 100644 --- a/tests/www/js/tests_interaction_secondary.js +++ b/tests/www/js/tests_interaction_secondary.js @@ -15,6 +15,7 @@ var assert = window.chai.assert; var utils = window['cordova-plugin-chromecast-tests'].utils; + var mediaUtils = window['cordova-plugin-chromecast-tests'].mediaUtils; mocha.setup({ bail: true, @@ -26,10 +27,6 @@ }); describe('Manual Tests - Secondary Device', function () { - var imageUrl = 'https://ia800705.us.archive.org/1/items/GoodHousekeeping193810/Good%20Housekeeping%201938-10.jpg'; - var videoUrl = 'https://ia801302.us.archive.org/1/items/TheWater_201510/TheWater.mp4'; - var audioUrl = 'https://ia800306.us.archive.org/26/items/1939RadioNews/1939-10-24-CBS-Elmer-Davis-Reports-City-Of-Flint-Still-Missing.mp3'; - // callOrder constants that are re-used frequently var success = 'success'; var stopped = 'stopped'; @@ -46,36 +43,21 @@ var checkItems = function (items) { assert.isTrue(items[0].autoplay); assert.equal(items[0].startTime, startTime); - assert.equal(items[0].media.contentId, videoUrl); + mediaUtils.assertMediaInfoItemEquals(items[0].media, videoItem); assert.isTrue(items[1].autoplay); assert.equal(items[1].startTime, startTime * 2); - assert.equal(items[1].media.contentId, audioUrl); + mediaUtils.assertMediaInfoItemEquals(items[1].media, audioItem); }; before('setup constants', function () { - videoItem = new chrome.cast.media.MediaInfo(videoUrl, 'video/mp4'); - videoItem.metadata = new chrome.cast.media.TvShowMediaMetadata(); - videoItem.metadata.title = 'DaTitle'; - videoItem.metadata.subtitle = 'DaSubtitle'; - videoItem.metadata.originalAirDate = new Date().valueOf(); - videoItem.metadata.episode = 15; - videoItem.metadata.season = 2; - videoItem.metadata.seriesTitle = 'DaSeries'; - videoItem.metadata.images = [new chrome.cast.Image(imageUrl)]; - - audioItem = new chrome.cast.media.MediaInfo(audioUrl, 'audio/mpeg'); - audioItem.metadata = new chrome.cast.media.MusicTrackMediaMetadata(); - audioItem.metadata.albumArtist = 'DaAlmbumArtist'; - audioItem.metadata.albumName = 'DaAlbum'; - audioItem.metadata.artist = 'DaArtist'; - audioItem.metadata.composer = 'DaComposer'; - audioItem.metadata.title = 'DaTitle'; - audioItem.metadata.songName = 'DaSongName'; - audioItem.metadata.myMadeUpMetadata = '15'; - audioItem.metadata.releaseDate = new Date().valueOf(); - audioItem.metadata.images = [new chrome.cast.Image(imageUrl)]; + // This must be identical to the before('setup constants'.. in tests_interaction_primary.js + videoItem = mediaUtils.getMediaInfoItem('VIDEO', chrome.cast.media.MetadataType.TV_SHOW, new Date(2020, 10, 31)); + audioItem = mediaUtils.getMediaInfoItem('AUDIO', chrome.cast.media.MetadataType.MUSIC_TRACK, new Date(2020, 10, 31)); + // TODO desktop chrome does not send all metadata attributes for some reason, + // So delete the metadata so that assertMediaInfoEquals does not compare it + videoItem.metadata = null; + audioItem.metadata = null; }); - before('Api should be available and initialize successfully', function (done) { session = null; var interval = setInterval(function () { @@ -123,43 +105,17 @@ done(); }); }); - it('session.loadMedia should be able to load a remote video and handle GenericMediaMetadata', function (done) { + it('session.loadMedia should be able to load a remote video', function (done) { utils.setAction('On primary click "Listen for External Load Media"', 'Load Media', function () { - var mediaInfo = new chrome.cast.media.MediaInfo(videoUrl, 'video/mp4'); - mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata(); - mediaInfo.metadata.title = 'DaTitle'; - mediaInfo.metadata.subtitle = 'DaSubtitle'; - mediaInfo.metadata.releaseDate = new Date().valueOf(); - mediaInfo.metadata.someTrueBoolean = true; - mediaInfo.metadata.someFalseBoolean = false; - mediaInfo.metadata.someSmallNumber = 15; - mediaInfo.metadata.someLargeNumber = 1234567890123456; - mediaInfo.metadata.someSmallDecimal = 15.15; - mediaInfo.metadata.someLargeDecimal = 1234567.123456789; - mediaInfo.metadata.someString = 'SomeString'; - mediaInfo.metadata.images = [new chrome.cast.Image(imageUrl)]; - session.loadMedia(new chrome.cast.media.LoadRequest(mediaInfo), function (m) { + session.loadMedia(new chrome.cast.media.LoadRequest(videoItem), function (m) { media = m; utils.testMediaProperties(media); assert.isUndefined(media.queueData); - assert.equal(media.media.metadata.title, mediaInfo.metadata.title); - assert.equal(media.media.metadata.subtitle, mediaInfo.metadata.subtitle); - assert.equal(media.media.metadata.releaseDate, mediaInfo.metadata.releaseDate); - // TODO figure out how to maintain the data types for custom params on the native side - // so that we don't have to do turn each actual and expected into a string - assert.equal(media.media.metadata.someTrueBoolean + '', mediaInfo.metadata.someTrueBoolean + ''); - assert.equal(media.media.metadata.someFalseBoolean + '', mediaInfo.metadata.someFalseBoolean + ''); - assert.equal(media.media.metadata.someSmallNumber + '', mediaInfo.metadata.someSmallNumber + ''); - assert.equal(media.media.metadata.someLargeNumber + '', mediaInfo.metadata.someLargeNumber + ''); - assert.equal(media.media.metadata.someSmallDecimal + '', mediaInfo.metadata.someSmallDecimal + ''); - assert.equal(media.media.metadata.someLargeDecimal + '', mediaInfo.metadata.someLargeDecimal + ''); - assert.equal(media.media.metadata.someString, mediaInfo.metadata.someString); - assert.equal(media.media.metadata.images[0].url, mediaInfo.metadata.images[0].url); - assert.equal(media.media.metadata.metadataType, chrome.cast.media.MetadataType.GENERIC); - assert.equal(media.media.metadata.type, chrome.cast.media.MetadataType.GENERIC); + mediaUtils.assertMediaInfoItemEquals(media.media, videoItem); media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); utils.testMediaProperties(media); + mediaUtils.assertMediaInfoItemEquals(media.media, videoItem); assert.oneOf(media.playerState, [ chrome.cast.media.PlayerState.PLAYING, chrome.cast.media.PlayerState.BUFFERING]); @@ -182,6 +138,7 @@ done(); }); media.addUpdateListener(function listener (isAlive) { + mediaUtils.assertMediaInfoItemEquals(media.media, videoItem); if (media.playerState === chrome.cast.media.PlayerState.IDLE) { media.removeUpdateListener(listener); assert.equal(media.idleReason, chrome.cast.media.IdleReason.CANCELLED); @@ -190,6 +147,7 @@ } }); media.stop(null, function () { + mediaUtils.assertMediaInfoItemEquals(media.media, videoItem); assert.equal(media.playerState, chrome.cast.media.PlayerState.IDLE); assert.equal(media.idleReason, chrome.cast.media.IdleReason.CANCELLED); called(success); @@ -227,23 +185,14 @@ assert.isFalse(media.queueData.shuffle); assert.equal(media.queueData.startIndex, request.startIndex); utils.testQueueItems(media.items); - assert.equal(media.media.contentId, audioUrl); + mediaUtils.assertMediaInfoItemEquals(media.media, audioItem); assert.equal(media.items.length, 2); checkItems(media.items); - assert.equal(media.items[i].media.metadata.albumArtist, audioItem.metadata.albumArtist); - assert.equal(media.items[i].media.metadata.albumName, audioItem.metadata.albumName); - assert.equal(media.items[i].media.metadata.artist, audioItem.metadata.artist); - assert.equal(media.items[i].media.metadata.composer, audioItem.metadata.composer); - assert.equal(media.items[i].media.metadata.title, audioItem.metadata.title); - assert.equal(media.items[i].media.metadata.songName, audioItem.metadata.songName); - assert.equal(media.items[i].media.metadata.releaseDate, audioItem.metadata.releaseDate); - assert.equal(media.items[i].media.metadata.images[0].url, audioItem.metadata.images[0].url); - assert.equal(media.items[i].media.metadata.myMadeUpMetadata, audioItem.metadata.myMadeUpMetadata); - assert.equal(media.items[i].media.metadata.metadataType, chrome.cast.media.MetadataType.MUSIC_TRACK); - assert.equal(media.items[i].media.metadata.type, chrome.cast.media.MetadataType.MUSIC_TRACK); + mediaUtils.assertMediaInfoItemEquals(media.items[i].media, audioItem); media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); utils.testMediaProperties(media); + mediaUtils.assertMediaInfoItemEquals(media.media, audioItem); assert.oneOf(media.playerState, [ chrome.cast.media.PlayerState.PLAYING, chrome.cast.media.PlayerState.BUFFERING]); @@ -280,25 +229,16 @@ assert.isTrue(isAlive); calledOrder(stopped); } - if (media.currentItemId !== media.items[i].itemId && media.media.contentId === videoUrl) { + if (media.currentItemId !== media.items[i].itemId) { i = utils.getCurrentItemIndex(media); media.removeUpdateListener(listener); utils.testMediaProperties(media); assert.equal(media.currentItemId, media.items[i].itemId); utils.testQueueItems(media.items); - assert.equal(media.media.contentId, videoUrl); + mediaUtils.assertMediaInfoItemEquals(media.media, videoItem); assert.equal(media.items.length, 2); checkItems(media.items); - assert.equal(media.items[i].media.contentId, videoUrl); - assert.equal(media.items[i].media.metadata.title, videoItem.metadata.title); - assert.equal(media.items[i].media.metadata.subtitle, videoItem.metadata.subtitle); - assert.equal(media.items[i].media.metadata.originalAirDate, videoItem.metadata.originalAirDate); - assert.equal(media.items[i].media.metadata.episode, videoItem.metadata.episode); - assert.equal(media.items[i].media.metadata.season, videoItem.metadata.season); - assert.equal(media.items[i].media.metadata.seriesTitle, videoItem.metadata.seriesTitle); - assert.equal(media.items[i].media.metadata.images[0].url, videoItem.metadata.images[0].url); - assert.equal(media.items[i].media.metadata.metadataType, chrome.cast.media.MetadataType.TV_SHOW); - assert.equal(media.items[i].media.metadata.type, chrome.cast.media.MetadataType.TV_SHOW); + mediaUtils.assertMediaInfoItemEquals(media.media, videoItem); assert.closeTo(media.getEstimatedTime(), startTime, 5); calledOrder(newMedia); } diff --git a/tests/www/js/tests_manual.js b/tests/www/js/tests_manual.js index 4b3b8a8..85b99ce 100644 --- a/tests/www/js/tests_manual.js +++ b/tests/www/js/tests_manual.js @@ -15,6 +15,7 @@ var assert = window.chai.assert; var utils = window['cordova-plugin-chromecast-tests'].utils; + var mediaUtils = window['cordova-plugin-chromecast-tests'].mediaUtils; mocha.setup({ bail: true, @@ -26,9 +27,6 @@ }); describe('Manual Tests', function () { - var imageUrl = 'https://ia800705.us.archive.org/1/items/GoodHousekeeping193810/Good%20Housekeeping%201938-10.jpg'; - var videoUrl = 'https://ia801302.us.archive.org/1/items/TheWater_201510/TheWater.mp4'; - // callOrder constants that are re-used frequently var success = 'success'; var stopped = 'stopped'; @@ -53,20 +51,8 @@ var cookieName = 'primary-p1_restart-reload'; var runningNum = parseInt(utils.getValue(cookieName) || '0'); var mediaInfo; - before(function () { - mediaInfo = new chrome.cast.media.MediaInfo(videoUrl, 'video/mp4'); - mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata(); - mediaInfo.metadata.title = 'DaTitle'; - mediaInfo.metadata.subtitle = 'DaSubtitle'; - mediaInfo.metadata.releaseDate = new Date(2019, 10, 24).valueOf(); - mediaInfo.metadata.someTrueBoolean = true; - mediaInfo.metadata.someFalseBoolean = false; - mediaInfo.metadata.someSmallNumber = 15; - mediaInfo.metadata.someLargeNumber = 1234567890123456; - mediaInfo.metadata.someSmallDecimal = 15.15; - mediaInfo.metadata.someLargeDecimal = 1234567.123456789; - mediaInfo.metadata.someString = 'SomeString'; - mediaInfo.metadata.images = [new chrome.cast.Image(imageUrl)]; + before('Create MediaInfo', function () { + mediaInfo = mediaUtils.getMediaInfoItem('VIDEO', chrome.cast.media.MetadataType.GENERIC, new Date(2019, 10, 24)); }); it('Create session', function (done) { this.timeout(15000); @@ -128,24 +114,11 @@ media = m; utils.testMediaProperties(media); assert.isUndefined(media.queueData); - assert.equal(media.media.metadata.title, mediaInfo.metadata.title); - assert.equal(media.media.metadata.subtitle, mediaInfo.metadata.subtitle); - assert.equal(media.media.metadata.releaseDate, mediaInfo.metadata.releaseDate); - // TODO figure out how to maintain the data types for custom params on the native side - // so that we don't have to do turn each actual and expected into a string - assert.equal(media.media.metadata.someTrueBoolean + '', mediaInfo.metadata.someTrueBoolean + ''); - assert.equal(media.media.metadata.someFalseBoolean + '', mediaInfo.metadata.someFalseBoolean + ''); - assert.equal(media.media.metadata.someSmallNumber + '', mediaInfo.metadata.someSmallNumber + ''); - assert.equal(media.media.metadata.someLargeNumber + '', mediaInfo.metadata.someLargeNumber + ''); - assert.equal(media.media.metadata.someSmallDecimal + '', mediaInfo.metadata.someSmallDecimal + ''); - assert.equal(media.media.metadata.someLargeDecimal + '', mediaInfo.metadata.someLargeDecimal + ''); - assert.equal(media.media.metadata.someString, mediaInfo.metadata.someString); - assert.equal(media.media.metadata.images[0].url, mediaInfo.metadata.images[0].url); - assert.equal(media.media.metadata.metadataType, chrome.cast.media.MetadataType.GENERIC); - assert.equal(media.media.metadata.type, chrome.cast.media.MetadataType.GENERIC); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); utils.testMediaProperties(media); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); assert.oneOf(media.playerState, [ chrome.cast.media.PlayerState.PLAYING, chrome.cast.media.PlayerState.BUFFERING]); @@ -196,21 +169,7 @@ assert.isAbove(sess.media.length, 0); media = sess.media[0]; assert.isUndefined(media.queueData); - assert.equal(media.media.metadata.title, mediaInfo.metadata.title); - assert.equal(media.media.metadata.subtitle, mediaInfo.metadata.subtitle); - assert.equal(media.media.metadata.releaseDate, mediaInfo.metadata.releaseDate); - // TODO figure out how to maintain the data types for custom params on the native side - // so that we don't have to do turn each actual and expected into a string - assert.equal(media.media.metadata.someTrueBoolean + '', mediaInfo.metadata.someTrueBoolean + ''); - assert.equal(media.media.metadata.someFalseBoolean + '', mediaInfo.metadata.someFalseBoolean + ''); - assert.equal(media.media.metadata.someSmallNumber + '', mediaInfo.metadata.someSmallNumber + ''); - assert.equal(media.media.metadata.someLargeNumber + '', mediaInfo.metadata.someLargeNumber + ''); - assert.equal(media.media.metadata.someSmallDecimal + '', mediaInfo.metadata.someSmallDecimal + ''); - assert.equal(media.media.metadata.someLargeDecimal + '', mediaInfo.metadata.someLargeDecimal + ''); - assert.equal(media.media.metadata.someString, mediaInfo.metadata.someString); - assert.equal(media.media.metadata.images[0].url, mediaInfo.metadata.images[0].url); - assert.equal(media.media.metadata.metadataType, chrome.cast.media.MetadataType.GENERIC); - assert.equal(media.media.metadata.type, chrome.cast.media.MetadataType.GENERIC); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); assert.oneOf(media.playerState, [ chrome.cast.media.PlayerState.PLAYING, chrome.cast.media.PlayerState.BUFFERING]); @@ -252,6 +211,7 @@ media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); assert.notEqual(media.playerState, chrome.cast.media.PlayerState.IDLE); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); if (media.playerState === chrome.cast.media.PlayerState.PAUSED) { media.removeUpdateListener(listener); called(update); @@ -259,6 +219,7 @@ }); media.pause(null, function () { assert.equal(media.playerState, chrome.cast.media.PlayerState.PAUSED); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); called(success); }, function (err) { assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); @@ -306,21 +267,7 @@ assert.isAbove(sess.media.length, 0); media = sess.media[0]; assert.isUndefined(media.queueData); - assert.equal(media.media.metadata.title, mediaInfo.metadata.title); - assert.equal(media.media.metadata.subtitle, mediaInfo.metadata.subtitle); - assert.equal(media.media.metadata.releaseDate, mediaInfo.metadata.releaseDate); - // TODO figure out how to maintain the data types for custom params on the native side - // so that we don't have to do turn each actual and expected into a string - assert.equal(media.media.metadata.someTrueBoolean + '', mediaInfo.metadata.someTrueBoolean + ''); - assert.equal(media.media.metadata.someFalseBoolean + '', mediaInfo.metadata.someFalseBoolean + ''); - assert.equal(media.media.metadata.someSmallNumber + '', mediaInfo.metadata.someSmallNumber + ''); - assert.equal(media.media.metadata.someLargeNumber + '', mediaInfo.metadata.someLargeNumber + ''); - assert.equal(media.media.metadata.someSmallDecimal + '', mediaInfo.metadata.someSmallDecimal + ''); - assert.equal(media.media.metadata.someLargeDecimal + '', mediaInfo.metadata.someLargeDecimal + ''); - assert.equal(media.media.metadata.someString, mediaInfo.metadata.someString); - assert.equal(media.media.metadata.images[0].url, mediaInfo.metadata.images[0].url); - assert.equal(media.media.metadata.metadataType, chrome.cast.media.MetadataType.GENERIC); - assert.equal(media.media.metadata.type, chrome.cast.media.MetadataType.GENERIC); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); assert.equal(media.playerState, chrome.cast.media.PlayerState.PAUSED); called(session_listener); }, function receiverListener (availability) { @@ -360,6 +307,7 @@ media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); assert.notEqual(media.playerState, chrome.cast.media.PlayerState.IDLE); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); if (media.playerState === chrome.cast.media.PlayerState.PLAYING) { media.removeUpdateListener(listener); called(update); @@ -369,6 +317,7 @@ assert.oneOf(media.playerState, [ chrome.cast.media.PlayerState.PLAYING, chrome.cast.media.PlayerState.BUFFERING]); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); called(success); }, function (err) { assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); @@ -412,21 +361,7 @@ assert.isAbove(sess.media.length, 0); media = sess.media[0]; assert.isUndefined(media.queueData); - assert.equal(media.media.metadata.title, mediaInfo.metadata.title); - assert.equal(media.media.metadata.subtitle, mediaInfo.metadata.subtitle); - assert.equal(media.media.metadata.releaseDate, mediaInfo.metadata.releaseDate); - // TODO figure out how to maintain the data types for custom params on the native side - // so that we don't have to do turn each actual and expected into a string - assert.equal(media.media.metadata.someTrueBoolean + '', mediaInfo.metadata.someTrueBoolean + ''); - assert.equal(media.media.metadata.someFalseBoolean + '', mediaInfo.metadata.someFalseBoolean + ''); - assert.equal(media.media.metadata.someSmallNumber + '', mediaInfo.metadata.someSmallNumber + ''); - assert.equal(media.media.metadata.someLargeNumber + '', mediaInfo.metadata.someLargeNumber + ''); - assert.equal(media.media.metadata.someSmallDecimal + '', mediaInfo.metadata.someSmallDecimal + ''); - assert.equal(media.media.metadata.someLargeDecimal + '', mediaInfo.metadata.someLargeDecimal + ''); - assert.equal(media.media.metadata.someString, mediaInfo.metadata.someString); - assert.equal(media.media.metadata.images[0].url, mediaInfo.metadata.images[0].url); - assert.equal(media.media.metadata.metadataType, chrome.cast.media.MetadataType.GENERIC); - assert.equal(media.media.metadata.type, chrome.cast.media.MetadataType.GENERIC); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); assert.equal(media.playerState, chrome.cast.media.PlayerState.PLAYING); called(session_listener); }, function receiverListener (availability) { diff --git a/tests/www/lib/mediaGenerateAndAssert.js b/tests/www/lib/mediaGenerateAndAssert.js new file mode 100644 index 0000000..25dd1c6 --- /dev/null +++ b/tests/www/lib/mediaGenerateAndAssert.js @@ -0,0 +1,180 @@ +/** + * Utility functions for creating media requests and checking returned media + * states. + */ + +(function () { + 'use strict'; + /* eslint-env mocha */ + /* global chrome */ + var assert = window.chai.assert; + + var audioUrl = 'https://ia800306.us.archive.org/26/items/1939RadioNews/1939-10-24-CBS-Elmer-Davis-Reports-City-Of-Flint-Still-Missing.mp3'; + var imageUrl = 'https://ia800705.us.archive.org/1/items/GoodHousekeeping193810/Good%20Housekeeping%201938-10.jpg'; + var videoUrl = 'https://ia801302.us.archive.org/1/items/TheWater_201510/TheWater.mp4'; + + var mediaUtils = { + CONTENT_TYPE: { + 'VIDEO': function () { + return new chrome.cast.media.MediaInfo(videoUrl, 'video/mp4'); + }, + 'AUDIO': function () { + return new chrome.cast.media.MediaInfo(audioUrl, 'audio/mpeg'); + }, + 'IMAGE': function () { + return new chrome.cast.media.MediaInfo(imageUrl, 'image/jpeg'); + } + } + }; + + /** + * Returns a new media item for use in requests. + * + * @param {*} contentType - Must be a string matching one of + * mediaUtils.CONTENT_TYPE or a chrome.cast.media.MediaInfo object. + * @param {chrome.cast.media.*Metadata} metadataType - (optional) Must be a + * chrome.cast.media.*Metadata object, or null. + * @param {Date} metadataDate - (optional) Used for any metadata fields where + * you would prefer the date to be constant. + */ + mediaUtils.getMediaInfoItem = function (contentType, metadataType, metadataDate) { + // Get the content + if (mediaUtils.CONTENT_TYPE[contentType]) { + contentType = mediaUtils.CONTENT_TYPE[contentType](); + } else { + assert.instanceOf(contentType, chrome.cast.cordova.MediaInfo); + } + // Get the metadata + if (metadataType !== undefined && metadataType !== null) { + contentType.metadata = generateMetadata(metadataType, metadataDate); + } + return contentType; + }; + + /** + * Asserts that the 2 MediaInfo items are equivalent. + * @param {chrome.cast.media.MediaInfo} actual + * @param {chrome.cast.media.MediaInfo} expected + */ + mediaUtils.assertMediaInfoItemEquals = function (actual, expected) { + // Test MediaInfo direct properties + assert.equal(actual.contentId, expected.contentId); + if (!actual.metadata && !expected.metadata) { + return; // No metadata to check + } + // Test common *Metadata properties + assert.equal(actual.metadata.images[0].url, expected.metadata.images[0].url); + assert.equal(actual.metadata.metadataType, expected.metadata.metadataType); + assert.equal(actual.metadata.queueItemId, expected.metadata.queueItemId); + assert.equal(actual.metadata.sectionDuration, expected.metadata.sectionDuration); + assert.equal(actual.metadata.sectionStartAbsoluteTime, expected.metadata.sectionStartAbsoluteTime); + assert.equal(actual.metadata.sectionStartTimeInContainer, expected.metadata.sectionStartTimeInContainer); + assert.equal(actual.metadata.sectionStartTimeInMedia, expected.metadata.sectionStartTimeInMedia); + assert.equal(actual.metadata.title, expected.metadata.title); + assert.equal(actual.metadata.type, expected.metadata.type); + assert.equal(actual.metadata.xMyMadeUpMetadata, expected.metadata.xMyMadeUpMetadata); + // Test unique Metadata properties + switch (actual.metadata.type) { + case chrome.cast.media.MetadataType.AUDIOBOOK_CHAPTER: + assert.equal(actual.metadata.bookTitle, expected.metadata.bookTitle); + assert.equal(actual.metadata.chapterNumber, expected.metadata.chapterNumber); + assert.equal(actual.metadata.chapterTitle, expected.metadata.chapterTitle); + assert.equal(actual.metadata.subtitle, expected.metadata.subtitle); + break; + case chrome.cast.media.MetadataType.GENERIC: + assert.equal(actual.metadata.releaseDate, expected.metadata.releaseDate); + assert.equal(actual.metadata.releaseDate, expected.metadata.releaseDate); + break; + case chrome.cast.media.MetadataType.MOVIE: + assert.equal(actual.metadata.studio, expected.metadata.studio); + break; + case chrome.cast.media.MetadataType.MUSIC_TRACK: + assert.equal(actual.metadata.albumArtist, expected.metadata.albumArtist); + assert.equal(actual.metadata.albumName, expected.metadata.albumName); + assert.equal(actual.metadata.artist, expected.metadata.artist); + assert.equal(actual.metadata.composer, expected.metadata.composer); + assert.equal(actual.metadata.songName, expected.metadata.songName); + assert.equal(actual.metadata.releaseDate, expected.metadata.releaseDate); + break; + case chrome.cast.media.MetadataType.PHOTO: + assert.equal(actual.metadata.artist, expected.metadata.artist); + assert.equal(actual.metadata.height, expected.metadata.height); + assert.equal(actual.metadata.creationDateTime, expected.metadata.creationDateTime); + assert.equal(actual.metadata.latitude, expected.metadata.latitude); + assert.equal(actual.metadata.location, expected.metadata.location); + assert.equal(actual.metadata.longitude, expected.metadata.longitude); + assert.equal(actual.metadata.width, expected.metadata.width); + break; + case chrome.cast.media.MetadataType.TV_SHOW: + assert.equal(actual.metadata.episode, expected.metadata.episode); + assert.equal(actual.metadata.originalAirDate, expected.metadata.originalAirDate); + assert.equal(actual.metadata.season, expected.metadata.season); + assert.equal(actual.metadata.seriesTitle, expected.metadata.seriesTitle); + assert.equal(actual.metadata.subtitle, expected.metadata.subtitle); + break; + default: + assert.fail('Unknown metadata type: "' + actual.metadata.type + '"'); + } + }; + + function generateMetadata (metadataType, metadataDate) { + var metadata; + metadataDate = (metadataDate && metadataDate.valueOf()) || new Date().valueOf(); + switch (metadataType) { + case chrome.cast.media.MetadataType.AUDIOBOOK_CHAPTER: + metadata = new chrome.cast.media.AudiobookChapterMediaMetadata(); + metadata.bookTitle = 'AudiobookBookTitle'; + metadata.chapterNumber = 12; + metadata.chapterTitle = 'AudiobookChapterTitle'; + metadata.subtitle = 'AudiobookSubtitle'; + break; + case chrome.cast.media.MetadataType.GENERIC: + metadata = new chrome.cast.media.GenericMediaMetadata(); + metadata.releaseDate = metadataDate; + metadata.subtitle = 'GenericSubtitle'; + break; + case chrome.cast.media.MetadataType.MOVIE: + metadata = new chrome.cast.media.MovieMediaMetadata(); + metadata.studio = 'MovieStudio'; + metadata.subtitle = 'MovieSubtitle'; + break; + case chrome.cast.media.MetadataType.MUSIC_TRACK: + metadata = new chrome.cast.media.MusicTrackMediaMetadata(); + metadata.albumArtist = 'MusicAlbumArtist'; + metadata.albumName = 'MusicAlbum'; + metadata.artist = 'MusicArtist'; + metadata.composer = 'MusicComposer'; + metadata.releaseDate = metadataDate; + metadata.songName = 'MusicSongName'; + break; + case chrome.cast.media.MetadataType.PHOTO: + metadata = new chrome.cast.media.PhotoMediaMetadata(); + metadata.artist = 'PhotoArtist'; + metadata.height = 100; + metadata.creationDateTime = metadataDate; + metadata.latitude = 102.13; + metadata.location = 'PhotoLocation'; + metadata.longitude = 101.12; + metadata.width = 100; + break; + case chrome.cast.media.MetadataType.TV_SHOW: + metadata = new chrome.cast.media.TvShowMediaMetadata(); + metadata.episode = 15; + metadata.originalAirDate = metadataDate; + metadata.season = 2; + metadata.seriesTitle = 'TvSeries'; + metadata.subtitle = 'TvSubtitle'; + break; + default: + assert.fail('Unknown metadata type: "' + metadataType + '"'); + } + // Add common metadata + metadata.images = [new chrome.cast.Image(imageUrl)]; + metadata.title = 'Title-' + metadata.type; + metadata.xMyMadeUpMetadata = 'MyMadeUpMetadata-' + metadata.type; + return metadata; + } + + window['cordova-plugin-chromecast-tests'] = window['cordova-plugin-chromecast-tests'] || {}; + window['cordova-plugin-chromecast-tests'].mediaUtils = mediaUtils; +}()); From e39a84880e419663be6eb9c06cb49be3b135ad49 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sat, 31 Oct 2020 01:44:26 -0600 Subject: [PATCH 16/35] (android) Fix queue-related bug: If you have a session and media loaded, then reload the page and load a queue (from main device, or from an external) the first returned media did not have any items. --- src/android/ChromecastSession.java | 23 ++++++++++---- tests/www/js/tests_manual.js | 50 ++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/android/ChromecastSession.java b/src/android/ChromecastSession.java index f17b16f..99a1c48 100644 --- a/src/android/ChromecastSession.java +++ b/src/android/ChromecastSession.java @@ -492,13 +492,24 @@ void refreshQueueItems() { // Reset lookingForIndexes lookingForIndexes = new ArrayList<>(); - // Only add indexes to look for it the currentItemIndex is valid - if (index != -1) { - // init i-1, i, i+1 (exclude items out of range), so always 2-3 items - for (int i = index - 1; i <= index + 1; i++) { - if (i >= 0 && i < len) { - lookingForIndexes.add(i); + // If we don't know the currentItemIndex, retry on queueStatusUpdated + // To be careful, only when we are expecting a queueRelodCallback and + // queueStatusUpdatedCallback is not already in use. + // (2nd+3rd conditions may be unnecessary) + if (index == -1 && queueReloadCallback != null && queueStatusUpdatedCallback == null) { + setQueueStatusUpdatedCallback(new Runnable() { + @Override + public void run() { + refreshQueueItems(); } + }); + return; + } + // Else, we can get the 2-3 items that are around the currentItem index! + // init i-1, i, i+1 (exclude items out of range), so always 2-3 items + for (int i = index - 1; i <= index + 1; i++) { + if (i >= 0 && i < len) { + lookingForIndexes.add(i); } } checkLookingForIndexes(); diff --git a/tests/www/js/tests_manual.js b/tests/www/js/tests_manual.js index 85b99ce..0be9121 100644 --- a/tests/www/js/tests_manual.js +++ b/tests/www/js/tests_manual.js @@ -191,6 +191,56 @@ return done(); } }); + it('session.queueLoad after page reload should get new media items', function (done) { + this.timeout(15000); + var testNum = 2; + assert.isAtLeast(runningNum, testNum, 'Should not be running this test yet'); + if (runningNum > testNum) { + // We must be looking to run a test further down the line + return done(); + } + // Else, run the test + + var photoItem = mediaUtils.getMediaInfoItem('IMAGE', chrome.cast.media.MetadataType.PHOTO, new Date(2020, 10, 31)); + var request; + + // Add items to the queue + var queue = []; + queue.push(new chrome.cast.media.QueueItem(mediaInfo)); + queue.push(new chrome.cast.media.QueueItem(photoItem)); + + // Create request to repeat all and start at last item + request = new chrome.cast.media.QueueLoadRequest(queue); + session.queueLoad(request, function (m) { + media = m; + console.log(media); + assertQueueProperties(media); + media.addUpdateListener(function listener (isAlive) { + assert.isTrue(isAlive); + assertQueueProperties(media); + assert.oneOf(media.playerState, [ + chrome.cast.media.PlayerState.PLAYING, + chrome.cast.media.PlayerState.BUFFERING]); + if (media.playerState === chrome.cast.media.PlayerState.PLAYING) { + media.removeUpdateListener(listener); + done(); + } + }); + }, function (err) { + assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); + }); + + function assertQueueProperties (media) { + utils.testMediaProperties(media); + assert.isObject(media.queueData); + utils.testQueueItems(media.items); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); + var i = utils.getCurrentItemIndex(media); + assert.equal(i, 0); + mediaUtils.assertMediaInfoItemEquals(media.items[0].media, mediaInfo); + mediaUtils.assertMediaInfoItemEquals(media.items[1].media, photoItem); + } + }); it('media.pause should pause playback', function (done) { this.timeout(15000); var testNum = 2; From 508ce1a63c273640cd4eb4be634dc58d045ef24b Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sat, 31 Oct 2020 06:03:18 -0600 Subject: [PATCH 17/35] (android) [Issue #73] Fix Push Notification stop casting button: Add tests for stop casting and pause from push notification --- src/android/ChromecastConnection.java | 45 +++++++----- tests/www/js/tests_manual.js | 102 +++++++++++++++----------- 2 files changed, 85 insertions(+), 62 deletions(-) diff --git a/src/android/ChromecastConnection.java b/src/android/ChromecastConnection.java index 42d939a..a475ecd 100644 --- a/src/android/ChromecastConnection.java +++ b/src/android/ChromecastConnection.java @@ -34,11 +34,15 @@ public class ChromecastConnection { private Activity activity; /** settings object. */ private SharedPreferences settings; - /** Controls the media. */ - private ChromecastSession media; + /** Controls the chromecastSession. */ + private ChromecastSession chromecastSession; /** Lifetime variable. */ private SessionListener newConnectionListener; + /** Indicates whether we left the session or stopped it. */ + private boolean sessionEndBecauseOfLeave = false; + /** Any callback to call after sessionEnd. */ + private CallbackContext sessionEndCallback = null; /** The Listener callback. */ private Listener listener; @@ -55,7 +59,7 @@ public class ChromecastConnection { this.settings = activity.getSharedPreferences("CORDOVA-PLUGIN-CHROMECAST_ChromecastConnection", 0); this.appId = settings.getString("appId", CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID); this.listener = connectionListener; - this.media = new ChromecastSession(activity, listener); + this.chromecastSession = new ChromecastSession(activity, listener); // Set the initial appId CastOptionsProvider.setAppId(appId); @@ -64,6 +68,19 @@ public class ChromecastConnection { // CastContext and prep it for searching for a session to rejoin // Also adds the receiver update callback getContext().addCastStateListener(listener); + getSessionManager().addSessionManagerListener(new SessionListener() { + @Override + public void onSessionEnded(CastSession castSession, int errCode) { + chromecastSession.setSession(null); + if (sessionEndCallback != null) { + sessionEndCallback.success(); + } + listener.onSessionEnd(ChromecastUtilities.createSessionObject(castSession, sessionEndBecauseOfLeave ? "disconnected" : "stopped")); + // Reset + sessionEndBecauseOfLeave = false; + sessionEndCallback = null; + } + }, CastSession.class); } /** @@ -71,7 +88,7 @@ public class ChromecastConnection { * @return the ChromecastSession object */ ChromecastSession getChromecastSession() { - return this.media; + return chromecastSession; } /** @@ -114,7 +131,7 @@ void onRouteUpdate(List routes) { // If we do have a session if (session != null) { // Let the client know - media.setSession(session); + chromecastSession.setSession(session); listener.onSessionRejoin(ChromecastUtilities.createSessionObject(session)); } } @@ -199,7 +216,7 @@ public void run() { // 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 + // time chromecastSession 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. @@ -372,7 +389,7 @@ private void listenForConnection(ConnectionCallback callback) { @Override public void onSessionStarted(CastSession castSession, String sessionId) { getSessionManager().removeSessionManagerListener(this, CastSession.class); - media.setSession(castSession); + chromecastSession.setSession(castSession); callback.onJoin(ChromecastUtilities.createSessionObject(castSession)); } @Override @@ -472,18 +489,8 @@ public void run() { void endSession(boolean stopCasting, CallbackContext callback) { activity.runOnUiThread(new Runnable() { public void run() { - getSessionManager().addSessionManagerListener(new SessionListener() { - @Override - public void onSessionEnded(CastSession castSession, int error) { - getSessionManager().removeSessionManagerListener(this, CastSession.class); - media.setSession(null); - if (callback != null) { - callback.success(); - } - listener.onSessionEnd(ChromecastUtilities.createSessionObject(castSession, stopCasting ? "stopped" : "disconnected")); - } - }, CastSession.class); - + sessionEndCallback = callback; + sessionEndBecauseOfLeave = !stopCasting; getSessionManager().endCurrentSession(stopCasting); } }); diff --git a/tests/www/js/tests_manual.js b/tests/www/js/tests_manual.js index 0be9121..6941f76 100644 --- a/tests/www/js/tests_manual.js +++ b/tests/www/js/tests_manual.js @@ -11,7 +11,7 @@ (function () { 'use strict'; /* eslint-env mocha */ - /* global chrome */ + /* global chrome cordova */ var assert = window.chai.assert; var utils = window['cordova-plugin-chromecast-tests'].utils; @@ -51,8 +51,20 @@ var cookieName = 'primary-p1_restart-reload'; var runningNum = parseInt(utils.getValue(cookieName) || '0'); var mediaInfo; + var photoItem; + var assertQueueProperties = function (media) { + utils.testMediaProperties(media); + assert.isObject(media.queueData); + utils.testQueueItems(media.items); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); + var i = utils.getCurrentItemIndex(media); + assert.equal(i, 0); + mediaUtils.assertMediaInfoItemEquals(media.items[0].media, mediaInfo); + mediaUtils.assertMediaInfoItemEquals(media.items[1].media, photoItem); + }; before('Create MediaInfo', function () { mediaInfo = mediaUtils.getMediaInfoItem('VIDEO', chrome.cast.media.MetadataType.GENERIC, new Date(2019, 10, 24)); + photoItem = mediaUtils.getMediaInfoItem('IMAGE', chrome.cast.media.MetadataType.PHOTO, new Date(2020, 10, 31)); }); it('Create session', function (done) { this.timeout(15000); @@ -201,16 +213,13 @@ } // Else, run the test - var photoItem = mediaUtils.getMediaInfoItem('IMAGE', chrome.cast.media.MetadataType.PHOTO, new Date(2020, 10, 31)); - var request; - // Add items to the queue var queue = []; queue.push(new chrome.cast.media.QueueItem(mediaInfo)); queue.push(new chrome.cast.media.QueueItem(photoItem)); // Create request to repeat all and start at last item - request = new chrome.cast.media.QueueLoadRequest(queue); + var request = new chrome.cast.media.QueueLoadRequest(queue); session.queueLoad(request, function (m) { media = m; console.log(media); @@ -229,19 +238,8 @@ }, function (err) { assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); }); - - function assertQueueProperties (media) { - utils.testMediaProperties(media); - assert.isObject(media.queueData); - utils.testQueueItems(media.items); - mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); - var i = utils.getCurrentItemIndex(media); - assert.equal(i, 0); - mediaUtils.assertMediaInfoItemEquals(media.items[0].media, mediaInfo); - mediaUtils.assertMediaInfoItemEquals(media.items[1].media, photoItem); - } }); - it('media.pause should pause playback', function (done) { + it('Pause media from notifications', function (done) { this.timeout(15000); var testNum = 2; assert.isAtLeast(runningNum, testNum, 'Should not be running this test yet'); @@ -250,30 +248,30 @@ return done(); } // Else, run the test - - var called = utils.waitForAllCalls([ - { id: success, repeats: false }, - { id: update, repeats: true } - ], function () { - utils.storeValue(cookieName, ++runningNum); - done(); - }); media.addUpdateListener(function listener (isAlive) { assert.isTrue(isAlive); assert.notEqual(media.playerState, chrome.cast.media.PlayerState.IDLE); - mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); + assertQueueProperties(media); if (media.playerState === chrome.cast.media.PlayerState.PAUSED) { media.removeUpdateListener(listener); - called(update); + utils.storeValue(cookieName, ++runningNum); + done(); } }); - media.pause(null, function () { - assert.equal(media.playerState, chrome.cast.media.PlayerState.PAUSED); - mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); - called(success); - }, function (err) { - assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); - }); + if (!utils.isDesktop() && cordova.platformId === 'android') { + utils.setAction('1. Drag down the Android notifications from the top status bar
2. Click the pause button', + 'There is no chromecast notification drop-down', mediaPause); + } else { + mediaPause(); + } + function mediaPause () { + media.pause(null, function () { + assert.equal(media.playerState, chrome.cast.media.PlayerState.PAUSED); + assertQueueProperties(media); + }, function (err) { + assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); + }); + } }); it('Restart app with active session, should receive session on initialize', function (done) { var instructionNum = 3; @@ -313,11 +311,9 @@ function (sess) { session = sess; utils.testSessionProperties(sess); - // // Ensure the media is maintained - assert.isAbove(sess.media.length, 0); + // Ensure the media is maintained media = sess.media[0]; - assert.isUndefined(media.queueData); - mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); + assertQueueProperties(media); assert.equal(media.playerState, chrome.cast.media.PlayerState.PAUSED); called(session_listener); }, function receiverListener (availability) { @@ -407,11 +403,9 @@ function (sess) { session = sess; utils.testSessionProperties(sess); - // // Ensure the media is maintained - assert.isAbove(sess.media.length, 0); - media = sess.media[0]; - assert.isUndefined(media.queueData); - mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); + // Ensure the media is maintained + media = session.media[0]; + assertQueueProperties(media); assert.equal(media.playerState, chrome.cast.media.PlayerState.PLAYING); called(session_listener); }, function receiverListener (availability) { @@ -431,6 +425,28 @@ return done(); } }); + it('Stop session from notifications (android)', function (done) { + session.addUpdateListener(function listener (isAlive) { + if (session.status === chrome.cast.SessionStatus.STOPPED) { + assert.isFalse(isAlive); + session.removeUpdateListener(listener); + session = null; + done(); + } + }); + if (!utils.isDesktop() && cordova.platformId === 'android') { + utils.setAction('1. Drag down the Android notifications from the top status bar
2. Click the "X"', + 'There is no chromecast notification drop-down', sessionStop); + } else { + sessionStop(); + } + function sessionStop () { + session.stop(function () { + }, function (err) { + assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); + }); + } + }); after('Ensure session is stopped', function (done) { // Reset tests utils.storeValue(cookieName, 0); From 07e9c8984c3166ba892466ee7f960eedced38c01 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sun, 1 Nov 2020 19:50:50 -0700 Subject: [PATCH 18/35] (test) WIP Create test for audio live stream [Issue https://github.com/miloproductionsinc/cordova-plugin-chromecast/issues/11] [PR https://github.com/miloproductionsinc/cordova-plugin-chromecast/pull/12] --- tests/www/js/tests_auto.js | 24 ++++++++++++++++++++++++ tests/www/lib/mediaGenerateAndAssert.js | 4 ++++ tests/www/lib/utils.js | 19 ++++++++++++------- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/tests/www/js/tests_auto.js b/tests/www/js/tests_auto.js index 5e521b5..2ade66e 100644 --- a/tests/www/js/tests_auto.js +++ b/tests/www/js/tests_auto.js @@ -964,6 +964,30 @@ assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); }); }); + it('session.loadMedia should be able to load live stream audio media', function (done) { + this.timeout(90000); + var mediaInfo = mediaUtils.getMediaInfoItem('LIVE_AUDIO'); + session.loadMedia(new chrome.cast.media.LoadRequest(mediaInfo), function (m) { + media = m; + utils.testMediaProperties(media, true); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); + media.addUpdateListener(function listener (isAlive) { + assert.isTrue(isAlive); + utils.testMediaProperties(media, true); + mediaUtils.assertMediaInfoItemEquals(media.media, mediaInfo); + assert.oneOf(media.playerState, [ + chrome.cast.media.PlayerState.PLAYING, + chrome.cast.media.PlayerState.BUFFERING]); + if (media.playerState === chrome.cast.media.PlayerState.PLAYING) { + console.log(media); + media.removeUpdateListener(listener); + done(); + } + }); + }, function (err) { + assert.fail('Unexpected Error: ' + err.code + ': ' + err.description); + }); + }); it('session.loadMedia should be able to load remote image and return the PhotoMediaMetadata', function (done) { var mediaInfo = mediaUtils.getMediaInfoItem('IMAGE', chrome.cast.media.MetadataType.PHOTO); session.loadMedia(new chrome.cast.media.LoadRequest(mediaInfo), function (m) { diff --git a/tests/www/lib/mediaGenerateAndAssert.js b/tests/www/lib/mediaGenerateAndAssert.js index 25dd1c6..77f40ac 100644 --- a/tests/www/lib/mediaGenerateAndAssert.js +++ b/tests/www/lib/mediaGenerateAndAssert.js @@ -11,6 +11,7 @@ var audioUrl = 'https://ia800306.us.archive.org/26/items/1939RadioNews/1939-10-24-CBS-Elmer-Davis-Reports-City-Of-Flint-Still-Missing.mp3'; var imageUrl = 'https://ia800705.us.archive.org/1/items/GoodHousekeeping193810/Good%20Housekeeping%201938-10.jpg'; + var liveAudioUrl = 'http://relay.publicdomainproject.org/classical.mp3'; var videoUrl = 'https://ia801302.us.archive.org/1/items/TheWater_201510/TheWater.mp4'; var mediaUtils = { @@ -23,6 +24,9 @@ }, 'IMAGE': function () { return new chrome.cast.media.MediaInfo(imageUrl, 'image/jpeg'); + }, + 'LIVE_AUDIO': function () { + return new chrome.cast.media.MediaInfo(liveAudioUrl, 'audio/mpeg'); } } }; diff --git a/tests/www/lib/utils.js b/tests/www/lib/utils.js index d8bb53e..5ed8cd2 100644 --- a/tests/www/lib/utils.js +++ b/tests/www/lib/utils.js @@ -279,14 +279,14 @@ assert.isFunction(session.loadMedia); }; - utils.testMediaProperties = function (media) { + utils.testMediaProperties = function (media, isLiveStream) { assert.instanceOf(media, chrome.cast.media.Media); assert.isNumber(media.currentItemId); assert.isNumber(media.currentTime); if (media.idleReason) { assert.oneOf(media.idleReason, utils.getObjectValues(chrome.cast.media.IdleReason)); } - utils.testMediaInfoProperties(media.media); + utils.testMediaInfoProperties(media.media, isLiveStream); assert.isNumber(media.mediaSessionId); assert.isNumber(media.playbackRate); assert.oneOf(media.playerState, utils.getObjectValues(chrome.cast.media.PlayerState)); @@ -298,14 +298,19 @@ assert.isFunction(media.removeUpdateListener); }; - utils.testMediaInfoProperties = function (mediaInfo) { + utils.testMediaInfoProperties = function (mediaInfo, isLiveStream) { // queue items contain a subset of identical properties utils.testQueueItemMediaInfoProperties(mediaInfo); // properties that are exclusive (or mandatory) to media.media - assert.isNumber(mediaInfo.duration); - if (mediaInfo.contentType.toLowerCase().indexOf('video') > -1 - || mediaInfo.contentType.toLowerCase().indexOf('audio') > -1) { - assert.isAbove(mediaInfo.duration, 0); + if (isLiveStream) { + // Live stream has null duration + assert.isNull(mediaInfo.duration); + } else { + assert.isNumber(mediaInfo.duration); + if (mediaInfo.contentType.toLowerCase().indexOf('video') > -1 + || mediaInfo.contentType.toLowerCase().indexOf('audio') > -1) { + assert.isAbove(mediaInfo.duration, 0); + } } assert.isArray(mediaInfo.tracks); }; From e1eb92045a6a157b3418a6efd348001fddb1d7ad Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sun, 1 Nov 2020 20:44:40 -0700 Subject: [PATCH 19/35] (android) WIP support live streams [Issue https://github.com/miloproductionsinc/cordova-plugin-chromecast/issues/11] [PR https://github.com/miloproductionsinc/cordova-plugin-chromecast/pull/12] --- src/android/ChromecastUtilities.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/android/ChromecastUtilities.java b/src/android/ChromecastUtilities.java index 549bdfb..17ea3fa 100644 --- a/src/android/ChromecastUtilities.java +++ b/src/android/ChromecastUtilities.java @@ -648,7 +648,12 @@ private static JSONObject createMediaInfoObject(MediaInfo mediaInfo) { out.put("contentId", mediaInfo.getContentId()); out.put("contentType", mediaInfo.getContentType()); out.put("customData", mediaInfo.getCustomData()); - out.put("duration", mediaInfo.getStreamDuration() / 1000.0); + long duration = mediaInfo.getStreamDuration(); + if (duration == -1) { + out.put("duration", null); + } else { + out.put("duration", duration / 1000.0); + } //out.put("mediaCategory",); out.put("metadata", createMetadataObject(mediaInfo.getMetadata())); out.put("streamType", ChromecastUtilities.getMediaInfoStreamType(mediaInfo)); From ed7d9be5741dbf168b74cdc99f3e2c587d104006 Mon Sep 17 00:00:00 2001 From: th3hamm0r Date: Mon, 3 Feb 2020 15:24:23 +0100 Subject: [PATCH 20/35] (ios) Fixed JSON serialization for streams with infinite stream duration [Issue https://github.com/miloproductionsinc/cordova-plugin-chromecast/issues/11] [PR https://github.com/miloproductionsinc/cordova-plugin-chromecast/pull/12] --- src/ios/MLPCastUtilities.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ios/MLPCastUtilities.m b/src/ios/MLPCastUtilities.m index 4a0a5be..dd9e536 100644 --- a/src/ios/MLPCastUtilities.m +++ b/src/ios/MLPCastUtilities.m @@ -585,7 +585,7 @@ + (NSDictionary *)createMediaInfoObject:(GCKMediaInformation *)mediaInfo { returnDict[@"contentId"] = mediaInfo.contentID? mediaInfo.contentID : mediaInfo.contentURL.absoluteString; returnDict[@"contentType"] = mediaInfo.contentType; returnDict[@"customData"] = mediaInfo.customData == nil ? @{} : mediaInfo.customData; - returnDict[@"duration"] = @(mediaInfo.streamDuration); + returnDict[@"duration"] = mediaInfo.streamDuration == INFINITY ? nil : @(mediaInfo.streamDuration); returnDict[@"metadata" ] = [MLPCastUtilities createMetadataObject:mediaInfo.metadata]; returnDict[@"streamType"] = [MLPCastUtilities getStreamType:mediaInfo.streamType]; returnDict[@"tracks"] = [MLPCastUtilities getMediaTracks:mediaInfo.mediaTracks]; From d3995be39d8e411a5754c46c9c582d4d3b2755c4 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Malatrasi Date: Thu, 26 Mar 2020 09:32:55 +0100 Subject: [PATCH 21/35] Change cocoapods URL --- plugin.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.xml b/plugin.xml index 6fe7a68..2249ec4 100644 --- a/plugin.xml +++ b/plugin.xml @@ -56,7 +56,7 @@ - + From 281733ece51f9a78244e0446fc150fe40d9e1342 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sun, 1 Nov 2020 21:36:22 -0700 Subject: [PATCH 22/35] Update Google Cast SDKs --- plugin.xml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugin.xml b/plugin.xml index 2249ec4..f646643 100644 --- a/plugin.xml +++ b/plugin.xml @@ -34,10 +34,7 @@ - - - - + @@ -59,7 +56,7 @@ - + From 34949c56d7e87563928d4147c1f2726fd28dddb3 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sun, 1 Nov 2020 22:28:48 -0700 Subject: [PATCH 23/35] [v1.1.0] --- RELEASENOTES.md | 34 ++++++++++++++++++++++++++++++++++ package.json | 2 +- plugin.xml | 2 +- 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 RELEASENOTES.md diff --git a/RELEASENOTES.md b/RELEASENOTES.md new file mode 100644 index 0000000..7f1286b --- /dev/null +++ b/RELEASENOTES.md @@ -0,0 +1,34 @@ + +## Release Notes for cordova-plugin-chromecast + +### 1.1.0 (2020-11-1) + +* Update Google Cast SDKs (iOS -> 4.4.8, android -> 19.0.0) +* (android) simulate mediaSessionId +* Add Audiobook chapter metadata +* (android) Fix queue bug: media returned with no items +* (android) [Issue #73] Fix Push Notification stop casting button +* [Live stream issue](https://github.com/miloproductionsinc/cordova-plugin-chromecast/issues/11) Fix for live stream media + +### 1.0.0 (2020-01-24) + +* For full list of changes, see PR #54 diff --git a/package.json b/package.json index cf8c2f2..34c0f61 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-chromecast", - "version": "1.1.0-dev", + "version": "1.1.0", "scripts": { "host-chrome-tests": "node tests/www/chrome/host-tests.js", "style-js": "npx eslint --ignore-pattern tests/www/vendor www tests/www doc/example.js", diff --git a/plugin.xml b/plugin.xml index f646643..f5fe164 100644 --- a/plugin.xml +++ b/plugin.xml @@ -2,7 +2,7 @@ + version="1.1.0"> From 5cef01790182b29890e220eec2a84d44cb99aeea Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sat, 7 Nov 2020 01:19:23 -0700 Subject: [PATCH 24/35] [v1.1.0-dev] --- package.json | 2 +- plugin.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 34c0f61..cf8c2f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-chromecast", - "version": "1.1.0", + "version": "1.1.0-dev", "scripts": { "host-chrome-tests": "node tests/www/chrome/host-tests.js", "style-js": "npx eslint --ignore-pattern tests/www/vendor www tests/www doc/example.js", diff --git a/plugin.xml b/plugin.xml index f5fe164..230ca70 100644 --- a/plugin.xml +++ b/plugin.xml @@ -2,7 +2,7 @@ + version="1.1.0-dev"> From 7aac33d22292d4e2c41738f57d5a7be20fd38e0c Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sat, 7 Nov 2020 00:51:49 -0700 Subject: [PATCH 25/35] (doc) Add a default comment to the required config.xml attributes so that developers can easily remember what these entries in their config.xml are for --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b8569f6..76a7e3b 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,16 @@ pod install To **distribute** an iOS app with this plugin you must add usage descriptions to your project's `config.xml`. These strings will be used when asking the user for permission to use the microphone and bluetooth. ```xml - - + + Bluetooth is required to scan for nearby Chromecast devices with guest mode enabled. - + Bluetooth is required to scan for nearby Chromecast devices with guest mode enabled. - + The microphone is required to pair with nearby Chromecast devices with guest mode enabled. From b198e5ce877c446a167c79c1254e0a80ead6160e Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sat, 7 Nov 2020 01:02:53 -0700 Subject: [PATCH 26/35] (ios) Update iOS Google Cast SDK - minimum supported iOS is now 10.0 --- README.md | 19 ++++++++++++++++--- plugin.xml | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 76a7e3b..59ed569 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ pod install ### Additional iOS Installation Instructions To **distribute** an iOS app with this plugin you must add usage descriptions to your project's `config.xml`. -These strings will be used when asking the user for permission to use the microphone and bluetooth. +The "*Description" key strings will be used when asking the user for permission to use the microphone/bluetooth/local network. ```xml @@ -30,13 +30,26 @@ These strings will be used when asking the user for permission to use the microp The microphone is required to pair with nearby Chromecast devices with guest mode enabled. + + + The local network permission is required to discover Cast-enabled devices on your WiFi network. + + + + _googlecast._tcp + + _CC1AD845._googlecast._tcp + + + + ``` # Supports -**Android** 4.4+ (7.x highest confirmed) (may support lower, untested) -**iOS** 9.0+ (14.1 highest confirmed) +**Android** 4.4+ (may support lower, untested) +**iOS** 10.0+ (The [Google Cast iOS Sender SDK 4.5.0](https://developers.google.com/cast/docs/release-notes#september-14,-2020) says iOS 10+ but all tests on the plugin work fine for iOS 9.3.5, so it appears to work on iOs 9 anyways. :/) ## Quirks * Android 4.4 (maybe 5.x and 6.x) are not able automatically rejoin/resume a chromecast session after an app restart. diff --git a/plugin.xml b/plugin.xml index 230ca70..e0c2163 100644 --- a/plugin.xml +++ b/plugin.xml @@ -56,7 +56,7 @@ - + From b655e468434e83a18ce70328fb0c8290398338b0 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sat, 7 Nov 2020 01:04:02 -0700 Subject: [PATCH 27/35] (ios) remove unused (now useless) "com.apple.developer.networking.wifi-info" setting --- plugin.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugin.xml b/plugin.xml index e0c2163..547b211 100644 --- a/plugin.xml +++ b/plugin.xml @@ -60,13 +60,6 @@ - - - - - - - From 4dbb43b4630c974589408726d8b79d57ed8444ec Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sat, 7 Nov 2020 01:23:12 -0700 Subject: [PATCH 28/35] [v2.0.0] --- RELEASENOTES.md | 9 +++++++++ package.json | 2 +- plugin.xml | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7f1286b..2690111 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -20,9 +20,18 @@ --> ## Release Notes for cordova-plugin-chromecast +### 2.0.0 (2020-11-07) + +* (ios) BREAKING - Update Google Cast SDK (iOS Sender -> 4.5.2) + * Google Cast SDK - [iOS sender 4.5.0+](https://developers.google.com/cast/docs/release-notes#september-14,-2020) has minimum iOS 10 + * But, all tests on the plugin work fine for iOS 9.3.5, so it appears to work on iOS 9 anyways. :/ + * But, since cordova@6.x.x no longer supports iOS 9+10 we will only be testing on iOS 11+. + * With the update, additional entries are required in `config.xml` for cast to work on iOs 14 (if built with Xcode 12+) (see README.md) + ### 1.1.0 (2020-11-1) * Update Google Cast SDKs (iOS -> 4.4.8, android -> 19.0.0) + * New SDK supports casting to Android TV (untested) * (android) simulate mediaSessionId * Add Audiobook chapter metadata * (android) Fix queue bug: media returned with no items diff --git a/package.json b/package.json index cf8c2f2..bada6db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-chromecast", - "version": "1.1.0-dev", + "version": "2.0.0", "scripts": { "host-chrome-tests": "node tests/www/chrome/host-tests.js", "style-js": "npx eslint --ignore-pattern tests/www/vendor www tests/www doc/example.js", diff --git a/plugin.xml b/plugin.xml index 547b211..90b8773 100644 --- a/plugin.xml +++ b/plugin.xml @@ -2,7 +2,7 @@ + version="2.0.0"> From 0250501e882f90a4d866c98480600659f7300206 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sat, 28 Nov 2020 13:28:24 -0700 Subject: [PATCH 29/35] Update README.md with correct url for running browser tests --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 59ed569..6a0ade2 100644 --- a/README.md +++ b/README.md @@ -221,7 +221,7 @@ They use the google provided cast_sender.js. These are particularly useful for ensuring we are following the [official Google Cast API for Chrome](https://developers.google.com/cast/docs/reference/chrome#chrome.cast) correctly. To run the tests: * run: `npm run host-chrome-tests [port default=8432]` -* Navigate to: [http://localhost:8432/chrome/tests_chrome.html](http://localhost:8432/chrome/tests_chrome.html) +* Navigate to: [http://localhost:8432/html/tests.html](http://localhost:8432/html/tests.html) ## Contributing From 94aa4a626c2a02cdad1a6835c283380f37468663 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sat, 28 Nov 2020 14:56:58 -0700 Subject: [PATCH 30/35] (ios) fix crash when media is loaded without metadata --- src/ios/MLPCastUtilities.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ios/MLPCastUtilities.m b/src/ios/MLPCastUtilities.m index dd9e536..831423d 100644 --- a/src/ios/MLPCastUtilities.m +++ b/src/ios/MLPCastUtilities.m @@ -118,6 +118,9 @@ + (GCKMediaTextTrackStyle *)buildTextTrackStyle:(NSDictionary *)data { } +(GCKMediaMetadata*)buildMediaMetadata:(NSDictionary*)data { + if ([data isEqual:[NSNull null]] || data == nil) { + return nil; + } GCKMediaMetadata* mediaMetaData = [[GCKMediaMetadata alloc] initWithMetadataType:GCKMediaMetadataTypeGeneric]; if (data[@"metadataType"]) { From 6bee8d6fcbb031913e5116cdf7507d178b5ba99a Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sat, 28 Nov 2020 14:57:10 -0700 Subject: [PATCH 31/35] [v2.0.1] --- RELEASENOTES.md | 4 ++++ package.json | 2 +- plugin.xml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2690111..b0c973a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -20,6 +20,10 @@ --> ## Release Notes for cordova-plugin-chromecast +### 2.0.1 (2020-11-28) + +* (ios) Bug Fix - media loaded without any metadata caused crash + ### 2.0.0 (2020-11-07) * (ios) BREAKING - Update Google Cast SDK (iOS Sender -> 4.5.2) diff --git a/package.json b/package.json index bada6db..fe5baa1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-chromecast", - "version": "2.0.0", + "version": "2.0.1", "scripts": { "host-chrome-tests": "node tests/www/chrome/host-tests.js", "style-js": "npx eslint --ignore-pattern tests/www/vendor www tests/www doc/example.js", diff --git a/plugin.xml b/plugin.xml index 90b8773..fe27132 100644 --- a/plugin.xml +++ b/plugin.xml @@ -2,7 +2,7 @@ + version="2.0.1"> From 785c41e265fac17a1dca1994b2d17537915b76ed Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sat, 28 Nov 2020 16:39:28 -0700 Subject: [PATCH 32/35] README.md - Add links to test videos --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 6a0ade2..8a6b6b4 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,13 @@ Run `npm test` to ensure your code fits the styling. It will also find some err * If errors are found, you can try running `npm run style`, this will attempt to automatically fix the errors. +You can view what the plug tests should look like here: +* [Auto Tests - Desktop Chrome](https://youtu.be/CdUwFrEht_A) +* [Auto Tests - Android or iOS](https://youtu.be/VUtiXee6m_8) +* [Manual Tests - Android or iOS](https://youtu.be/cgyOpBRXdEI) +* [Interaction Tests - Android & iOS](https://youtu.be/rphp_s5ruzM) +* [Interaction Tests - Android (or iOS) & Desktop Chrome](https://youtu.be/1ccBHqeMLhs) + ### Tests Mobile Requirements: From 1b94f782c6af3a26e774fc6ef9cc6b3efe1c9c14 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Sun, 29 Nov 2020 19:46:55 -0700 Subject: [PATCH 33/35] Add link to chromecast icon assets --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 8a6b6b4..90e1a0f 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,9 @@ The "*Description" key strings will be used when asking the user for permission ``` +## Chromecast Icon Assets +[chromecast-assets.zip](https://github.com/jellyfin/cordova-plugin-chromecast/wiki/chromecast-assets.zip) + # Supports **Android** 4.4+ (may support lower, untested) From 1feef1580ccc8b7bc6467e687698da9998f91ed4 Mon Sep 17 00:00:00 2001 From: Enrico Dente Date: Thu, 11 Feb 2021 22:19:53 +0100 Subject: [PATCH 34/35] Fixed issue when using the sendMessage method on iOS implementation --- src/ios/MLPChromecastSession.m | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ios/MLPChromecastSession.m b/src/ios/MLPChromecastSession.m index 1cc71f1..8aa0799 100644 --- a/src/ios/MLPChromecastSession.m +++ b/src/ios/MLPChromecastSession.m @@ -214,21 +214,24 @@ - (void)createMessageChannelWithCommand:(CDVInvokedUrlCommand*)command namespace [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } -- (void)sendMessageWithCommand:(CDVInvokedUrlCommand*)command namespace:(NSString*)namespace message:(NSString*)message { - GCKGenericChannel* channel = self.genericChannels[namespace]; - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[NSString stringWithFormat:@"Namespace %@ not founded",namespace]]; +- (void)sendMessageWithCommand:(CDVInvokedUrlCommand*)command namespace:(NSString*)namespace message:(NSString*)message{ + + GCKGenericChannel* newChannel = [[GCKGenericChannel alloc] initWithNamespace:namespace]; + newChannel.delegate = self; + self.genericChannels[namespace] = newChannel; + [currentSession addChannel:newChannel]; + + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[NSString stringWithFormat:@"Namespace %@ not found",namespace]]; - if (channel != nil) { + if(newChannel != nil) { GCKError* error = nil; - [channel sendTextMessage:message error:&error]; + [newChannel sendTextMessage:message error:&error]; if (error != nil) { pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.description]; } else { pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; } } - - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } - (void)mediaSeekWithCommand:(CDVInvokedUrlCommand*)command position:(NSTimeInterval)position resumeState:(GCKMediaResumeState)resumeState { From d82f07e26a53df6322043403e4dc98bb2eeb4e39 Mon Sep 17 00:00:00 2001 From: Lindsay-Needs-Sleep Date: Fri, 28 May 2021 20:09:16 -0600 Subject: [PATCH 35/35] README - Add notice asking for new person to maintain --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 90e1a0f..b94a014 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@

cordova-plugin-chromecast

Control Chromecast from your Cordova app

+--- + +### NOTICE: This isn't really actively mainted, if you would like be the maintainer of **cordova-plugin-chromecast**, please fork and submit a PR to change this notice to point to your fork! + +--- + # Installation ```