Skip to content

Commit

Permalink
fix(videoplayer): few refactors and lint fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
ferferga committed Feb 28, 2023
1 parent 07b0e54 commit f05baf9
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 99 deletions.
8 changes: 4 additions & 4 deletions frontend/src/components/Buttons/PlayButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/Playback/PiPVideoPlayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
:model-value="isHovering"
v-bind="props"
contained
eager
scrim
height="100%"
width="100%">
Expand Down
25 changes: 1 addition & 24 deletions frontend/src/components/Playback/PlayerElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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
*/
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/playback/video/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
45 changes: 28 additions & 17 deletions frontend/src/store/playbackManager.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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();
}
};

/**
Expand Down Expand Up @@ -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 {}
});
};

Expand Down
42 changes: 37 additions & 5 deletions frontend/src/store/playerElement.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -8,6 +14,7 @@ import { useRouter } from '@/composables';

const playbackManager = playbackManagerStore();
let subtitlesOctopus: SubtitlesOctopus | undefined;
const fullscreenRoute = '/playback/video';

/**
* == INTERFACES ==
Expand Down Expand Up @@ -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;
}

/**
Expand All @@ -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'
Expand Down Expand Up @@ -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();
Expand Down
48 changes: 1 addition & 47 deletions frontend/src/utils/supported-features.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,28 @@
import {
isTv,
isApple,
isChrome,
isEdge,
isChromiumBased
} 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;
}
Expand Down

0 comments on commit f05baf9

Please sign in to comment.