diff --git a/DIFFERENCE.md b/DIFFERENCE.md new file mode 100644 index 00000000..c127189b --- /dev/null +++ b/DIFFERENCE.md @@ -0,0 +1,13 @@ +# Difference from [original repository](https://github.com/katzer/cordova-plugin-background-mode) +This repository is trying to solve issues with Windows compatibility of original plugin (mainly by finding workarounds for [issue #222](https://github.com/katzer/cordova-plugin-background-mode/issues/222)). +## Changes +1. Plugin hook to add windows capability `backgroundMediaPlayback` into windows `*.appxmanifest` files +1. Usage of `Windows.ApplicationModel.ExtendedExecution` functionality to keep app running when minimized +1. Starting audio playback automatically (to keep application running; quiet, but playing) - *might have performance & battery impact* +## Upsides +1. Plugin works for Windows applications (Desktop windows tested, haven't tested @mobile) +1. You can pause application background tasks by clicking pause button under application hover-preview in taskbar (side effect of being "media-app") +## Downsides +1. Windows target is not buildable by `[ionic] cordova build|run windows` with package cordova-windows in version lower than 6.0 +1. When you're removing plugin, capability of `backgroundMediaPlayback` is kept in `*.appxmanifest` files, which means, that app is still not buildable by Cordova CLI (cordova-windows lower than 6.0; to fix it, remove/readd windows platform to project after plugin removal) +1. Possible severe performance issues/battery drains, because of continuous unstopped playback diff --git a/package.json b/package.json index ebbedf34..6e9cecc7 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "platforms": [ "ios", "android", - "browser" + "browser", + "windows" ] }, "repository": { @@ -20,7 +21,8 @@ "ecosystem:cordova", "cordova-ios", "cordova-android", - "cordova-browser" + "cordova-browser", + "cordova-windows" ], "engines": [ { diff --git a/plugin.xml b/plugin.xml index 52bd90e9..736bf633 100644 --- a/plugin.xml +++ b/plugin.xml @@ -90,7 +90,7 @@ target-dir="src/de/appplant/cordova/plugin/background" /> - @@ -98,10 +98,6 @@ - - - - @@ -114,7 +110,10 @@ - --> + + + + diff --git a/scripts/hook-windows-background-capability.js b/scripts/hook-windows-background-capability.js new file mode 100644 index 00000000..2fc9fcbc --- /dev/null +++ b/scripts/hook-windows-background-capability.js @@ -0,0 +1,111 @@ +#!/usr/bin/env javascript + +/* + Copyright (c) Microsoft. All rights reserved. + Licensed under the MIT license. See LICENSE file in the project root for full license information. +*/ +var fs = require("fs"); +var path = require("path"); +var Q; +var glob; +var xml2js = require('xml2js'); + +var requiredCapabilities = [ + { + name: "backgroundMediaPlayback", + present: false, + tag: "uap3:Capability", + namespace: { name: "uap3", uri: "http://schemas.microsoft.com/appx/manifest/uap/windows10/3" } + } /*, { + name: "extendedExecutionUnconstrained", + present: false, + tag: "rescap:Capability", + namespace: {name: "rescap", uri:"http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"} + }*/ +]; + +module.exports = function(context) { + + console.log("Adding proper capability to windows manifests"); + + // Grab the Q, glob node modules from cordova + Q=context.requireCordovaModule("q"); + glob=context.requireCordovaModule("glob"); + + // Need to return a promise since glob is async + var deferred = Q.defer(); + + // Find all custom framework files within plugin source code for the iOS platform + glob("platforms/windows/package.*.appxmanifest", function(err, manifests) { + if(err) { + deferred.reject(err); + } else { + // Folder symlinks like "Header" will appear as normal files without an extension if they came from + // npm or were sourced from windows. Inside these files is the relative path to the directory the + // symlink points to. So, start detecting them them by finding files < 1k without a file extension. + manifests.forEach(function(manifest) { + manifest = path.join(context.opts.projectRoot, manifest); + + fs.readFile(manifest, 'utf8', function(err, data) { + console.log("Processing:", manifest); + if (err) { + return console.log(err); + } + + xml2js.parseString(data, function(err, xml) { + if (err) { + return console.log(err); + } + var capabilities = xml['Package']['Capabilities'][0]; + if (typeof capabilities === "undefined") { + capabilities = {}; + } + // Check if capability is already present + requiredCapabilities.forEach(function (req) { + if (typeof capabilities[req.tag] !== "undefined") { + capabilities[req.tag].forEach(function(capability) { + if (capability["$"] && capability["$"]["Name"] == req.name) { + req.present = true; + } + }); + } + }); + + requiredCapabilities.forEach(function (req) { + if (!req.present) { + console.log ("Adding", req.name) + if (typeof capabilities[req.tag] === "undefined") { + capabilities[req.tag] = []; + } + capabilities[req.tag].push({"$": { "Name" : req.name }}) + if (req.namespace && !xml['Package']['$']['xmlns:' + req.namespace.name]) { + xml['Package']['$']['xmlns:' + req.namespace.name] = req.namespace.uri; + } + } + }); + var namespaces = []; + for (ns in xml['Package']['$']) { + if (ns.match(/^xmlns:/)) { + namespaces.push(ns.split(":")[1]); + } + }; + xml['Package']['$']['IgnorableNamespaces'] = namespaces.join(" "); + + xml['Package']['Capabilities'] = capabilities; + + // write modified appxmanifest + var builder = new xml2js.Builder(); + fs.writeFile(manifest, builder.buildObject(xml), function(err) { + if(err) { + return console.log(err); + } + }); + }); + }); + }); + deferred.resolve(); + } + }); + + return deferred.promise; +} diff --git a/src/windows/BackgroundModeProxy.js b/src/windows/BackgroundModeProxy.js index 5542e3f7..5ac64b7d 100644 --- a/src/windows/BackgroundModeProxy.js +++ b/src/windows/BackgroundModeProxy.js @@ -27,7 +27,8 @@ var Uri = Windows.Foundation.Uri, MediaPlaybackList = Windows.Media.Playback.MediaPlaybackList, AudioCategory = Windows.Media.Playback.MediaPlayerAudioCategory, MediaPlayer = Windows.Media.Playback.MediaPlayer, - WebUIApplication = Windows.UI.WebUI.WebUIApplication; + WebUIApplication = Windows.UI.WebUI.WebUIApplication, + ExtendedExecution = Windows.ApplicationModel.ExtendedExecution; /** * Activates the background mode. When activated the application @@ -40,6 +41,29 @@ var Uri = Windows.Foundation.Uri, * @return [ Void ] */ exports.enable = function (success, error) { + + var newSession = new ExtendedExecution.ExtendedExecutionSession(); + newSession.reason = ExtendedExecution.ExtendedExecutionReason.unspecified; + newSession.onrevoked = function (args) { + console.log("Revoked!!", args); + exports.stopKeepingAwake(); + } + + newSession.requestExtensionAsync() + .then(function (result) { + switch (result) { + case ExtendedExecution.ExtendedExecutionResult.allowed: + exports.keepAwake(); + break; + case ExtendedExecution.ExtendedExecutionResult.denied: + console.log("BG session failed :("); + break; + default: + break; + } + }, function (err) { + var abc = err.message; + }); success(); }; @@ -63,8 +87,9 @@ exports.disable = function (success, error) { * @return [ Void ] */ exports.keepAwake = function () { - if (!plugin.isEnabled() || plugin.isActive()) + if (!plugin.isEnabled() || plugin.isActive()) { return; + } exports.configureAudioPlayer(); exports.audioPlayer.play(); @@ -110,14 +135,11 @@ exports.configureAudioPlayer = function () { playList.autoRepeatEnabled = true; audioPlayer.source = playList; - audioPlayer.autoPlay = false; + audioPlayer.autoPlay = true; audioPlayer.audioCategory = AudioCategory.soundEffects; audioPlayer.volume = 0; exports.audioPlayer = audioPlayer; }; -WebUIApplication.addEventListener('enteredbackground', exports.keepAwake, false); -WebUIApplication.addEventListener('leavingbackground', exports.stopKeepingAwake, false); - cordova.commandProxy.add('BackgroundMode', exports);