1
0
Fork 0
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:
Ian Walton 2021-04-07 01:39:34 -04:00
parent e12e4081e9
commit 456f32ee15
10 changed files with 68 additions and 20 deletions

View file

@ -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);

View file

@ -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;

View file

@ -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();

View file

@ -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();

View file

@ -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();
};
}
} }
/** /**

View file

@ -1,6 +1,7 @@
{ {
"includeCorsCredentials": false, "includeCorsCredentials": false,
"multiserver": false, "multiserver": false,
"ignorePlayPermission": false,
"themes": [ "themes": [
{ {
"name": "Apple TV", "name": "Apple TV",

View file

@ -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) {

View file

@ -76,7 +76,13 @@ import Headroom from 'headroom.js';
} }
function onBackClick() { 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() { function retranslateUi() {

View file

@ -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') {

View file

@ -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 || [];