mirror of
https://github.com/jellyfin/jellyfin-vue.git
synced 2026-09-03 05:10:26 +03:00
fix(videoplayer): few refactors and lint fixes
This commit is contained in:
@@ -48,12 +48,12 @@ const props = withDefaults(
|
||||
iconOnly?: boolean;
|
||||
fab?: boolean;
|
||||
shuffle?: boolean;
|
||||
videoTrackIndex?: number;
|
||||
audioTrackIndex?: number;
|
||||
subtitleTrackIndex?: number;
|
||||
videoTrackIndex: number;
|
||||
audioTrackIndex: number;
|
||||
subtitleTrackIndex: number;
|
||||
disabled?: boolean;
|
||||
}>(),
|
||||
{ disabled: false }
|
||||
{ iconOnly: false, fab: false, shuffle: false, disabled: false }
|
||||
);
|
||||
|
||||
const playbackManager = playbackManagerStore();
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
:model-value="isHovering"
|
||||
v-bind="props"
|
||||
contained
|
||||
eager
|
||||
scrim
|
||||
height="100%"
|
||||
width="100%">
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, watch, nextTick } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import Hls, { ErrorData, ErrorTypes, Events } from 'hls.js';
|
||||
@@ -40,9 +39,8 @@ import { mediaElementRef } from '@/store/playbackManager';
|
||||
|
||||
const playbackManager = playbackManagerStore();
|
||||
const playerElement = playerElementStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
|
||||
/**
|
||||
* Safari iOS doesn't support hls.js, so we need to handle the cases where we don't need hls.js
|
||||
*/
|
||||
@@ -156,27 +154,6 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => ({ currentItem: playbackManager.currentItem }),
|
||||
(newValue, oldValue) => {
|
||||
if (
|
||||
(!newValue.currentItem &&
|
||||
router.currentRoute.value.fullPath === '/playback/video') ||
|
||||
(newValue.currentItem &&
|
||||
!oldValue?.currentItem &&
|
||||
playbackManager.currentlyPlayingMediaType === 'Video')
|
||||
) {
|
||||
/**
|
||||
* If no item is present, we either manually loaded this or playback is stopped
|
||||
* OR
|
||||
* If there was no item and there's now a video, default to going FS
|
||||
*/
|
||||
playerElement.toggleFullscreenVideoPlayer();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(mediaElementRef, async () => {
|
||||
await nextTick();
|
||||
detachHls();
|
||||
|
||||
@@ -25,7 +25,7 @@ const playerElement = playerElementStore();
|
||||
|
||||
const osd = ref(true);
|
||||
|
||||
let fullscreen = useFullscreen(document.body);
|
||||
const fullscreen = useFullscreen(document.body);
|
||||
|
||||
/**
|
||||
* Toggles the fullscreen view, based on browsers supporting it or not (basically iOS or the others)
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* This store handles the state of the playback
|
||||
*
|
||||
* It must be used in an agnostic way to cover both local and remote playback.
|
||||
* If you want to handle the state of the local player element, use playerElement store instead.
|
||||
*/
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { shuffle, isNil, cloneDeep } from 'lodash-es';
|
||||
import {
|
||||
@@ -615,18 +621,25 @@ class PlaybackManagerStore {
|
||||
/**
|
||||
* Report playback stopped to the server. Used by the "Now playing" statistics in other clients.
|
||||
*/
|
||||
private _reportPlaybackStopped = async (itemId: string): Promise<void> => {
|
||||
private _reportPlaybackStopped = async (
|
||||
itemId: string,
|
||||
sessionId = state.playSessionId,
|
||||
currentTime = this.currentTime,
|
||||
updateState = true
|
||||
): Promise<void> => {
|
||||
const remote = useRemote();
|
||||
|
||||
await remote.sdk.newUserApi(getPlaystateApi).reportPlaybackStopped({
|
||||
playbackStopInfo: {
|
||||
ItemId: itemId,
|
||||
PlaySessionId: state.playSessionId,
|
||||
PositionTicks: msToTicks((this.currentTime || 0) * 1000)
|
||||
PlaySessionId: sessionId,
|
||||
PositionTicks: msToTicks((currentTime || 0) * 1000)
|
||||
}
|
||||
});
|
||||
|
||||
state.lastProgressUpdate = Date.now();
|
||||
if (updateState) {
|
||||
state.lastProgressUpdate = Date.now();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -831,24 +844,22 @@ class PlaybackManagerStore {
|
||||
};
|
||||
|
||||
public stop = (): void => {
|
||||
const sessionId = String(state.playSessionId || '');
|
||||
const time = Number(this.currentTime);
|
||||
const itemId = String(this.currentItem?.Id || '');
|
||||
const volume = Number(this.currentVolume);
|
||||
|
||||
Object.assign(state, defaultState);
|
||||
this.currentVolume = volume;
|
||||
|
||||
window.setTimeout(async () => {
|
||||
const remote = useRemote();
|
||||
|
||||
try {
|
||||
if (
|
||||
!isNil(this.currentItem) &&
|
||||
!isNil(this.currentItem.Id) &&
|
||||
!isNil(remote.auth.currentUser)
|
||||
) {
|
||||
this._reportPlaybackStopped(this.currentItem.Id);
|
||||
if (sessionId && itemId && time && remote.auth.currentUser) {
|
||||
await this._reportPlaybackStopped(itemId, sessionId, time, false);
|
||||
}
|
||||
} catch {
|
||||
} finally {
|
||||
const volume = this.currentVolume;
|
||||
|
||||
Object.assign(state, defaultState);
|
||||
this.currentVolume = volume;
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
/**
|
||||
* This store handles the state of the local player.
|
||||
*
|
||||
* In the other part, playbackManager is suited to handle the playback state in
|
||||
* an agnostic way, regardless of where the media is being played (remotely or locally)
|
||||
*/
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { nextTick, reactive } from 'vue';
|
||||
import { nextTick, reactive, watch } from 'vue';
|
||||
// @ts-expect-error - No types on libass-wasm
|
||||
import SubtitlesOctopus from '@jellyfin/libass-wasm';
|
||||
import subtitlesOctopusWorkerUrl from '@jellyfin/libass-wasm/dist/js/subtitles-octopus-worker.js?url';
|
||||
@@ -8,6 +14,7 @@ import { useRouter } from '@/composables';
|
||||
|
||||
const playbackManager = playbackManagerStore();
|
||||
let subtitlesOctopus: SubtitlesOctopus | undefined;
|
||||
const fullscreenRoute = '/playback/video';
|
||||
|
||||
/**
|
||||
* == INTERFACES ==
|
||||
@@ -56,12 +63,12 @@ class PlayerElementStore {
|
||||
return state.isStretched;
|
||||
}
|
||||
|
||||
public set isStretched(newisStretched: boolean) {
|
||||
state.isStretched = newisStretched;
|
||||
public set isStretched(newIsStretched: boolean) {
|
||||
state.isStretched = newIsStretched;
|
||||
}
|
||||
|
||||
public get isFullscreenVideoPlayer(): boolean {
|
||||
return useRouter().currentRoute.value.fullPath === '/playback/video';
|
||||
return useRouter().currentRoute.value.fullPath === fullscreenRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,7 +83,7 @@ class PlayerElementStore {
|
||||
const router = useRouter();
|
||||
|
||||
if (!this.isFullscreenVideoPlayer) {
|
||||
router.push('/playback/video');
|
||||
router.push(fullscreenRoute);
|
||||
} else {
|
||||
router.replace(
|
||||
typeof router.options.history.state.back === 'string'
|
||||
@@ -162,6 +169,31 @@ class PlayerElementStore {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public constructor() {
|
||||
watch(
|
||||
() => playbackManager.currentItem,
|
||||
(newValue, oldValue) => {
|
||||
const router = useRouter();
|
||||
|
||||
if (
|
||||
(!newValue &&
|
||||
router.currentRoute.value.fullPath === fullscreenRoute) ||
|
||||
(newValue &&
|
||||
!oldValue &&
|
||||
playbackManager.currentlyPlayingMediaType === 'Video')
|
||||
) {
|
||||
/**
|
||||
* If no item is present, we either manually loaded this or playback is stopped
|
||||
* OR
|
||||
* If there was no item and there's now a video, default to going FS
|
||||
*/
|
||||
playerElement.toggleFullscreenVideoPlayer();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const playerElement = new PlayerElementStore();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
isTv,
|
||||
isApple,
|
||||
isChrome,
|
||||
isEdge,
|
||||
@@ -7,68 +6,23 @@ import {
|
||||
} from '@/utils/browser-detection';
|
||||
|
||||
export interface SupportedFeatures {
|
||||
pictureInPicture: boolean;
|
||||
airPlay: boolean;
|
||||
googleCast: boolean;
|
||||
playbackRate: boolean;
|
||||
fullScreen: boolean;
|
||||
}
|
||||
|
||||
const supportedFeatures: SupportedFeatures = {
|
||||
pictureInPicture: false,
|
||||
airPlay: false,
|
||||
googleCast: false,
|
||||
playbackRate: false,
|
||||
fullScreen: false
|
||||
playbackRate: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Detects if the current platform supports showing fullscreen videos
|
||||
*
|
||||
* @returns - Whether fullscreen is supported or not
|
||||
*/
|
||||
function supportsFullscreen(): boolean {
|
||||
// TVs don't support fullscreen.
|
||||
if (isTv()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const element = document.documentElement;
|
||||
const video = document.createElement('video');
|
||||
|
||||
return !!(
|
||||
element.requestFullscreen ||
|
||||
// check properties that are not recorded on the element type
|
||||
('mozRequestFullScreen' in element && element.mozRequestFullScreen) ||
|
||||
('webkitRequestFullscreen' in element && element.webkitRequestFullscreen) ||
|
||||
('msRequestFullscreen' in element && element.msRequestFullscreen) ||
|
||||
('webkitEnterFullscreen' in video && video.webkitEnterFullscreen)
|
||||
);
|
||||
}
|
||||
|
||||
const video = document.createElement('video');
|
||||
|
||||
if (
|
||||
// Check non-standard Safari PiP support
|
||||
('webkitSupportsPresentationMode' in video &&
|
||||
typeof video.webkitSupportsPresentationMode === 'function' &&
|
||||
video.webkitSupportsPresentationMode('picture-in-picture') &&
|
||||
'webkitSetPresentationMode' in video &&
|
||||
typeof video.webkitSetPresentationMode === 'function') ||
|
||||
// Check standard PiP support
|
||||
document.pictureInPictureEnabled
|
||||
) {
|
||||
supportedFeatures.pictureInPicture = true;
|
||||
}
|
||||
|
||||
if (typeof video.playbackRate === 'number') {
|
||||
supportedFeatures.playbackRate = true;
|
||||
}
|
||||
|
||||
if (supportsFullscreen()) {
|
||||
supportedFeatures.fullScreen = true;
|
||||
}
|
||||
|
||||
if (isApple()) {
|
||||
supportedFeatures.airPlay = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user