mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Support features for JellyfinMediaPlayer.
This commit is contained in:
parent
e12e4081e9
commit
456f32ee15
10 changed files with 68 additions and 20 deletions
|
@ -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.map(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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -165,14 +165,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();
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { getIgnorePlayPermission } from '../../../scripts/settings/webSettings';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
@ -32,8 +34,12 @@ class PlaybackPermissionManager {
|
||||||
* Tests playback permission. Grabs the permission when called inside a click event (or any other valid user interaction).
|
* 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.
|
* @returns {Promise} Promise that resolves succesfully if playback permission is allowed.
|
||||||
*/
|
*/
|
||||||
check () {
|
async check () {
|
||||||
return new Promise((resolve, reject) => {
|
if (await getIgnorePlayPermission()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
const media = createTestMediaElement();
|
const media = createTestMediaElement();
|
||||||
media.play().then(() => {
|
media.play().then(() => {
|
||||||
resolve();
|
resolve();
|
||||||
|
|
|
@ -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();
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"includeCorsCredentials": false,
|
"includeCorsCredentials": false,
|
||||||
"multiserver": false,
|
"multiserver": false,
|
||||||
|
"ignorePlayPermission": false,
|
||||||
"themes": [
|
"themes": [
|
||||||
{
|
{
|
||||||
"name": "Apple TV",
|
"name": "Apple TV",
|
||||||
|
|
|
@ -312,8 +312,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) {
|
||||||
|
|
|
@ -76,8 +76,14 @@ import Headroom from 'headroom.js';
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBackClick() {
|
function onBackClick() {
|
||||||
|
// 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();
|
appRouter.back();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function retranslateUi() {
|
function retranslateUi() {
|
||||||
if (headerSyncButton) {
|
if (headerSyncButton) {
|
||||||
|
|
|
@ -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') {
|
||||||
|
|
|
@ -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() {
|
export function getServers() {
|
||||||
return getConfig().then(config => {
|
return getConfig().then(config => {
|
||||||
return config.servers || [];
|
return config.servers || [];
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue