mirror of
https://github.com/jellyfin/jellyfin-vue.git
synced 2026-09-03 05:10:26 +03:00
feat(video-osd): add playback information
This commit is contained in:
committed by
Julien Machiels
parent
bbf81795fb
commit
ca521dff5a
@@ -20,7 +20,6 @@
|
||||
<v-btn
|
||||
class="align-self-center active-button"
|
||||
icon
|
||||
disabled
|
||||
v-bind="attrs"
|
||||
v-on="{ ...tooltip, ...menu }"
|
||||
>
|
||||
@@ -92,7 +91,7 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-list-item @click.stop="$emit('open-playback-data')">
|
||||
<v-list-item-content>
|
||||
{{ $t('playbackData') }}
|
||||
</v-list-item-content>
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
<template>
|
||||
<v-container
|
||||
v-if="sessionInfo"
|
||||
class="playback-data-dialog pointer-events-none pa-lg-6"
|
||||
>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6" lg="4" xl="3">
|
||||
<v-card class="mt-12 pb-6">
|
||||
<v-card-title class="pb-2">
|
||||
{{ $t('playbackInfo.name') }}
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
class="pointer-events-all"
|
||||
icon
|
||||
@click="$emit('close-playback-data')"
|
||||
>
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text class="py-0">
|
||||
<div v-if="getPlayMethod" class="d-flex">
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="$t('playbackInfo.playMethod.name')"
|
||||
/>
|
||||
<div>{{ getPlayMethod }}</div>
|
||||
</div>
|
||||
<div v-if="getStreamType" class="d-flex">
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="$t('playbackInfo.streamType.name')"
|
||||
/>
|
||||
<div>{{ getStreamType }}</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title v-if="playerStats" class="py-2">
|
||||
{{ $t('playerInfo.name') }}
|
||||
</v-card-title>
|
||||
<v-card-text v-if="playerStats" class="py-0">
|
||||
<div v-if="playerStats.width && playerStats.height" class="d-flex">
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="$t('playerInfo.videoResolution.name')"
|
||||
/>
|
||||
<div>
|
||||
{{
|
||||
$t('playerInfo.videoResolution.value', {
|
||||
width: playerStats.width,
|
||||
height: playerStats.height
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="videoDimensions.width && videoDimensions.height"
|
||||
class="d-flex"
|
||||
>
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="$t('playerInfo.playbackResolution.name')"
|
||||
/>
|
||||
<div>
|
||||
{{ $t('playerInfo.videoResolution.value', videoDimensions) }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isNaN(playerStats.decodedFrames)" class="d-flex">
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="$t('playerInfo.decodedFrames.name')"
|
||||
/>
|
||||
<div>{{ playerStats.decodedFrames }}</div>
|
||||
</div>
|
||||
<div v-if="!isNaN(playerStats.corruptedFrames)" class="d-flex">
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="$t('playerInfo.droppedFrames.name')"
|
||||
/>
|
||||
<div>{{ playerStats.droppedFrames }}</div>
|
||||
</div>
|
||||
<div v-if="!isNaN(playerStats.corruptedFrames)" class="d-flex">
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="$t('playerInfo.corruptedFrames.name')"
|
||||
/>
|
||||
<div>{{ playerStats.corruptedFrames }}</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title class="py-2">
|
||||
{{ $t('mediaInfo.name') }}
|
||||
</v-card-title>
|
||||
<v-card-text class="py-0">
|
||||
<div v-if="mediaContainer" class="d-flex">
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="$t('mediaInfo.container.name')"
|
||||
/>
|
||||
<div>
|
||||
{{ mediaContainer }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="mediaVideoCodec" class="d-flex">
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="$t('mediaInfo.videoCodec.name')"
|
||||
/>
|
||||
<div>
|
||||
{{ mediaVideoCodec }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="mediaAudioCodec" class="d-flex">
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="$t('mediaInfo.audioCodec.name')"
|
||||
/>
|
||||
<div>
|
||||
{{ mediaAudioCodec }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="mediaSubtitleCodec" class="d-flex">
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="$t('mediaInfo.subtitleCodec.name')"
|
||||
/>
|
||||
<div>
|
||||
{{ mediaSubtitleCodec }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="mediaAudioChannels" class="d-flex">
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="$t('mediaInfo.audioChannels.name')"
|
||||
/>
|
||||
<div>
|
||||
{{ mediaAudioChannels }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="mediaTotalBitrate" class="d-flex">
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="$t('mediaInfo.bitrate.name')"
|
||||
/>
|
||||
<div>
|
||||
{{ mediaTotalBitrate }}
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title v-if="isTranscoding" class="py-2">
|
||||
{{ $t('transcodingInfo.name') }}
|
||||
</v-card-title>
|
||||
<v-card-text class="py-0">
|
||||
<div
|
||||
v-if="
|
||||
isTranscoding &&
|
||||
sessionInfo.TranscodingInfo &&
|
||||
sessionInfo.TranscodingInfo.TranscodeReasons.length > 0
|
||||
"
|
||||
class="d-flex"
|
||||
>
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="
|
||||
$tc(
|
||||
'playbackInfo.transcodingReason.name',
|
||||
sessionInfo.TranscodingInfo.TranscodeReasons.length
|
||||
)
|
||||
"
|
||||
/>
|
||||
<div class="d-flex flex-column">
|
||||
<div
|
||||
v-for="(reason, index) in sessionInfo.TranscodingInfo
|
||||
.TranscodeReasons"
|
||||
:key="index"
|
||||
>
|
||||
{{ $t(camelCase(reason)) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
sessionInfo.TranscodingInfo &&
|
||||
sessionInfo.TranscodingInfo.Framerate
|
||||
"
|
||||
class="d-flex"
|
||||
>
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="$t('playbackInfo.transcodingFramerate.name')"
|
||||
/>
|
||||
<div>
|
||||
{{
|
||||
$t('playbackInfo.transcodingFramerate.value', {
|
||||
value: sessionInfo.TranscodingInfo.Framerate
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
sessionInfo.TranscodingInfo &&
|
||||
sessionInfo.TranscodingInfo.CompletionPercentage
|
||||
"
|
||||
class="d-flex flex-column"
|
||||
>
|
||||
<div class="d-flex">
|
||||
<div
|
||||
class="font-weight-bold mr-2"
|
||||
v-text="$t('playbackInfo.transcodingProgress')"
|
||||
/>
|
||||
<div>
|
||||
{{
|
||||
sessionInfo.TranscodingInfo.CompletionPercentage.toFixed(
|
||||
1
|
||||
) + '%'
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<v-progress-linear
|
||||
class="d-inline-block mt-2"
|
||||
:value="
|
||||
sessionInfo.TranscodingInfo.CompletionPercentage.toFixed(1)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { SessionInfo } from '@jellyfin/client-axios';
|
||||
import Vue from 'vue';
|
||||
import { mapGetters, mapState } from 'vuex';
|
||||
import { camelCase } from 'camel-case';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
updateSessionInterval: null as number | null,
|
||||
sessionInfo: null as SessionInfo | null,
|
||||
playerStats: {},
|
||||
videoDimensions: { width: 0, height: 0 }
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState('deviceProfile', ['deviceId']),
|
||||
...mapState('playbackManager', ['currentMediaSource']),
|
||||
...mapGetters('playbackManager', [
|
||||
'getCurrentVideoTrack',
|
||||
'getCurrentAudioTrack',
|
||||
'getCurrentSubtitleTrack'
|
||||
]),
|
||||
isTranscoding(): boolean {
|
||||
return !!(
|
||||
this.sessionInfo?.PlayState?.PlayMethod === 'Transcode' ||
|
||||
this.sessionInfo?.TranscodingInfo
|
||||
);
|
||||
},
|
||||
getStreamType(): string {
|
||||
if (window.player?.getAssetUri()) {
|
||||
if (window.player.getAssetUri().includes('.m3u8')) {
|
||||
return this.$t('playbackInfo.streamType.hls');
|
||||
} else if (window.player.getAssetUri().includes('.mpd')) {
|
||||
return this.$t('playbackInfo.streamType.dash');
|
||||
} else {
|
||||
return this.$t('playbackInfo.streamType.video');
|
||||
}
|
||||
}
|
||||
|
||||
return this.$t('playbackInfo.streamType.unknown');
|
||||
},
|
||||
getPlayMethod(): string {
|
||||
if (this.sessionInfo?.PlayState?.PlayMethod === 'Transcode') {
|
||||
return this.$t('playbackInfo.playMethod.transcode');
|
||||
} else if (this.sessionInfo?.PlayState?.PlayMethod === 'DirectStream') {
|
||||
return this.$t('playbackInfo.playMethod.remux');
|
||||
} else if (this.sessionInfo?.PlayState?.PlayMethod === 'DirectPlay') {
|
||||
return this.$t('playbackInfo.playMethod.direct');
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
mediaContainer(): string | null | undefined {
|
||||
if (
|
||||
this.sessionInfo?.TranscodingInfo?.Container &&
|
||||
this.sessionInfo.TranscodingInfo.Container !==
|
||||
this.sessionInfo?.NowPlayingItem?.Container
|
||||
) {
|
||||
return `${this.sessionInfo?.NowPlayingItem?.Container} ➞ ${this.sessionInfo?.TranscodingInfo?.Container}`;
|
||||
}
|
||||
|
||||
return this.sessionInfo?.NowPlayingItem?.Container;
|
||||
},
|
||||
mediaVideoCodec(): string | null | undefined {
|
||||
if (this.getCurrentVideoTrack) {
|
||||
if (
|
||||
(this.sessionInfo?.TranscodingInfo?.VideoCodec &&
|
||||
this.getCurrentVideoTrack.Codec !==
|
||||
this.sessionInfo.TranscodingInfo.VideoCodec) ||
|
||||
!this.sessionInfo?.TranscodingInfo?.IsAudioDirect
|
||||
) {
|
||||
return `${this.getCurrentVideoTrack.Codec} ➞ ${this.sessionInfo?.TranscodingInfo?.VideoCodec}`;
|
||||
}
|
||||
|
||||
return this.getCurrentVideoTrack.Codec;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
mediaAudioCodec(): string | null | undefined {
|
||||
if (this.getCurrentAudioTrack) {
|
||||
if (
|
||||
(this.sessionInfo?.TranscodingInfo?.AudioCodec &&
|
||||
this.getCurrentAudioTrack.Codec !==
|
||||
this.sessionInfo.TranscodingInfo.AudioCodec) ||
|
||||
!this.sessionInfo?.TranscodingInfo?.IsAudioDirect
|
||||
) {
|
||||
return `${this.getCurrentAudioTrack.Codec} ➞ ${this.sessionInfo?.TranscodingInfo?.AudioCodec}`;
|
||||
}
|
||||
|
||||
return this.getCurrentAudioTrack.Codec;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
mediaSubtitleCodec(): string | null | undefined {
|
||||
return this.getCurrentSubtitleTrack?.Codec;
|
||||
},
|
||||
mediaAudioChannels(): string | null | undefined {
|
||||
if (
|
||||
this.sessionInfo?.TranscodingInfo?.AudioChannels &&
|
||||
this.getCurrentAudioTrack.Channels !==
|
||||
this.sessionInfo.TranscodingInfo.AudioChannels
|
||||
) {
|
||||
return `${this.getCurrentAudioTrack.Channels} ➞ ${this.sessionInfo?.TranscodingInfo?.AudioChannels}`;
|
||||
}
|
||||
|
||||
return this.getCurrentAudioTrack?.Channels;
|
||||
},
|
||||
mediaTotalBitrate(): string | null | undefined {
|
||||
if (
|
||||
this.sessionInfo?.TranscodingInfo?.Bitrate &&
|
||||
this.sessionInfo.TranscodingInfo.Bitrate !==
|
||||
this.currentMediaSource.Bitrate
|
||||
) {
|
||||
return `${this.getDisplayBitrate(
|
||||
this.currentMediaSource.Bitrate
|
||||
)} ➞ ${this.getDisplayBitrate(
|
||||
this.sessionInfo?.TranscodingInfo?.Bitrate
|
||||
)}`;
|
||||
}
|
||||
|
||||
return this.getDisplayBitrate(this.currentMediaSource.Bitrate);
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
this.updateSessionInterval = window.setInterval(() => {
|
||||
this.updateSession();
|
||||
}, 1000);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.updateSessionInterval !== null) {
|
||||
window.clearInterval(this.updateSessionInterval);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
camelCase(value: string): string {
|
||||
return camelCase(value);
|
||||
},
|
||||
getDisplayBitrate(bitrate: number): string {
|
||||
if (bitrate > 1000000) {
|
||||
return this.$t('formats.mbps', {
|
||||
value: (bitrate / 1000000).toFixed(1)
|
||||
});
|
||||
} else {
|
||||
return this.$t('formats.kbps', {
|
||||
value: Math.floor(bitrate / 1000)
|
||||
});
|
||||
}
|
||||
},
|
||||
async updateSession(): Promise<void> {
|
||||
this.sessionInfo = (
|
||||
await this.$api.session.getSessions({
|
||||
deviceId: this.deviceId
|
||||
})
|
||||
).data?.[0];
|
||||
|
||||
this.playerStats = window.player.getStats();
|
||||
|
||||
// Compute the displayed video's size
|
||||
const videoRatio =
|
||||
window.player.getMediaElement().videoWidth /
|
||||
window.player.getMediaElement().videoHeight;
|
||||
let width = window.player.getMediaElement().offsetWidth;
|
||||
let height = window.player.getMediaElement().offsetHeight;
|
||||
|
||||
const elementRatio = width / height;
|
||||
|
||||
// If the video element is short and wide
|
||||
if (elementRatio > videoRatio) {
|
||||
width = height * videoRatio;
|
||||
} else {
|
||||
height = width / videoRatio;
|
||||
}
|
||||
|
||||
this.videoDimensions = {
|
||||
width: Math.floor(width),
|
||||
height: Math.floor(height)
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -19,6 +19,10 @@
|
||||
:width="$vuetify.breakpoint.mobile ? '60vw' : '25vw'"
|
||||
:value="isPlaying"
|
||||
>
|
||||
<playback-info-card
|
||||
v-show="!isMinimized && playbackData"
|
||||
@close-playback-data="playbackData = false"
|
||||
/>
|
||||
<v-hover v-slot="{ hover }">
|
||||
<v-card class="player-card" width="100%">
|
||||
<v-container fill-height fluid class="pa-0 justify-center">
|
||||
@@ -205,6 +209,7 @@
|
||||
<playback-settings-button
|
||||
:nudge-top="$vuetify.breakpoint.mdAndUp ? 60 : 30"
|
||||
@input="onQueueChangeHandler($event)"
|
||||
@open-playback-data="onOpenPlaybackData"
|
||||
/>
|
||||
<v-btn
|
||||
v-if="$features.pictureInPicture"
|
||||
@@ -254,7 +259,8 @@ export default Vue.extend({
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
unsubscribe(): void {},
|
||||
fullScreenVideo: false,
|
||||
keepOpen: false
|
||||
keepOpen: false,
|
||||
playbackData: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -587,6 +593,9 @@ export default Vue.extend({
|
||||
} else if (!value) {
|
||||
this.setFullscreenTimeout();
|
||||
}
|
||||
},
|
||||
onOpenPlaybackData(): void {
|
||||
this.playbackData = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -619,6 +628,11 @@ export default Vue.extend({
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.playback-data-dialog {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.v-card.player-card {
|
||||
background-color: black !important;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ declare global {
|
||||
interface Window {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
muxjs: any;
|
||||
player: any;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +102,9 @@ export default Vue.extend({
|
||||
shaka.polyfill.installAll();
|
||||
|
||||
if (shaka.Player.isBrowserSupported()) {
|
||||
this.player = new shaka.Player(this.$refs.shakaPlayer);
|
||||
// We use a global for ease of debugging and to fetch data from the playback information popup
|
||||
window.player = new shaka.Player(this.$refs.shakaPlayer);
|
||||
this.player = window.player;
|
||||
|
||||
// Register player events
|
||||
this.player.addEventListener('error', this.onPlayerError);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"artists": "Artists",
|
||||
"aspectRatio": "Aspect ratio",
|
||||
"audio": "Audio",
|
||||
"audioCodecNotSupported": "The audio codec is not supported",
|
||||
"badRequest": "Bad request. Try again",
|
||||
"browserNotSupported": "Your browser is not supported for playing this file.",
|
||||
"buttons": {
|
||||
@@ -25,6 +26,7 @@
|
||||
"communityRating": "Community rating",
|
||||
"confirm": "Confirm",
|
||||
"connect": "Connect",
|
||||
"containerNotSupported": "The container is not supported",
|
||||
"continueListening": "Continue listening",
|
||||
"continueWatching": "Continue watching",
|
||||
"criticRating": "Critic rating",
|
||||
@@ -56,6 +58,10 @@
|
||||
"features": "Features",
|
||||
"filter": "Filter",
|
||||
"filtersNotFound": "Unable to load filters",
|
||||
"formats": {
|
||||
"kbps": "{value} Kbps",
|
||||
"mbps": "{value} Mbps"
|
||||
},
|
||||
"fullScreen": "Full screen",
|
||||
"general": "General",
|
||||
"genericJellyfinPlaceholderDevice": "Generic Jellyfin device",
|
||||
@@ -159,6 +165,27 @@
|
||||
"noLogsFound": "No logs found"
|
||||
},
|
||||
"manualLogin": "Manual login",
|
||||
"mediaInfo": {
|
||||
"audioChannels": {
|
||||
"name": "Audio channels:"
|
||||
},
|
||||
"audioCodec": {
|
||||
"name": "Audio codec:"
|
||||
},
|
||||
"bitrate": {
|
||||
"name": "Bitrate:"
|
||||
},
|
||||
"container": {
|
||||
"name": "Container:"
|
||||
},
|
||||
"name": "Media",
|
||||
"subtitleCodec": {
|
||||
"name": "Subtitle codec:"
|
||||
},
|
||||
"videoCodec": {
|
||||
"name": "Video codec:"
|
||||
}
|
||||
},
|
||||
"menu": "Menu",
|
||||
"metadata": {
|
||||
"source": "Source",
|
||||
@@ -214,9 +241,52 @@
|
||||
"shuffle": "Shuffle",
|
||||
"shuffleAll": "Shuffle all"
|
||||
},
|
||||
"playbackData": "Playback data",
|
||||
"playbackData": "Playback information",
|
||||
"playbackInfo": {
|
||||
"name": "Playback",
|
||||
"playMethod": {
|
||||
"direct": "Direct",
|
||||
"name": "Method:",
|
||||
"remux": "Remux",
|
||||
"transcode": "Transcode"
|
||||
},
|
||||
"streamType": {
|
||||
"dash": "DASH",
|
||||
"hls": "HLS",
|
||||
"name": "Stream type:",
|
||||
"unknown": "Unknown",
|
||||
"video": "Video"
|
||||
},
|
||||
"transcodingFramerate": {
|
||||
"name": "Framerate:",
|
||||
"value": "{value} fps"
|
||||
},
|
||||
"transcodingProgress": "Progress:",
|
||||
"transcodingReason": {
|
||||
"name": "Reason: | Reasons:"
|
||||
}
|
||||
},
|
||||
"playbackSettings": "Playback settings",
|
||||
"played": "Played",
|
||||
"playerInfo": {
|
||||
"corruptedFrames": {
|
||||
"name": "Corrupted frames:"
|
||||
},
|
||||
"decodedFrames": {
|
||||
"name": "Decoded frames:"
|
||||
},
|
||||
"droppedFrames": {
|
||||
"name": "Dropped frames:"
|
||||
},
|
||||
"name": "Player",
|
||||
"playbackResolution": {
|
||||
"name": "Viewport resolution:"
|
||||
},
|
||||
"videoResolution": {
|
||||
"name": "Video resolution:",
|
||||
"value": "{width}✕{height}"
|
||||
}
|
||||
},
|
||||
"present": "Present",
|
||||
"producer": "Producer",
|
||||
"quality": "Quality",
|
||||
@@ -359,6 +429,7 @@
|
||||
"speed": "Speed",
|
||||
"status": "Status",
|
||||
"studios": "Studios",
|
||||
"subtitleCodecNotSupported": "The subtitle codec is not supported",
|
||||
"subtitles": "Subtitles",
|
||||
"syncPlayGroups": "SyncPlay groups",
|
||||
"syncingInProgress": "Syncing in progress",
|
||||
@@ -372,6 +443,9 @@
|
||||
"switchToLightMode": "Switch to light mode"
|
||||
},
|
||||
"trailer": "Trailer",
|
||||
"transcodingInfo": {
|
||||
"name": "Transcoding"
|
||||
},
|
||||
"type": "Type",
|
||||
"unableGetPublicUsers": "Unable to get users",
|
||||
"unableGetRelated": "Unable to get related items",
|
||||
@@ -394,6 +468,7 @@
|
||||
},
|
||||
"version": "Version",
|
||||
"video": "Video",
|
||||
"videoCodecNotSupported": "The video codec is not supported",
|
||||
"videoTypes": "Video Types",
|
||||
"viewDetails": "View details",
|
||||
"vueClientVersion": "Client version",
|
||||
|
||||
@@ -140,6 +140,36 @@ export const getters: GetterTree<PlaybackManagerState, RootState> = {
|
||||
return stream.Type === 'Subtitle';
|
||||
});
|
||||
}
|
||||
},
|
||||
getCurrentVideoTrack: (state) => {
|
||||
if (
|
||||
state.currentMediaSource !== null &&
|
||||
state.currentVideoStreamIndex !== null
|
||||
) {
|
||||
return state.currentMediaSource.MediaStreams?.filter((stream) => {
|
||||
return stream.Type === 'Video';
|
||||
})[state.currentVideoStreamIndex];
|
||||
}
|
||||
},
|
||||
getCurrentAudioTrack: (state) => {
|
||||
if (
|
||||
state.currentMediaSource !== null &&
|
||||
state.currentAudioStreamIndex !== null
|
||||
) {
|
||||
return state.currentMediaSource.MediaStreams?.filter((stream) => {
|
||||
return stream.Type === 'Audio';
|
||||
})[state.currentAudioStreamIndex];
|
||||
}
|
||||
},
|
||||
getCurrentSubtitleTrack: (state) => {
|
||||
if (
|
||||
state.currentMediaSource !== null &&
|
||||
state.currentSubtitleStreamIndex !== null
|
||||
) {
|
||||
return state.currentMediaSource.MediaStreams?.filter((stream) => {
|
||||
return stream.Type === 'Subtitle';
|
||||
})[state.currentSubtitleStreamIndex];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"@nuxtjs/pwa": "^3.3.5",
|
||||
"blurhash": "^1.1.3",
|
||||
"body-scroll-lock": "^3.1.5",
|
||||
"camel-case": "^4.1.2",
|
||||
"compare-versions": "^3.6.0",
|
||||
"cookie": "^0.4.1",
|
||||
"date-fns": "^2.22.1",
|
||||
|
||||
Reference in New Issue
Block a user