mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge pull request #2557 from iwalton3/jpm-plugin
NativeShell enhancements for Jellyfin Media Player
This commit is contained in:
commit
f5cf6879f9
12 changed files with 108 additions and 45 deletions
|
@ -275,7 +275,7 @@ const supportedFeatures = function () {
|
||||||
*/
|
*/
|
||||||
function doExit() {
|
function doExit() {
|
||||||
try {
|
try {
|
||||||
if (window.NativeShell) {
|
if (window.NativeShell?.AppHost?.exit) {
|
||||||
window.NativeShell.AppHost.exit();
|
window.NativeShell.AppHost.exit();
|
||||||
} else if (browser.tizen) {
|
} else if (browser.tizen) {
|
||||||
tizen.application.getCurrentApplication().exit();
|
tizen.application.getCurrentApplication().exit();
|
||||||
|
@ -360,16 +360,20 @@ export const appHost = {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
deviceName: function () {
|
deviceName: function () {
|
||||||
return window.NativeShell ? window.NativeShell.AppHost.deviceName() : getDeviceName();
|
return window.NativeShell?.AppHost?.deviceName
|
||||||
|
? window.NativeShell.AppHost.deviceName() : getDeviceName();
|
||||||
},
|
},
|
||||||
deviceId: function () {
|
deviceId: function () {
|
||||||
return window.NativeShell ? window.NativeShell.AppHost.deviceId() : getDeviceId();
|
return window.NativeShell?.AppHost?.deviceId
|
||||||
|
? window.NativeShell.AppHost.deviceId() : getDeviceId();
|
||||||
},
|
},
|
||||||
appName: function () {
|
appName: function () {
|
||||||
return window.NativeShell ? window.NativeShell.AppHost.appName() : appName;
|
return window.NativeShell?.AppHost?.appName
|
||||||
|
? window.NativeShell.AppHost.appName() : appName;
|
||||||
},
|
},
|
||||||
appVersion: function () {
|
appVersion: function () {
|
||||||
return window.NativeShell ? window.NativeShell.AppHost.appVersion() : appVersion;
|
return window.NativeShell?.AppHost?.appVersion
|
||||||
|
? window.NativeShell.AppHost.appVersion() : appVersion;
|
||||||
},
|
},
|
||||||
getPushTokenInfo: function () {
|
getPushTokenInfo: function () {
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -429,7 +429,7 @@ function getPlaybackInfo(player,
|
||||||
enableDirectStream,
|
enableDirectStream,
|
||||||
allowVideoStreamCopy,
|
allowVideoStreamCopy,
|
||||||
allowAudioStreamCopy) {
|
allowAudioStreamCopy) {
|
||||||
if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio') {
|
if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio' && !player.useServerPlaybackInfoForAudio) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
MediaSources: [
|
MediaSources: [
|
||||||
{
|
{
|
||||||
|
@ -1692,7 +1692,7 @@ class PlaybackManager {
|
||||||
if (validatePlaybackInfoResult(self, result)) {
|
if (validatePlaybackInfoResult(self, result)) {
|
||||||
currentMediaSource = result.MediaSources[0];
|
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.fullscreen = currentPlayOptions.fullscreen;
|
||||||
streamInfo.lastMediaInfoQuery = lastMediaInfoQuery;
|
streamInfo.lastMediaInfoQuery = lastMediaInfoQuery;
|
||||||
|
|
||||||
|
@ -2272,7 +2272,7 @@ class PlaybackManager {
|
||||||
playOptions.items = null;
|
playOptions.items = null;
|
||||||
|
|
||||||
return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(function (mediaSource) {
|
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;
|
streamInfo.fullscreen = playOptions.fullscreen;
|
||||||
|
|
||||||
|
@ -2311,7 +2311,7 @@ class PlaybackManager {
|
||||||
|
|
||||||
return player.getDeviceProfile(item).then(function (deviceProfile) {
|
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 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 mediaUrl;
|
||||||
let contentType;
|
let contentType;
|
||||||
let transcodingOffsetTicks = 0;
|
let transcodingOffsetTicks = 0;
|
||||||
|
@ -2349,6 +2349,14 @@ class PlaybackManager {
|
||||||
const mediaSourceContainer = (mediaSource.Container || '').toLowerCase();
|
const mediaSourceContainer = (mediaSource.Container || '').toLowerCase();
|
||||||
let directOptions;
|
let directOptions;
|
||||||
|
|
||||||
|
if (mediaSource.MediaStreams && player.useFullSubtitleUrls) {
|
||||||
|
mediaSource.MediaStreams.forEach(stream => {
|
||||||
|
if (stream.DeliveryUrl && stream.DeliveryUrl.startsWith('/')) {
|
||||||
|
stream.DeliveryUrl = apiClient.getUrl(stream.DeliveryUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (type === 'Video' || type === 'Audio') {
|
if (type === 'Video' || type === 'Audio') {
|
||||||
contentType = getMimeType(type.toLowerCase(), mediaSourceContainer);
|
contentType = getMimeType(type.toLowerCase(), mediaSourceContainer);
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,11 @@ import globalize from '../scripts/globalize';
|
||||||
import loading from './loading/loading';
|
import loading from './loading/loading';
|
||||||
import appSettings from '../scripts/settings/appSettings';
|
import appSettings from '../scripts/settings/appSettings';
|
||||||
import { playbackManager } from './playback/playbackmanager';
|
import { playbackManager } from './playback/playbackmanager';
|
||||||
|
import { appHost } from '../components/apphost';
|
||||||
|
import { appRouter } from '../components/appRouter';
|
||||||
|
import * as inputManager from '../scripts/inputManager';
|
||||||
|
import toast from '../components/toast/toast';
|
||||||
|
import confirm from '../components/confirm/confirm';
|
||||||
|
|
||||||
/* eslint-disable indent */
|
/* eslint-disable indent */
|
||||||
|
|
||||||
|
@ -90,7 +95,13 @@ import { playbackManager } from './playback/playbackmanager';
|
||||||
events: Events,
|
events: Events,
|
||||||
loading,
|
loading,
|
||||||
appSettings,
|
appSettings,
|
||||||
playbackManager
|
playbackManager,
|
||||||
|
globalize,
|
||||||
|
appHost,
|
||||||
|
appRouter,
|
||||||
|
inputManager,
|
||||||
|
toast,
|
||||||
|
confirm
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.debug(`Loading plugin (via dynamic import): ${pluginSpec}`);
|
console.debug(`Loading plugin (via dynamic import): ${pluginSpec}`);
|
||||||
|
|
|
@ -118,9 +118,11 @@ class PlaybackCore {
|
||||||
* Sends a buffering request to the server.
|
* Sends a buffering request to the server.
|
||||||
* @param {boolean} isBuffering Whether this client is buffering or not.
|
* @param {boolean} isBuffering Whether this client is buffering or not.
|
||||||
*/
|
*/
|
||||||
sendBufferingRequest(isBuffering = true) {
|
async sendBufferingRequest(isBuffering = true) {
|
||||||
const playerWrapper = this.manager.getPlayerWrapper();
|
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 currentPositionTicks = Math.round(currentPosition * Helper.TicksPerMillisecond);
|
||||||
const isPlaying = playerWrapper.isPlaying();
|
const isPlaying = playerWrapper.isPlaying();
|
||||||
|
|
||||||
|
@ -155,7 +157,7 @@ class PlaybackCore {
|
||||||
* Applies a command and checks the playback state if a duplicate command is received.
|
* Applies a command and checks the playback state if a duplicate command is received.
|
||||||
* @param {Object} command The playback command.
|
* @param {Object} command The playback command.
|
||||||
*/
|
*/
|
||||||
applyCommand(command) {
|
async applyCommand(command) {
|
||||||
// Check if duplicate.
|
// Check if duplicate.
|
||||||
if (this.lastCommand &&
|
if (this.lastCommand &&
|
||||||
this.lastCommand.When.getTime() === command.When.getTime() &&
|
this.lastCommand.When.getTime() === command.When.getTime() &&
|
||||||
|
@ -177,7 +179,9 @@ class PlaybackCore {
|
||||||
} else {
|
} else {
|
||||||
// Check if playback state matches requested command.
|
// Check if playback state matches requested command.
|
||||||
const playerWrapper = this.manager.getPlayerWrapper();
|
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();
|
const isPlaying = playerWrapper.isPlaying();
|
||||||
|
|
||||||
switch (command.Command) {
|
switch (command.Command) {
|
||||||
|
@ -255,14 +259,16 @@ class PlaybackCore {
|
||||||
* @param {Date} playAtTime The server's UTC time at which to resume playback.
|
* @param {Date} playAtTime The server's UTC time at which to resume playback.
|
||||||
* @param {number} positionTicks The PositionTicks from where to resume.
|
* @param {number} positionTicks The PositionTicks from where to resume.
|
||||||
*/
|
*/
|
||||||
scheduleUnpause(playAtTime, positionTicks) {
|
async scheduleUnpause(playAtTime, positionTicks) {
|
||||||
this.clearScheduledCommand();
|
this.clearScheduledCommand();
|
||||||
const enableSyncTimeout = this.maxDelaySpeedToSync / 2.0;
|
const enableSyncTimeout = this.maxDelaySpeedToSync / 2.0;
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
const playAtTimeLocal = this.timeSyncCore.remoteDateToLocal(playAtTime);
|
const playAtTimeLocal = this.timeSyncCore.remoteDateToLocal(playAtTime);
|
||||||
|
|
||||||
const playerWrapper = this.manager.getPlayerWrapper();
|
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) {
|
if (playAtTimeLocal > currentTime) {
|
||||||
const playTimeout = playAtTimeLocal - currentTime;
|
const playTimeout = playAtTimeLocal - currentTime;
|
||||||
|
|
|
@ -167,14 +167,16 @@ class QueueCore {
|
||||||
* @param {string} origin The origin of the wait call, used for debug.
|
* @param {string} origin The origin of the wait call, used for debug.
|
||||||
*/
|
*/
|
||||||
scheduleReadyRequestOnPlaybackStart(apiClient, origin) {
|
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.');
|
console.debug('SyncPlay scheduleReadyRequestOnPlaybackStart: local pause and notify server.');
|
||||||
const playerWrapper = this.manager.getPlayerWrapper();
|
const playerWrapper = this.manager.getPlayerWrapper();
|
||||||
playerWrapper.localPause();
|
playerWrapper.localPause();
|
||||||
|
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
const now = this.manager.timeSyncCore.localDateToRemote(currentTime);
|
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 currentPositionTicks = Math.round(currentPosition * Helper.TicksPerMillisecond);
|
||||||
const isPlaying = playerWrapper.isPlaying();
|
const isPlaying = playerWrapper.isPlaying();
|
||||||
|
|
||||||
|
|
|
@ -44,13 +44,15 @@ class PlayerFactory {
|
||||||
return this.getDefaultWrapper(syncPlayManager);
|
return this.getDefaultWrapper(syncPlayManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug('SyncPlay WrapperFactory getWrapper:', player.id);
|
const playerId = player.syncPlayWrapAs || player.id;
|
||||||
const Wrapper = this.wrappers[player.id];
|
|
||||||
|
console.debug('SyncPlay WrapperFactory getWrapper:', playerId);
|
||||||
|
const Wrapper = this.wrappers[playerId];
|
||||||
if (Wrapper) {
|
if (Wrapper) {
|
||||||
return new Wrapper(player, syncPlayManager);
|
return new Wrapper(player, syncPlayManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug(`SyncPlay WrapperFactory getWrapper: unknown player ${player.id}, using default wrapper.`);
|
console.debug(`SyncPlay WrapperFactory getWrapper: unknown player ${playerId}, using default wrapper.`);
|
||||||
return this.getDefaultWrapper(syncPlayManager);
|
return this.getDefaultWrapper(syncPlayManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { appHost } from '../../apphost';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an audio element that plays a silent sound.
|
* Creates an audio element that plays a silent sound.
|
||||||
* @returns {HTMLMediaElement} The audio element.
|
* @returns {HTMLMediaElement} The audio element.
|
||||||
|
@ -33,6 +35,10 @@ class PlaybackPermissionManager {
|
||||||
* @returns {Promise} Promise that resolves succesfully if playback permission is allowed.
|
* @returns {Promise} Promise that resolves succesfully if playback permission is allowed.
|
||||||
*/
|
*/
|
||||||
check () {
|
check () {
|
||||||
|
if (appHost.supports('htmlaudioautoplay')) {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const media = createTestMediaElement();
|
const media = createTestMediaElement();
|
||||||
media.play().then(() => {
|
media.play().then(() => {
|
||||||
|
|
|
@ -17,6 +17,16 @@ class HtmlVideoPlayer extends NoActivePlayer {
|
||||||
this.isPlayerActive = false;
|
this.isPlayerActive = false;
|
||||||
this.savedPlaybackRate = 1.0;
|
this.savedPlaybackRate = 1.0;
|
||||||
this.minBufferingThresholdMillis = 3000;
|
this.minBufferingThresholdMillis = 3000;
|
||||||
|
|
||||||
|
if (player.currentTimeAsync) {
|
||||||
|
/**
|
||||||
|
* Gets current playback position.
|
||||||
|
* @returns {Promise<number>} The player position, in milliseconds.
|
||||||
|
*/
|
||||||
|
this.currentTimeAsync = () => {
|
||||||
|
return this.player.currentTimeAsync();
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -94,13 +94,13 @@ import '../../assets/css/flexstyles.scss';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onStartNowClick() {
|
async function onStartNowClick() {
|
||||||
const options = this.options;
|
const options = this.options;
|
||||||
|
|
||||||
if (options) {
|
if (options) {
|
||||||
const player = options.player;
|
const player = options.player;
|
||||||
|
|
||||||
this.hide();
|
await this.hide();
|
||||||
|
|
||||||
playbackManager.nextTrack(player);
|
playbackManager.nextTrack(player);
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ import '../../assets/css/flexstyles.scss';
|
||||||
Events.trigger(instance, 'hide');
|
Events.trigger(instance, 'hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideComingUpNext() {
|
async function hideComingUpNext() {
|
||||||
const instance = this;
|
const instance = this;
|
||||||
clearCountdownTextTimeout(this);
|
clearCountdownTextTimeout(this);
|
||||||
|
|
||||||
|
@ -159,17 +159,21 @@ import '../../assets/css/flexstyles.scss';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// trigger a reflow to force it to animate again
|
|
||||||
void elem.offsetWidth;
|
|
||||||
|
|
||||||
elem.classList.add('upNextDialog-hidden');
|
|
||||||
|
|
||||||
const fn = onHideAnimationComplete.bind(instance);
|
const fn = onHideAnimationComplete.bind(instance);
|
||||||
instance._onHideAnimationComplete = fn;
|
instance._onHideAnimationComplete = fn;
|
||||||
|
|
||||||
dom.addEventListener(elem, transitionEndEventName, fn, {
|
const transitionEvent = await new Promise((resolve) => {
|
||||||
once: true
|
dom.addEventListener(elem, transitionEndEventName, resolve, {
|
||||||
|
once: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// trigger a reflow to force it to animate again
|
||||||
|
void elem.offsetWidth;
|
||||||
|
|
||||||
|
elem.classList.add('upNextDialog-hidden');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
instance._onHideAnimationComplete(transitionEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTimeRemainingMs(instance) {
|
function getTimeRemainingMs(instance) {
|
||||||
|
@ -226,8 +230,8 @@ class UpNextDialog {
|
||||||
|
|
||||||
startComingUpNextHideTimer(this);
|
startComingUpNextHideTimer(this);
|
||||||
}
|
}
|
||||||
hide() {
|
async hide() {
|
||||||
hideComingUpNext.call(this);
|
await hideComingUpNext.bind(this)();
|
||||||
}
|
}
|
||||||
destroy() {
|
destroy() {
|
||||||
hideComingUpNext.call(this);
|
hideComingUpNext.call(this);
|
||||||
|
|
|
@ -313,8 +313,8 @@ import { appRouter } from '../../../components/appRouter';
|
||||||
|
|
||||||
function onPointerMove(e) {
|
function onPointerMove(e) {
|
||||||
if ((e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse')) === 'mouse') {
|
if ((e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse')) === 'mouse') {
|
||||||
const eventX = e.screenX || 0;
|
const eventX = e.screenX || e.clientX || 0;
|
||||||
const eventY = e.screenY || 0;
|
const eventY = e.screenY || e.clientY || 0;
|
||||||
const obj = lastPointerMoveData;
|
const obj = lastPointerMoveData;
|
||||||
|
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
|
|
|
@ -54,8 +54,8 @@ import dom from '../scripts/dom';
|
||||||
|
|
||||||
let lastPointerMoveData;
|
let lastPointerMoveData;
|
||||||
function onPointerMove(e) {
|
function onPointerMove(e) {
|
||||||
const eventX = e.screenX;
|
const eventX = e.screenX || e.clientX;
|
||||||
const eventY = e.screenY;
|
const eventY = e.screenY || e.clientY;
|
||||||
|
|
||||||
// if coord don't exist how could it move
|
// if coord don't exist how could it move
|
||||||
if (typeof eventX === 'undefined' && typeof eventY === 'undefined') {
|
if (typeof eventX === 'undefined' && typeof eventY === 'undefined') {
|
||||||
|
|
|
@ -1,30 +1,40 @@
|
||||||
// TODO: This seems like a good candidate for deprecation
|
// TODO: This seems like a good candidate for deprecation
|
||||||
export default {
|
export default {
|
||||||
enableFullscreen: function() {
|
enableFullscreen: function() {
|
||||||
window.NativeShell?.enableFullscreen();
|
if (window.NativeShell?.enableFullscreen) {
|
||||||
|
window.NativeShell.enableFullscreen();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
disableFullscreen: function() {
|
disableFullscreen: function() {
|
||||||
window.NativeShell?.disableFullscreen();
|
if (window.NativeShell?.disableFullscreen) {
|
||||||
|
window.NativeShell.disableFullscreen();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
openUrl: function(url, target) {
|
openUrl: function(url, target) {
|
||||||
if (window.NativeShell) {
|
if (window.NativeShell?.openUrl) {
|
||||||
window.NativeShell.openUrl(url, target);
|
window.NativeShell.openUrl(url, target);
|
||||||
} else {
|
} else {
|
||||||
window.open(url, target || '_blank');
|
window.open(url, target || '_blank');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateMediaSession(mediaInfo) {
|
updateMediaSession(mediaInfo) {
|
||||||
window.NativeShell?.updateMediaSession(mediaInfo);
|
if (window.NativeShell?.updateMediaSession) {
|
||||||
|
window.NativeShell.updateMediaSession(mediaInfo);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
hideMediaSession() {
|
hideMediaSession() {
|
||||||
window.NativeShell?.hideMediaSession();
|
if (window.NativeShell?.hideMediaSession) {
|
||||||
|
window.NativeShell.hideMediaSession();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Notify the NativeShell about volume level changes.
|
* Notify the NativeShell about volume level changes.
|
||||||
* Useful for e.g. remote playback.
|
* Useful for e.g. remote playback.
|
||||||
*/
|
*/
|
||||||
updateVolumeLevel(volume) {
|
updateVolumeLevel(volume) {
|
||||||
window.NativeShell?.updateVolumeLevel(volume);
|
if (window.NativeShell?.updateVolumeLevel) {
|
||||||
|
window.NativeShell.updateVolumeLevel(volume);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Download specified files with NativeShell if possible
|
* Download specified files with NativeShell if possible
|
||||||
|
@ -32,7 +42,7 @@ export default {
|
||||||
* @returns true on success
|
* @returns true on success
|
||||||
*/
|
*/
|
||||||
downloadFiles(items) {
|
downloadFiles(items) {
|
||||||
if (window.NativeShell) {
|
if (window.NativeShell?.downloadFile) {
|
||||||
items.forEach(item => window.NativeShell.downloadFile(item));
|
items.forEach(item => window.NativeShell.downloadFile(item));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue