diff --git a/gulpfile.js b/gulpfile.js index 6c33167386..ad77d9a677 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -45,7 +45,7 @@ const options = { query: ['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg'] }, copy: { - query: ['src/**/*.json', 'src/**/*.ico'] + query: ['src/**/*.json', 'src/**/*.ico', 'src/**/*.wav'] }, injectBundle: { query: 'src/index.html' diff --git a/package.json b/package.json index 39330cf600..19d3b05a45 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,9 @@ "src/components/playback/mediasession.js", "src/components/sanatizefilename.js", "src/components/scrollManager.js", + "src/components/syncplay/playbackPermissionManager.js", + "src/components/syncplay/groupSelectionMenu.js", + "src/components/syncplay/syncplayManager.js", "src/scripts/dfnshelper.js", "src/scripts/dom.js", "src/scripts/filesystem.js", @@ -105,9 +108,7 @@ "src/scripts/keyboardnavigation.js", "src/scripts/settings/appSettings.js", "src/scripts/settings/userSettings.js", - "src/scripts/settings/webSettings.js", - "src/components/syncplay/groupSelectionMenu.js", - "src/components/syncplay/syncplayManager.js" + "src/scripts/settings/webSettings.js" ], "plugins": [ "@babel/plugin-transform-modules-amd" diff --git a/src/assets/audio/silence.wav b/src/assets/audio/silence.wav new file mode 100644 index 0000000000..63f253da84 Binary files /dev/null and b/src/assets/audio/silence.wav differ diff --git a/src/components/syncplay/groupSelectionMenu.js b/src/components/syncplay/groupSelectionMenu.js index 046900d8e2..1b490db803 100644 --- a/src/components/syncplay/groupSelectionMenu.js +++ b/src/components/syncplay/groupSelectionMenu.js @@ -7,6 +7,7 @@ import datetime from 'datetime'; import toast from 'toast'; import actionsheet from 'actionsheet'; import globalize from 'globalize'; +import playbackPermissionManager from 'playbackPermissionManager'; /** * Gets active player id. @@ -153,6 +154,16 @@ events.on(syncplayManager, 'SyncplayEnabled', function (e, enabled) { export function show(button) { loading.show(); + // TODO: should feature be disabled if playback permission is missing? + playbackPermissionManager.check().then(() => { + console.debug("Playback is allowed."); + }).catch((error) => { + console.error("Playback not allowed!", error); + toast({ + text: globalize.translate("MessageSyncplayPlaybackPermissionRequired") + }); + }); + const apiClient = connectionManager.currentApiClient(); connectionManager.user(apiClient).then((user) => { if (syncplayEnabled) { diff --git a/src/components/syncplay/playbackPermissionManager.js b/src/components/syncplay/playbackPermissionManager.js new file mode 100644 index 0000000000..df16545b39 --- /dev/null +++ b/src/components/syncplay/playbackPermissionManager.js @@ -0,0 +1,51 @@ +/** + * Creates an audio element that plays a silent sound. + * @returns {HTMLMediaElement} The audio element. + */ +function createTestMediaElement () { + + const elem = document.createElement('audio'); + elem.classList.add('testMediaPlayerAudio'); + elem.classList.add('hide'); + + document.body.appendChild(elem); + + elem.volume = 1; // Volume should not be zero to trigger proper permissions + elem.src = "assets/audio/silence.wav"; // Silent sound + + return elem; +} + +/** + * Destroys a media element. + * @param {HTMLMediaElement} elem The element to destroy. + */ +function destroyTestMediaElement (elem) { + elem.pause(); + elem.remove(); +} + +/** + * Class that manages the playback permission. + */ +class PlaybackPermissionManager { + /** + * Tests playback permission. Grabs the permission when called inside a click event (or any other valid user interaction). + * @returns {Promise} Promise that resolves succesfully if playback permission is allowed. + */ + check () { + return new Promise((resolve, reject) => { + const media = createTestMediaElement(); + media.play().then(() => { + resolve(); + }).catch((error) => { + reject(error); + }).finally(() => { + destroyTestMediaElement(media); + }); + }); + } +} + +/** PlaybackPermissionManager singleton. */ +export default new PlaybackPermissionManager(); diff --git a/src/scripts/site.js b/src/scripts/site.js index 421cdae32e..3954b153a4 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -825,6 +825,7 @@ var AppInfo = {}; define('homescreenSettings', [componentsPath + '/homescreensettings/homescreensettings'], returnFirstDependency); define('playbackManager', [componentsPath + '/playback/playbackmanager'], getPlaybackManager); define('syncplayManager', [componentsPath + '/syncplay/syncplaymanager'], returnDefault); + define('playbackPermissionManager', [componentsPath + '/syncplay/playbackPermissionManager'], returnDefault); define('layoutManager', [componentsPath + '/layoutManager', 'apphost'], getLayoutManager); define('homeSections', [componentsPath + '/homesections/homesections'], returnFirstDependency); define('playMenu', [componentsPath + '/playmenu'], returnFirstDependency); diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 83890426c5..136a015430 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -1036,6 +1036,7 @@ "MessageSyncplayGroupWait": "{0} is buffering...", "MessageSyncplayNoGroupsAvailable": "No groups available.", "MessageSyncplayPermissionRequired": "Permission required to create a group.", + "MessageSyncplayPlaybackPermissionRequired": "Playback permission required.", "Metadata": "Metadata", "MetadataManager": "Metadata Manager", "MetadataSettingChangeHelp": "Changing metadata settings will affect new content that is added going forward. To refresh existing content, open the detail screen and click the refresh button, or perform bulk refreshes using the metadata manager.", diff --git a/webpack.dev.js b/webpack.dev.js index 76a1a7a752..d8879fe808 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -44,6 +44,10 @@ module.exports = merge(common, { use: [ 'file-loader' ] + }, + { + test: /\.(wav)$/i, + use: ["file-loader"] } ] } diff --git a/webpack.prod.js b/webpack.prod.js index f5c7accd04..cc4c57b9f4 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -37,6 +37,10 @@ module.exports = merge(common, { use: [ 'file-loader' ] + }, + { + test: /\.(wav)$/i, + use: ["file-loader"] } ] }