Support custom "Station Offline" and "Live Broadcast" text per-station.
This commit is contained in:
parent
fccf41032c
commit
6b3d2b2095
|
@ -24,6 +24,11 @@ release channel, you can take advantage of these new features and fixes.
|
|||
tasks, with a secondary instance that will automatically take over these functions if the main installation is
|
||||
nonresponsive.
|
||||
|
||||
- **Custom "Station Offline" and "Live Broadcast" Messages**: You can now specify, on a per-station level, the default
|
||||
message that shows when the station is offline or when a DJ has connected but has not yet sent any metadata. The
|
||||
former is located on the Branding Configuration page, and the latter is under the "Streamers/DJs" tab on the station
|
||||
profile.
|
||||
|
||||
## Code Quality/Technical Changes
|
||||
|
||||
- **Frontend Overhaul**: We have updated the code that powers the browser-facing frontend of our application. In
|
||||
|
|
|
@ -74,6 +74,6 @@ return static function (RouteCollectorProxy $app) {
|
|||
->setName('public:podcast:feed');
|
||||
}
|
||||
)
|
||||
->add(Middleware\GetStation::class)
|
||||
->add(Middleware\EnableView::class);
|
||||
->add(Middleware\EnableView::class)
|
||||
->add(Middleware\GetStation::class);
|
||||
};
|
||||
|
|
|
@ -112,6 +112,14 @@
|
|||
:label="$gettext('Customize DJ/Streamer Mount Point')"
|
||||
:description="$gettext('If your streaming software requires a specific mount point path, specify it here. Otherwise, use the default.')"
|
||||
/>
|
||||
|
||||
<form-group-field
|
||||
id="edit_form_backend_live_broadcast_text"
|
||||
class="col-md-6"
|
||||
:field="v$.backend_config.live_broadcast_text"
|
||||
:label="$gettext('Default Live Broadcast Message')"
|
||||
:description="$gettext('If a live DJ connects but has not yet sent metadata, this is the message that will display on player pages.')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</form-fieldset>
|
||||
|
@ -148,6 +156,7 @@ const {enableAdvancedFeatures} = useAzuraCast();
|
|||
const emit = defineEmits(['update:form']);
|
||||
const form = useVModel(props, 'form', emit);
|
||||
|
||||
|
||||
const {v$, tabClass} = useVuelidateOnFormTab(
|
||||
computed(() => {
|
||||
let validations = {
|
||||
|
@ -158,6 +167,7 @@ const {v$, tabClass} = useVuelidateOnFormTab(
|
|||
record_streams_format: {},
|
||||
record_streams_bitrate: {},
|
||||
dj_buffer: {numeric},
|
||||
live_broadcast_text: {}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -184,6 +194,7 @@ const {v$, tabClass} = useVuelidateOnFormTab(
|
|||
record_streams_format: 'mp3',
|
||||
record_streams_bitrate: 128,
|
||||
dj_buffer: 5,
|
||||
live_broadcast_text: 'Live Broadcast'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
<div v-if="!np.is_online">
|
||||
<h4 class="now-playing-title text-muted">
|
||||
{{ $gettext('Station Offline') }}
|
||||
{{ offlineText ?? $gettext('Station Offline') }}
|
||||
</h4>
|
||||
</div>
|
||||
<div v-else-if="np.now_playing.song.title !== ''">
|
||||
|
@ -148,6 +148,7 @@ import useNowPlaying from "~/functions/useNowPlaying";
|
|||
import playerProps from "~/components/Public/playerProps";
|
||||
import MuteButton from "~/components/Common/MuteButton.vue";
|
||||
import AlbumArt from "~/components/Common/AlbumArt.vue";
|
||||
import {useAzuraCastStation} from "~/vendor/azuracast";
|
||||
|
||||
const props = defineProps({
|
||||
...playerProps
|
||||
|
@ -155,6 +156,8 @@ const props = defineProps({
|
|||
|
||||
const emit = defineEmits(['np_updated']);
|
||||
|
||||
const {offlineText} = useAzuraCastStation();
|
||||
|
||||
const {
|
||||
np,
|
||||
currentTrackPercent,
|
||||
|
|
|
@ -23,6 +23,14 @@
|
|||
<loading :loading="isLoading">
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<form-group-field
|
||||
id="form_edit_offline_text"
|
||||
class="col-md-6"
|
||||
:field="v$.offline_text"
|
||||
:label="$gettext('Station Offline Display Text')"
|
||||
:description="$gettext('This will be shown on public player pages if the station is offline. Leave blank to default to a localized version of "%{message}".', {message: $gettext('Station Offline')})"
|
||||
/>
|
||||
|
||||
<form-group-field
|
||||
id="form_edit_default_album_art_url"
|
||||
class="col-md-6"
|
||||
|
@ -99,14 +107,16 @@ const error = ref(null);
|
|||
|
||||
const {form, resetForm, v$, ifValid} = useVuelidateOnForm(
|
||||
{
|
||||
'default_album_art_url': {},
|
||||
'public_custom_css': {},
|
||||
'public_custom_js': {},
|
||||
default_album_art_url: {},
|
||||
public_custom_css: {},
|
||||
public_custom_js: {},
|
||||
offline_text: {}
|
||||
},
|
||||
{
|
||||
'default_album_art_url': '',
|
||||
'public_custom_css': '',
|
||||
'public_custom_js': ''
|
||||
default_album_art_url: '',
|
||||
public_custom_css: '',
|
||||
public_custom_js: '',
|
||||
offline_text: ''
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
<div class="d-table-cell align-middle w-100">
|
||||
<div v-if="!np.is_online">
|
||||
<h5 class="media-heading m-0 text-muted">
|
||||
{{ $gettext('Station Offline') }}
|
||||
{{ offlineText ?? $gettext('Station Offline') }}
|
||||
</h5>
|
||||
</div>
|
||||
<div v-else-if="np.now_playing.song.title !== ''">
|
||||
|
@ -238,6 +238,7 @@ 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";
|
||||
|
||||
const props = defineProps({
|
||||
...nowPlayingPanelProps,
|
||||
|
@ -245,6 +246,8 @@ const props = defineProps({
|
|||
|
||||
const emit = defineEmits(['api-call']);
|
||||
|
||||
const {offlineText} = useAzuraCastStation();
|
||||
|
||||
const {
|
||||
np,
|
||||
currentTrackDurationDisplay,
|
||||
|
|
|
@ -13,6 +13,7 @@ export interface AzuraCastStationConstants {
|
|||
name: string | null,
|
||||
shortName: string | null,
|
||||
timezone: string | null,
|
||||
offlineText: string | null,
|
||||
}
|
||||
|
||||
export interface AzuraCastUserConstants {
|
||||
|
@ -60,6 +61,7 @@ export function useAzuraCastStation(): AzuraCastStationConstants {
|
|||
id: null,
|
||||
name: null,
|
||||
shortName: null,
|
||||
timezone: null
|
||||
timezone: null,
|
||||
offlineText: null
|
||||
};
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ final class SoundExchangeAction implements SingleActionInterface
|
|||
$historyRowsById = array_column($historyRows, null, 'media_id');
|
||||
|
||||
// Remove any reference to the "Stream Offline" song.
|
||||
$offlineSongHash = Song::createOffline()->getSongId();
|
||||
$offlineSongHash = Song::OFFLINE_SONG_ID;
|
||||
unset($historyRowsById[$offlineSongHash]);
|
||||
|
||||
// Assemble report items
|
||||
|
|
|
@ -164,7 +164,7 @@ final class NowPlayingApiGenerator
|
|||
$np->station = $this->stationApiGenerator->__invoke($station, $baseUri);
|
||||
$np->listeners = new Listeners();
|
||||
|
||||
$songObj = Song::createOffline();
|
||||
$songObj = Song::createOffline($station->getBrandingConfig()->getOfflineText());
|
||||
|
||||
$offlineApiNowPlaying = new CurrentSong();
|
||||
$offlineApiNowPlaying->sh_id = 0;
|
||||
|
|
|
@ -12,6 +12,8 @@ class Song implements SongInterface
|
|||
{
|
||||
use Traits\HasSongFields;
|
||||
|
||||
public final const OFFLINE_SONG_ID = '5a6a865199cf5df73b1417326d2ff24f';
|
||||
|
||||
public function __construct(?SongInterface $song = null)
|
||||
{
|
||||
if (null !== $song) {
|
||||
|
@ -62,6 +64,10 @@ class Song implements SongInterface
|
|||
|
||||
$songText = str_replace($removeChars, '', $songText);
|
||||
|
||||
if (empty($songText)) {
|
||||
return self::OFFLINE_SONG_ID;
|
||||
}
|
||||
|
||||
$hashBase = mb_strtolower($songText, 'UTF-8');
|
||||
return md5($hashBase);
|
||||
}
|
||||
|
@ -104,8 +110,10 @@ class Song implements SongInterface
|
|||
return self::createFromNowPlayingSong($currentSong);
|
||||
}
|
||||
|
||||
public static function createOffline(): self
|
||||
public static function createOffline(?string $text = null): self
|
||||
{
|
||||
return self::createFromText('Station Offline');
|
||||
$song = self::createFromText($text ?? 'Station Offline');
|
||||
$song->setSongId(self::OFFLINE_SONG_ID);
|
||||
return $song;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -421,6 +421,22 @@ class StationBackendConfiguration extends AbstractStationConfiguration
|
|||
$this->set(self::HLS_IS_DEFAULT, $value);
|
||||
}
|
||||
|
||||
public const LIVE_BROADCAST_TEXT = 'live_broadcast_text';
|
||||
|
||||
public function getLiveBroadcastText(): string
|
||||
{
|
||||
$text = $this->get(self::LIVE_BROADCAST_TEXT);
|
||||
|
||||
return (!empty($text))
|
||||
? $text
|
||||
: 'Live Broadcast';
|
||||
}
|
||||
|
||||
public function setLiveBroadcastText(?string $text): void
|
||||
{
|
||||
$this->set(self::LIVE_BROADCAST_TEXT, $text);
|
||||
}
|
||||
|
||||
public const CUSTOM_TOP = 'custom_config_top';
|
||||
public const CUSTOM_PRE_PLAYLISTS = 'custom_config_pre_playlists';
|
||||
public const CUSTOM_PRE_LIVE = 'custom_config_pre_live';
|
||||
|
|
|
@ -53,4 +53,20 @@ class StationBrandingConfiguration extends AbstractStationConfiguration
|
|||
{
|
||||
$this->set(self::PUBLIC_CUSTOM_JS, $js);
|
||||
}
|
||||
|
||||
public const OFFLINE_TEXT = 'offline_text';
|
||||
|
||||
public function getOfflineText(): ?string
|
||||
{
|
||||
$message = $this->get(self::OFFLINE_TEXT);
|
||||
|
||||
return (!empty($message))
|
||||
? $message
|
||||
: null;
|
||||
}
|
||||
|
||||
public function setOfflineText(?string $message): void
|
||||
{
|
||||
$this->set(self::OFFLINE_TEXT, $message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,12 +60,17 @@ trait HasSongFields
|
|||
return $this->song_id;
|
||||
}
|
||||
|
||||
protected function setSongId(string $songId): void
|
||||
{
|
||||
$this->song_id = $songId;
|
||||
}
|
||||
|
||||
public function updateSongId(): void
|
||||
{
|
||||
$text = $this->getText();
|
||||
$this->song_id = (null !== $text)
|
||||
$this->song_id = !empty($text)
|
||||
? Song::getSongHash($text)
|
||||
: Song::createOffline()->getSongId();
|
||||
: Song::OFFLINE_SONG_ID;
|
||||
}
|
||||
|
||||
public function getText(): ?string
|
||||
|
|
|
@ -67,8 +67,7 @@ final class RemoteAlbumArt
|
|||
public function getUrlForSong(SongInterface $song): ?string
|
||||
{
|
||||
// Avoid tracks that shouldn't ever hit remote APIs.
|
||||
$offlineSong = Song::createOffline();
|
||||
if ($song->getSongId() === $offlineSong->getSongId()) {
|
||||
if ($song->getSongId() === Song::OFFLINE_SONG_ID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -1010,6 +1010,10 @@ final class ConfigWriter implements EventSubscriberInterface
|
|||
|
||||
$harborParams = implode(', ', $harborParams);
|
||||
|
||||
$liveBroadcastText = self::cleanUpString(
|
||||
$settings->getLiveBroadcastText()
|
||||
);
|
||||
|
||||
$event->appendBlock(
|
||||
<<<LIQ
|
||||
# A Pre-DJ source of radio that can be broadcast if needed
|
||||
|
@ -1021,7 +1025,7 @@ final class ConfigWriter implements EventSubscriberInterface
|
|||
|
||||
def insert_missing(m) =
|
||||
if m == [] then
|
||||
[("title", "Live Broadcast"), ("is_live", "true")]
|
||||
[("title", "{$liveBroadcastText}"), ("is_live", "true")]
|
||||
else
|
||||
[("is_live", "true")]
|
||||
end
|
||||
|
|
|
@ -229,6 +229,7 @@ final class View extends Engine
|
|||
'name' => $station->getName(),
|
||||
'shortName' => $station->getShortName(),
|
||||
'timezone' => $station->getTimezone(),
|
||||
'offlineText' => $station->getBrandingConfig()->getOfflineText(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue