308 lines
13 KiB
Vue
308 lines
13 KiB
Vue
<template>
|
|
<card-page
|
|
id="profile-nowplaying"
|
|
class="nowplaying"
|
|
header-id="hdr_now_playing"
|
|
>
|
|
<template #header="{id}">
|
|
<div class="d-flex align-items-center">
|
|
<h3
|
|
:id="id"
|
|
class="flex-shrink card-title my-0"
|
|
>
|
|
{{ $gettext('On the Air') }}
|
|
</h3>
|
|
<h6
|
|
class="card-subtitle text-end flex-fill my-0"
|
|
style="line-height: 1;"
|
|
>
|
|
<icon
|
|
class="align-middle"
|
|
:icon="IconHeadphones"
|
|
/>
|
|
<span class="ps-1">
|
|
{{ langListeners }}
|
|
</span>
|
|
|
|
<br>
|
|
<small>
|
|
<span class="pe-1">{{ np.listeners.unique }}</span>
|
|
{{ $gettext('Unique') }}
|
|
</small>
|
|
</h6>
|
|
<router-link
|
|
v-if="userAllowedForStation(StationPermission.Reports)"
|
|
class="flex-shrink btn btn-link text-white ms-2 px-1 py-2"
|
|
:to="{name: 'stations:reports:listeners'}"
|
|
:title="$gettext('Listener Report')"
|
|
>
|
|
<icon :icon="IconLogs" />
|
|
</router-link>
|
|
</div>
|
|
</template>
|
|
|
|
<div class="card-body">
|
|
<loading :loading="np.loading">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="clearfix">
|
|
<div class="d-table">
|
|
<div class="d-table-row">
|
|
<div class="d-table-cell align-middle text-end pe-2 pb-2">
|
|
<icon :icon="IconMusicNote" />
|
|
</div>
|
|
<div class="d-table-cell align-middle w-100 pb-2">
|
|
<h5 class="m-0">
|
|
{{ $gettext('Now Playing') }}
|
|
</h5>
|
|
</div>
|
|
</div>
|
|
<div class="d-table-row">
|
|
<div class="d-table-cell align-top text-end pe-2">
|
|
<a
|
|
v-if="np.now_playing.song.art"
|
|
v-lightbox
|
|
:href="np.now_playing.song.art"
|
|
target="_blank"
|
|
>
|
|
<img
|
|
class="rounded"
|
|
:src="np.now_playing.song.art"
|
|
alt="Album Art"
|
|
style="width: 50px;"
|
|
>
|
|
</a>
|
|
</div>
|
|
<div class="d-table-cell align-middle w-100">
|
|
<div v-if="!np.is_online">
|
|
<h5 class="media-heading m-0 text-muted">
|
|
{{ offlineText ?? $gettext('Station Offline') }}
|
|
</h5>
|
|
</div>
|
|
<div v-else-if="np.now_playing.song.title !== ''">
|
|
<h6
|
|
class="media-heading m-0"
|
|
style="line-height: 1.2;"
|
|
>
|
|
{{ np.now_playing.song.title }}<br>
|
|
<small class="text-muted">{{ np.now_playing.song.artist }}</small>
|
|
</h6>
|
|
</div>
|
|
<div v-else>
|
|
<h6
|
|
class="media-heading m-0"
|
|
style="line-height: 1.2;"
|
|
>
|
|
{{ np.now_playing.song.text }}
|
|
</h6>
|
|
</div>
|
|
<div v-if="np.now_playing.playlist">
|
|
<small class="text-muted">
|
|
{{ $gettext('Playlist') }}
|
|
: {{ np.now_playing.playlist }}</small>
|
|
</div>
|
|
<div
|
|
v-if="currentTrackElapsedDisplay"
|
|
class="nowplaying-progress"
|
|
>
|
|
<small>
|
|
{{ currentTrackElapsedDisplay }} / {{ currentTrackDurationDisplay }}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div
|
|
v-if="!np.live.is_live && np.playing_next"
|
|
class="clearfix"
|
|
>
|
|
<div class="d-table">
|
|
<div class="d-table-row">
|
|
<div class="d-table-cell align-middle pe-2 text-end pb-2">
|
|
<icon :icon="IconSkipNext" />
|
|
</div>
|
|
<div class="d-table-cell align-middle w-100 pb-2">
|
|
<h5 class="m-0">
|
|
{{ $gettext('Playing Next') }}
|
|
</h5>
|
|
</div>
|
|
</div>
|
|
<div class="d-table-row">
|
|
<div class="d-table-cell align-top text-end pe-2">
|
|
<a
|
|
v-if="np.playing_next.song.art"
|
|
v-lightbox
|
|
:href="np.playing_next.song.art"
|
|
target="_blank"
|
|
>
|
|
<img
|
|
:src="np.playing_next.song.art"
|
|
class="rounded"
|
|
alt="Album Art"
|
|
style="width: 40px;"
|
|
>
|
|
</a>
|
|
</div>
|
|
<div class="d-table-cell align-middle w-100">
|
|
<div v-if="np.playing_next.song.title !== ''">
|
|
<h6
|
|
class="media-heading m-0"
|
|
style="line-height: 1;"
|
|
>
|
|
{{ np.playing_next.song.title }}<br>
|
|
<small class="text-muted">{{ np.playing_next.song.artist }}</small>
|
|
</h6>
|
|
</div>
|
|
<div v-else>
|
|
<h6
|
|
class="media-heading m-0"
|
|
style="line-height: 1;"
|
|
>
|
|
{{ np.playing_next.song.text }}
|
|
</h6>
|
|
</div>
|
|
|
|
<div v-if="np.playing_next.playlist">
|
|
<small class="text-muted">
|
|
{{ $gettext('Playlist') }}
|
|
: {{ np.playing_next.playlist }}</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-else-if="np.live.is_live"
|
|
class="clearfix"
|
|
>
|
|
<h6 style="margin-left: 22px;">
|
|
<icon :icon="IconMic" />
|
|
{{ $gettext('Live') }}
|
|
</h6>
|
|
|
|
<h4
|
|
class="media-heading"
|
|
style="margin-left: 22px;"
|
|
>
|
|
{{ np.live.streamer_name }}
|
|
</h4>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</loading>
|
|
</div>
|
|
|
|
<template
|
|
v-if="isLiquidsoap && userAllowedForStation(StationPermission.Broadcasting)"
|
|
#footer_actions
|
|
>
|
|
<button
|
|
v-if="!np.live.is_live"
|
|
id="btn_skip_song"
|
|
type="button"
|
|
class="btn btn-link text-primary"
|
|
@click="makeApiCall(backendSkipSongUri)"
|
|
>
|
|
<icon :icon="IconSkipNext" />
|
|
<span>
|
|
{{ $gettext('Skip Song') }}
|
|
</span>
|
|
</button>
|
|
<button
|
|
v-if="np.live.is_live"
|
|
id="btn_disconnect_streamer"
|
|
type="button"
|
|
class="btn btn-link text-primary"
|
|
@click="makeApiCall(backendDisconnectStreamerUri)"
|
|
>
|
|
<icon :icon="IconVolumeOff" />
|
|
<span>
|
|
{{ $gettext('Disconnect Streamer') }}
|
|
</span>
|
|
</button>
|
|
<button
|
|
id="btn_update_metadata"
|
|
type="button"
|
|
class="btn btn-link text-secondary"
|
|
@click="updateMetadata()"
|
|
>
|
|
<icon :icon="IconUpdate" />
|
|
<span>
|
|
{{ $gettext('Update Metadata') }}
|
|
</span>
|
|
</button>
|
|
</template>
|
|
</card-page>
|
|
|
|
<template v-if="isLiquidsoap && userAllowedForStation(StationPermission.Broadcasting)">
|
|
<update-metadata-modal ref="$updateMetadataModal" />
|
|
</template>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import {BackendAdapter} from '~/entities/RadioAdapters';
|
|
import Icon from '~/components/Common/Icon.vue';
|
|
import {computed, Ref, ref} from "vue";
|
|
import {useTranslate} from "~/vendor/gettext";
|
|
import nowPlayingPanelProps from "~/components/Stations/Profile/nowPlayingPanelProps";
|
|
import useNowPlaying from "~/functions/useNowPlaying";
|
|
import Loading from "~/components/Common/Loading.vue";
|
|
import CardPage from "~/components/Common/CardPage.vue";
|
|
import {useLightbox} from "~/vendor/lightbox";
|
|
import {StationPermission, userAllowedForStation} from "~/acl";
|
|
import {useAzuraCastStation} from "~/vendor/azuracast";
|
|
import {
|
|
IconHeadphones,
|
|
IconLogs,
|
|
IconMic,
|
|
IconMusicNote,
|
|
IconSkipNext,
|
|
IconUpdate,
|
|
IconVolumeOff
|
|
} from "~/components/Common/icons";
|
|
import UpdateMetadataModal from "~/components/Stations/Profile/UpdateMetadataModal.vue";
|
|
|
|
const props = defineProps({
|
|
...nowPlayingPanelProps,
|
|
});
|
|
|
|
const emit = defineEmits(['api-call']);
|
|
|
|
const {offlineText} = useAzuraCastStation();
|
|
|
|
const {
|
|
np,
|
|
currentTrackDurationDisplay,
|
|
currentTrackElapsedDisplay
|
|
} = useNowPlaying(props);
|
|
|
|
const {$ngettext} = useTranslate();
|
|
|
|
const langListeners = computed(() => {
|
|
return $ngettext(
|
|
'%{listeners} Listener',
|
|
'%{listeners} Listeners',
|
|
np.value?.listeners?.total ?? 0,
|
|
{listeners: np.value?.listeners?.total ?? 0}
|
|
);
|
|
});
|
|
|
|
const isLiquidsoap = computed(() => {
|
|
return props.backendType === BackendAdapter.Liquidsoap;
|
|
});
|
|
|
|
const {vLightbox} = useLightbox();
|
|
|
|
const makeApiCall = (uri) => {
|
|
emit('api-call', uri);
|
|
};
|
|
|
|
const $updateMetadataModal: Ref<InstanceType<typeof UpdateMetadataModal> | null> = ref(null);
|
|
const updateMetadata = () => {
|
|
$updateMetadataModal.value?.open();
|
|
}
|
|
</script>
|