diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 2b6c13bb1c..0e2bd7c957 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -429,7 +429,7 @@ function getPlaybackInfo(player, enableDirectStream, allowVideoStreamCopy, allowAudioStreamCopy) { - if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio') { + if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio' && !player.useServerPlaybackInfoForAudio) { return Promise.resolve({ MediaSources: [ { @@ -1692,7 +1692,7 @@ class PlaybackManager { if (validatePlaybackInfoResult(self, result)) { currentMediaSource = result.MediaSources[0]; - const streamInfo = createStreamInfo(apiClient, currentItem.MediaType, currentItem, currentMediaSource, ticks); + const streamInfo = createStreamInfo(apiClient, currentItem.MediaType, currentItem, currentMediaSource, ticks, player); streamInfo.fullscreen = currentPlayOptions.fullscreen; streamInfo.lastMediaInfoQuery = lastMediaInfoQuery; @@ -2272,7 +2272,7 @@ class PlaybackManager { playOptions.items = null; return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(function (mediaSource) { - const streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition); + const streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition, player); streamInfo.fullscreen = playOptions.fullscreen; @@ -2311,7 +2311,7 @@ class PlaybackManager { return player.getDeviceProfile(item).then(function (deviceProfile) { return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, options.mediaSourceId, options.audioStreamIndex, options.subtitleStreamIndex).then(function (mediaSource) { - return createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition); + return createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition, player); }); }); }); @@ -2337,7 +2337,7 @@ class PlaybackManager { }); }; - function createStreamInfo(apiClient, type, item, mediaSource, startPosition) { + function createStreamInfo(apiClient, type, item, mediaSource, startPosition, player) { let mediaUrl; let contentType; let transcodingOffsetTicks = 0; @@ -2349,6 +2349,14 @@ class PlaybackManager { const mediaSourceContainer = (mediaSource.Container || '').toLowerCase(); let directOptions; + if (mediaSource.MediaStreams && player.useFullSubtitleUrls) { + mediaSource.MediaStreams.map(stream => { + if (stream.DeliveryUrl && stream.DeliveryUrl.startsWith('/')) { + stream.DeliveryUrl = apiClient.getUrl(stream.DeliveryUrl); + } + }); + } + if (type === 'Video' || type === 'Audio') { contentType = getMimeType(type.toLowerCase(), mediaSourceContainer); diff --git a/src/components/syncPlay/core/PlaybackCore.js b/src/components/syncPlay/core/PlaybackCore.js index 12e0c67abb..b88a1f0739 100644 --- a/src/components/syncPlay/core/PlaybackCore.js +++ b/src/components/syncPlay/core/PlaybackCore.js @@ -118,9 +118,11 @@ class PlaybackCore { * Sends a buffering request to the server. * @param {boolean} isBuffering Whether this client is buffering or not. */ - sendBufferingRequest(isBuffering = true) { + async sendBufferingRequest(isBuffering = true) { const playerWrapper = this.manager.getPlayerWrapper(); - const currentPosition = playerWrapper.currentTime(); + const currentPosition = (playerWrapper.currentTimeAsync + ? await playerWrapper.currentTimeAsync() + : playerWrapper.currentTime()); const currentPositionTicks = Math.round(currentPosition * Helper.TicksPerMillisecond); const isPlaying = playerWrapper.isPlaying(); @@ -155,7 +157,7 @@ class PlaybackCore { * Applies a command and checks the playback state if a duplicate command is received. * @param {Object} command The playback command. */ - applyCommand(command) { + async applyCommand(command) { // Check if duplicate. if (this.lastCommand && this.lastCommand.When.getTime() === command.When.getTime() && @@ -177,7 +179,9 @@ class PlaybackCore { } else { // Check if playback state matches requested command. const playerWrapper = this.manager.getPlayerWrapper(); - const currentPositionTicks = Math.round(playerWrapper.currentTime() * Helper.TicksPerMillisecond); + const currentPositionTicks = Math.round((playerWrapper.currentTimeAsync + ? await playerWrapper.currentTimeAsync() + : playerWrapper.currentTime()) * Helper.TicksPerMillisecond); const isPlaying = playerWrapper.isPlaying(); switch (command.Command) { @@ -255,14 +259,16 @@ class PlaybackCore { * @param {Date} playAtTime The server's UTC time at which to resume playback. * @param {number} positionTicks The PositionTicks from where to resume. */ - scheduleUnpause(playAtTime, positionTicks) { + async scheduleUnpause(playAtTime, positionTicks) { this.clearScheduledCommand(); const enableSyncTimeout = this.maxDelaySpeedToSync / 2.0; const currentTime = new Date(); const playAtTimeLocal = this.timeSyncCore.remoteDateToLocal(playAtTime); const playerWrapper = this.manager.getPlayerWrapper(); - const currentPositionTicks = playerWrapper.currentTime() * Helper.TicksPerMillisecond; + const currentPositionTicks = (playerWrapper.currentTimeAsync + ? await playerWrapper.currentTimeAsync() + : playerWrapper.currentTime()) * Helper.TicksPerMillisecond; if (playAtTimeLocal > currentTime) { const playTimeout = playAtTimeLocal - currentTime; diff --git a/src/components/syncPlay/core/QueueCore.js b/src/components/syncPlay/core/QueueCore.js index ba9bb754fe..1dcc765ff1 100644 --- a/src/components/syncPlay/core/QueueCore.js +++ b/src/components/syncPlay/core/QueueCore.js @@ -165,14 +165,16 @@ class QueueCore { * @param {string} origin The origin of the wait call, used for debug. */ scheduleReadyRequestOnPlaybackStart(apiClient, origin) { - Helper.waitForEventOnce(this.manager, 'playbackstart', Helper.WaitForEventDefaultTimeout, ['playbackerror']).then(() => { + Helper.waitForEventOnce(this.manager, 'playbackstart', Helper.WaitForEventDefaultTimeout, ['playbackerror']).then(async () => { console.debug('SyncPlay scheduleReadyRequestOnPlaybackStart: local pause and notify server.'); const playerWrapper = this.manager.getPlayerWrapper(); playerWrapper.localPause(); const currentTime = new Date(); const now = this.manager.timeSyncCore.localDateToRemote(currentTime); - const currentPosition = playerWrapper.currentTime(); + const currentPosition = (playerWrapper.currentTimeAsync + ? await playerWrapper.currentTimeAsync() + : playerWrapper.currentTime()); const currentPositionTicks = Math.round(currentPosition * Helper.TicksPerMillisecond); const isPlaying = playerWrapper.isPlaying(); diff --git a/src/components/syncPlay/ui/playbackPermissionManager.js b/src/components/syncPlay/ui/playbackPermissionManager.js index e2d7ef2f4c..feebf85876 100644 --- a/src/components/syncPlay/ui/playbackPermissionManager.js +++ b/src/components/syncPlay/ui/playbackPermissionManager.js @@ -1,3 +1,5 @@ +import { getIgnorePlayPermission } from '../../../scripts/settings/webSettings'; + /** * Creates an audio element that plays a silent sound. * @returns {HTMLMediaElement} The audio element. @@ -32,8 +34,12 @@ 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) => { + async check () { + if (await getIgnorePlayPermission()) { + return true; + } + + return await new Promise((resolve, reject) => { const media = createTestMediaElement(); media.play().then(() => { resolve(); diff --git a/src/components/syncPlay/ui/players/HtmlVideoPlayer.js b/src/components/syncPlay/ui/players/HtmlVideoPlayer.js index cc045d4954..16d26cd1c2 100644 --- a/src/components/syncPlay/ui/players/HtmlVideoPlayer.js +++ b/src/components/syncPlay/ui/players/HtmlVideoPlayer.js @@ -17,6 +17,16 @@ class HtmlVideoPlayer extends NoActivePlayer { this.isPlayerActive = false; this.savedPlaybackRate = 1.0; this.minBufferingThresholdMillis = 3000; + + if (player.currentTimeAsync) { + /** + * Gets current playback position. + * @returns {Promise} The player position, in milliseconds. + */ + this.currentTimeAsync = () => { + return this.player.currentTimeAsync(); + }; + } } /** diff --git a/src/config.json b/src/config.json index 9dd6fa01d6..e511dcdee6 100644 --- a/src/config.json +++ b/src/config.json @@ -1,6 +1,7 @@ { "includeCorsCredentials": false, "multiserver": false, + "ignorePlayPermission": false, "themes": [ { "name": "Apple TV", diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index d74bc103e7..0b531954c0 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -312,8 +312,8 @@ import { appRouter } from '../../../components/appRouter'; function onPointerMove(e) { if ((e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse')) === 'mouse') { - const eventX = e.screenX || 0; - const eventY = e.screenY || 0; + const eventX = e.screenX || e.clientX || 0; + const eventY = e.screenY || e.clientY || 0; const obj = lastPointerMoveData; if (!obj) { diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index c9ad29ddff..7e6c539442 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -76,7 +76,13 @@ import Headroom from 'headroom.js'; } function onBackClick() { - appRouter.back(); + // If playing on a player that can't be destroyed with navigation, stop it manually. + const player = playbackManager.getCurrentPlayer(); + if (player && player.mustDestroy && skinHeader.classList.contains('osdHeader')) { + playbackManager.stop(); + } else { + appRouter.back(); + } } function retranslateUi() { diff --git a/src/scripts/mouseManager.js b/src/scripts/mouseManager.js index 3f37dfbd9e..1ad3389c04 100644 --- a/src/scripts/mouseManager.js +++ b/src/scripts/mouseManager.js @@ -54,8 +54,8 @@ import dom from '../scripts/dom'; let lastPointerMoveData; function onPointerMove(e) { - const eventX = e.screenX; - const eventY = e.screenY; + const eventX = e.screenX || e.clientX; + const eventY = e.screenY || e.clientY; // if coord don't exist how could it move if (typeof eventX === 'undefined' && typeof eventY === 'undefined') { diff --git a/src/scripts/settings/webSettings.js b/src/scripts/settings/webSettings.js index e88e65ad8f..f985cdb118 100644 --- a/src/scripts/settings/webSettings.js +++ b/src/scripts/settings/webSettings.js @@ -79,6 +79,15 @@ export function getMultiServer() { }); } +export function getIgnorePlayPermission() { + return getConfig().then(config => { + return !!config.ignorePlayPermission; + }).catch(error => { + console.log('cannot get web config:', error); + return false; + }); +} + export function getServers() { return getConfig().then(config => { return config.servers || [];