Fixes #4721 -- Base elapsed time in players on server time, not client time.
This commit is contained in:
parent
03b593f6b0
commit
6a5276820b
|
@ -142,6 +142,7 @@ return static function (CallableEventDispatcherInterface $dispatcher) {
|
|||
App\Sync\Task\RotateLogsTask::class,
|
||||
App\Sync\Task\RunAnalyticsTask::class,
|
||||
App\Sync\Task\RunBackupTask::class,
|
||||
App\Sync\Task\SendTimeOnSocketTask::class,
|
||||
App\Sync\Task\UpdateGeoLiteTask::class,
|
||||
App\Sync\Task\UpdateStorageLocationSizesTask::class,
|
||||
]);
|
||||
|
|
|
@ -17,7 +17,7 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const np = useNowPlaying(props);
|
||||
const {np} = useNowPlaying(props);
|
||||
|
||||
const history = computed(() => {
|
||||
return np.value.song_history ?? [];
|
||||
|
|
|
@ -28,18 +28,18 @@
|
|||
<h4 class="now-playing-title">{{ np.now_playing.song.text }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="time-display" v-if="time_display_played != null">
|
||||
<div class="time-display" v-if="currentTimeElapsedDisplay != null">
|
||||
<div class="time-display-played text-secondary">
|
||||
{{ time_display_played }}
|
||||
{{ currentTimeElapsedDisplay }}
|
||||
</div>
|
||||
<div class="time-display-progress">
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-secondary" role="progressbar"
|
||||
:style="{ width: time_percent+'%' }"></div>
|
||||
:style="{ width: currentTrackPercent+'%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="time-display-total text-secondary">
|
||||
{{ time_display_total }}
|
||||
{{ currentTimeTotalDisplay }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -48,14 +48,14 @@
|
|||
<hr>
|
||||
|
||||
<div class="radio-controls">
|
||||
<play-button class="radio-control-play-button" icon-class="outlined lg" :url="current_stream.url"
|
||||
:is-hls="current_stream.hls" is-stream></play-button>
|
||||
<play-button class="radio-control-play-button" icon-class="outlined lg" :url="currentStream.url"
|
||||
:is-hls="currentStream.hls" is-stream></play-button>
|
||||
|
||||
<div class="radio-control-select-stream">
|
||||
<div v-if="streams.length > 1" class="dropdown">
|
||||
<button class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" id="btn-select-stream"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ current_stream.name }}
|
||||
{{ currentStream.name }}
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="btn-select-stream">
|
||||
<a class="dropdown-item" v-for="stream in streams" href="javascript:"
|
||||
|
@ -212,7 +212,7 @@ import AudioPlayer from '~/components/Common/AudioPlayer';
|
|||
import Icon from '~/components/Common/Icon';
|
||||
import PlayButton from "~/components/Common/PlayButton";
|
||||
import {computed, onMounted, ref, shallowRef, watch} from "vue";
|
||||
import {useIntervalFn, useMounted, useStorage} from "@vueuse/core";
|
||||
import {useMounted, useStorage} from "@vueuse/core";
|
||||
import formatTime from "~/functions/formatTime";
|
||||
import {useTranslate} from "~/vendor/gettext";
|
||||
import useNowPlaying from "~/functions/useNowPlaying";
|
||||
|
@ -224,9 +224,9 @@ const props = defineProps({
|
|||
|
||||
const emit = defineEmits(['np_updated']);
|
||||
|
||||
const np = useNowPlaying(props);
|
||||
const np_elapsed = ref(0);
|
||||
const current_stream = shallowRef({
|
||||
const {np, currentTrackElapsed, currentTrackDuration} = useNowPlaying(props);
|
||||
|
||||
const currentStream = shallowRef({
|
||||
'name': '',
|
||||
'url': '',
|
||||
'hls': false,
|
||||
|
@ -240,11 +240,11 @@ const enable_hls = computed(() => {
|
|||
const {$gettext} = useTranslate();
|
||||
|
||||
const streams = computed(() => {
|
||||
let all_streams = [];
|
||||
let allStreams = [];
|
||||
let $np = np.value;
|
||||
|
||||
if (enable_hls.value) {
|
||||
all_streams.push({
|
||||
allStreams.push({
|
||||
'name': $gettext('HLS'),
|
||||
'url': $np.station.hls_url,
|
||||
'hls': true,
|
||||
|
@ -252,60 +252,55 @@ const streams = computed(() => {
|
|||
}
|
||||
|
||||
$np.station.mounts.forEach(function (mount) {
|
||||
all_streams.push({
|
||||
allStreams.push({
|
||||
'name': mount.name,
|
||||
'url': mount.url,
|
||||
'hls': false,
|
||||
});
|
||||
});
|
||||
$np.station.remotes.forEach(function (remote) {
|
||||
all_streams.push({
|
||||
allStreams.push({
|
||||
'name': remote.name,
|
||||
'url': remote.url,
|
||||
'hls': false,
|
||||
});
|
||||
});
|
||||
|
||||
return all_streams;
|
||||
return allStreams;
|
||||
});
|
||||
|
||||
const time_total = computed(() => {
|
||||
let $np = np.value;
|
||||
return $np?.now_playing?.duration ?? 0;
|
||||
});
|
||||
const currentTrackPercent = computed(() => {
|
||||
let $currentTrackElapsed = currentTrackElapsed.value;
|
||||
let $currentTrackDuration = currentTrackDuration.value;
|
||||
|
||||
const time_percent = computed(() => {
|
||||
let $np_elapsed = np_elapsed.value;
|
||||
let $time_total = time_total.value;
|
||||
|
||||
if (!$time_total) {
|
||||
if (!$currentTrackDuration) {
|
||||
return 0;
|
||||
}
|
||||
if ($np_elapsed > $time_total) {
|
||||
if ($currentTrackElapsed > $currentTrackDuration) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
return ($np_elapsed / $time_total) * 100;
|
||||
return ($currentTrackElapsed / $currentTrackDuration) * 100;
|
||||
});
|
||||
|
||||
const time_display_played = computed(() => {
|
||||
let $np_elapsed = np_elapsed.value;
|
||||
let $time_total = time_total.value;
|
||||
const currentTimeElapsedDisplay = computed(() => {
|
||||
let $currentTrackElapsed = currentTrackElapsed.value;
|
||||
let $currentTrackDuration = currentTrackDuration.value;
|
||||
|
||||
if (!$time_total) {
|
||||
if (!$currentTrackDuration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($np_elapsed > $time_total) {
|
||||
$np_elapsed = $time_total;
|
||||
if ($currentTrackElapsed > $currentTrackDuration) {
|
||||
$currentTrackElapsed = $currentTrackDuration;
|
||||
}
|
||||
|
||||
return formatTime($np_elapsed);
|
||||
return formatTime($currentTrackElapsed);
|
||||
});
|
||||
|
||||
const time_display_total = computed(() => {
|
||||
let $time_total = time_total.value;
|
||||
return ($time_total) ? formatTime($time_total) : null;
|
||||
const currentTimeTotalDisplay = computed(() => {
|
||||
let $currentTrackDuration = currentTrackDuration.value;
|
||||
return ($currentTrackDuration) ? formatTime($currentTrackDuration) : null;
|
||||
});
|
||||
|
||||
const isMounted = useMounted();
|
||||
|
@ -323,31 +318,13 @@ const fullVolume = () => {
|
|||
};
|
||||
|
||||
const switchStream = (new_stream) => {
|
||||
current_stream.value = new_stream;
|
||||
currentStream.value = new_stream;
|
||||
player.value.toggle(new_stream.url, true, new_stream.hls);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
useIntervalFn(
|
||||
() => {
|
||||
let $np = np.value;
|
||||
|
||||
let current_time = Math.floor(Date.now() / 1000);
|
||||
let $np_elapsed = current_time - $np.now_playing.played_at;
|
||||
|
||||
if ($np_elapsed < 0) {
|
||||
$np_elapsed = 0;
|
||||
} else if ($np_elapsed >= $np.now_playing.duration) {
|
||||
$np_elapsed = $np.now_playing.duration;
|
||||
}
|
||||
|
||||
np_elapsed.value = $np_elapsed;
|
||||
},
|
||||
1000
|
||||
);
|
||||
|
||||
if (props.autoplay) {
|
||||
switchStream(current_stream.value);
|
||||
switchStream(currentStream.value);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -356,27 +333,27 @@ const onNowPlayingUpdated = (np_new) => {
|
|||
|
||||
// Set a "default" current stream if none exists.
|
||||
let $streams = streams.value;
|
||||
let $current_stream = current_stream.value;
|
||||
let $currentStream = currentStream.value;
|
||||
|
||||
if ($current_stream.url === '' && $streams.length > 0) {
|
||||
if ($currentStream.url === '' && $streams.length > 0) {
|
||||
if (props.hlsIsDefault && enable_hls.value) {
|
||||
current_stream.value = $streams[0];
|
||||
currentStream.value = $streams[0];
|
||||
} else {
|
||||
$current_stream = null;
|
||||
$currentStream = null;
|
||||
|
||||
if (np_new.station.listen_url !== '') {
|
||||
$streams.forEach(function (stream) {
|
||||
if (stream.url === np_new.station.listen_url) {
|
||||
$current_stream = stream;
|
||||
$currentStream = stream;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ($current_stream === null) {
|
||||
$current_stream = $streams[0];
|
||||
if ($currentStream === null) {
|
||||
$currentStream = $streams[0];
|
||||
}
|
||||
|
||||
current_stream.value = $current_stream;
|
||||
currentStream.value = $currentStream;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -17,5 +17,5 @@ export default {
|
|||
autoplay: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import NowPlaying from '~/components/Entity/NowPlaying';
|
||||
import {onMounted, shallowRef, watch} from "vue";
|
||||
import {useEventSource} from "@vueuse/core";
|
||||
import {onMounted, ref, shallowRef, watch} from "vue";
|
||||
import {useEventSource, useIntervalFn} from "@vueuse/core";
|
||||
import {useAxios} from "~/vendor/axios";
|
||||
import {has} from "lodash";
|
||||
|
||||
export const nowPlayingProps = {
|
||||
nowPlayingUri: {
|
||||
|
@ -23,15 +24,25 @@ export const nowPlayingProps = {
|
|||
default() {
|
||||
return NowPlaying;
|
||||
}
|
||||
}
|
||||
},
|
||||
timeUri: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
};
|
||||
|
||||
export default function useNowPlaying(props) {
|
||||
const np = shallowRef(props.initialNowPlaying);
|
||||
|
||||
const currentTime = ref(Math.floor(Date.now() / 1000));
|
||||
const currentTrackDuration = ref(0);
|
||||
const currentTrackElapsed = ref(0);
|
||||
|
||||
const setNowPlaying = (np_new) => {
|
||||
np.value = np_new;
|
||||
|
||||
currentTrackDuration.value = np_new?.now_playing?.duration ?? 0;
|
||||
|
||||
// Update the browser metadata for browsers that support it (i.e. Mobile Chrome)
|
||||
if ('mediaSession' in navigator) {
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
|
@ -48,16 +59,21 @@ export default function useNowPlaying(props) {
|
|||
}));
|
||||
}
|
||||
|
||||
// Trigger initial NP set.
|
||||
setNowPlaying(np.value);
|
||||
|
||||
if (props.useSse) {
|
||||
const {data} = useEventSource(props.sseUri);
|
||||
watch(data, (sse_data_raw) => {
|
||||
const sse_data = JSON.parse(sse_data_raw);
|
||||
const sse_np = sse_data?.pub?.data?.np || null;
|
||||
watch(data, (data_raw) => {
|
||||
const json_data = JSON.parse(data_raw);
|
||||
const json_data_np = json_data?.pub?.data ?? {};
|
||||
|
||||
if (sse_np) {
|
||||
if (has(json_data_np, 'np')) {
|
||||
setTimeout(() => {
|
||||
setNowPlaying(sse_np);
|
||||
setNowPlaying(json_data_np.np);
|
||||
}, 3000);
|
||||
} else if (has(json_data_np, 'time')) {
|
||||
currentTime.value = json_data_np.time;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
@ -76,12 +92,51 @@ export default function useNowPlaying(props) {
|
|||
}).catch(() => {
|
||||
setTimeout(checkNowPlaying, (!document.hidden) ? 30000 : 120000);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const checkTime = () => {
|
||||
axios.get(props.timeUri, {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0',
|
||||
}
|
||||
}).then((response) => {
|
||||
currentTime.value = response.data.timestamp;
|
||||
}).finally(() => {
|
||||
setTimeout(checkTime, (!document.hidden) ? 300000 : 600000);
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(checkTime, 5000);
|
||||
setTimeout(checkNowPlaying, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
return np;
|
||||
onMounted(() => {
|
||||
useIntervalFn(
|
||||
() => {
|
||||
let currentTrackPlayedAt = np.value?.now_playing?.played_at ?? 0;
|
||||
let elapsed = currentTime.value - currentTrackPlayedAt;
|
||||
|
||||
if (elapsed < 0) {
|
||||
elapsed = 0;
|
||||
} else if (elapsed >= currentTrackDuration.value) {
|
||||
elapsed = currentTrackDuration.value;
|
||||
}
|
||||
|
||||
currentTrackElapsed.value = elapsed;
|
||||
currentTime.value = currentTime.value + 1;
|
||||
},
|
||||
1000
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
np,
|
||||
currentTime,
|
||||
currentTrackDuration,
|
||||
currentTrackElapsed
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,16 +4,16 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Controller\Frontend\PublicPages;
|
||||
|
||||
use App\Entity;
|
||||
use App\Exception\StationNotFoundException;
|
||||
use App\Http\Response;
|
||||
use App\Http\ServerRequest;
|
||||
use App\VueComponent\NowPlayingComponent;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
final class HistoryAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Entity\ApiGenerator\NowPlayingApiGenerator $npApiGenerator
|
||||
private readonly NowPlayingComponent $nowPlayingComponent
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -28,14 +28,6 @@ final class HistoryAction
|
|||
throw new StationNotFoundException();
|
||||
}
|
||||
|
||||
$np = $this->npApiGenerator->currentOrEmpty($station);
|
||||
$np->resolveUrls($request->getRouter()->getBaseUrl());
|
||||
|
||||
$customization = $request->getCustomization();
|
||||
$router = $request->getRouter();
|
||||
|
||||
$useStatic = $customization->useStaticNowPlaying();
|
||||
|
||||
return $request->getView()->renderVuePage(
|
||||
response: $response->withHeader('X-Frame-Options', '*'),
|
||||
component: 'Vue_PublicHistory',
|
||||
|
@ -46,13 +38,7 @@ final class HistoryAction
|
|||
'page_class' => 'embed station-' . $station->getShortName(),
|
||||
'hide_footer' => true,
|
||||
],
|
||||
props: [
|
||||
'initialNowPlaying' => $np,
|
||||
'showAlbumArt' => !$customization->hideAlbumArt(),
|
||||
'nowPlayingUri' => $useStatic
|
||||
? '/api/nowplaying_static/' . urlencode($station->getShortName()) . '.json'
|
||||
: $router->named('api:nowplaying:index', ['station_id' => $station->getShortName()]),
|
||||
],
|
||||
props: $this->nowPlayingComponent->getProps($request),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,15 +8,14 @@ use App\Entity;
|
|||
use App\Exception\StationNotFoundException;
|
||||
use App\Http\Response;
|
||||
use App\Http\ServerRequest;
|
||||
use App\Service\Centrifugo;
|
||||
use App\VueComponent\NowPlayingComponent;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
final class PlayerAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Entity\ApiGenerator\NowPlayingApiGenerator $npApiGenerator,
|
||||
private readonly Entity\Repository\CustomFieldRepository $customFieldRepo,
|
||||
private readonly Centrifugo $centrifugo
|
||||
private readonly NowPlayingComponent $nowPlayingComponent
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -36,32 +35,10 @@ final class PlayerAction
|
|||
throw new StationNotFoundException();
|
||||
}
|
||||
|
||||
$baseUrl = $request->getRouter()->getBaseUrl();
|
||||
|
||||
$np = $this->npApiGenerator->currentOrEmpty($station);
|
||||
$np->resolveUrls($baseUrl);
|
||||
|
||||
// Build Vue props.
|
||||
$customization = $request->getCustomization();
|
||||
$router = $request->getRouter();
|
||||
|
||||
$backendConfig = $station->getBackendConfig();
|
||||
|
||||
$props = [
|
||||
'initialNowPlaying' => $np,
|
||||
'showAlbumArt' => !$customization->hideAlbumArt(),
|
||||
'autoplay' => !empty($request->getQueryParam('autoplay')),
|
||||
'showHls' => $backendConfig->getHlsEnableOnPublicPlayer(),
|
||||
'hlsIsDefault' => $backendConfig->getHlsIsDefault(),
|
||||
'nowPlayingUri' => $customization->useStaticNowPlaying()
|
||||
? '/api/nowplaying_static/' . urlencode($station->getShortName()) . '.json'
|
||||
: $router->named('api:nowplaying:index', ['station_id' => $station->getShortName()]),
|
||||
];
|
||||
|
||||
if ($customization->useStaticNowPlaying() && $this->centrifugo->isSupported()) {
|
||||
$props['useSse'] = true;
|
||||
$props['sseUri'] = $this->centrifugo->getSseUrl($station);
|
||||
}
|
||||
$props = $this->nowPlayingComponent->getProps($request);
|
||||
|
||||
// Render embedded player.
|
||||
if (!empty($embed)) {
|
||||
|
|
|
@ -10,6 +10,9 @@ use GuzzleHttp\Client;
|
|||
|
||||
final class Centrifugo
|
||||
{
|
||||
public const GLOBAL_TIME_CHANNEL = 'global:time';
|
||||
|
||||
|
||||
public function __construct(
|
||||
private readonly Environment $environment,
|
||||
private readonly Client $client,
|
||||
|
@ -21,20 +24,38 @@ final class Centrifugo
|
|||
return $this->environment->isDocker();
|
||||
}
|
||||
|
||||
public function sendTime(): void
|
||||
{
|
||||
$this->send([
|
||||
'method' => 'publish',
|
||||
'params' => [
|
||||
'channel' => self::GLOBAL_TIME_CHANNEL,
|
||||
'data' => [
|
||||
'time' => time(),
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function publishToStation(Station $station, mixed $message): void
|
||||
{
|
||||
$this->send([
|
||||
'method' => 'publish',
|
||||
'params' => [
|
||||
'channel' => $this->getChannelName($station),
|
||||
'data' => [
|
||||
'np' => $message,
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function send(array $body): void
|
||||
{
|
||||
$this->client->post(
|
||||
'http://localhost:6025/api',
|
||||
[
|
||||
'json' => [
|
||||
'method' => 'publish',
|
||||
'params' => [
|
||||
'channel' => $this->getChannelName($station),
|
||||
'data' => [
|
||||
'np' => $message,
|
||||
],
|
||||
],
|
||||
],
|
||||
'json' => $body,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -47,6 +68,7 @@ final class Centrifugo
|
|||
[
|
||||
'subs' => [
|
||||
$this->getChannelName($station) => [],
|
||||
self::GLOBAL_TIME_CHANNEL => [],
|
||||
],
|
||||
],
|
||||
JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Sync\Task;
|
||||
|
||||
use App\Doctrine\ReloadableEntityManagerInterface;
|
||||
use App\Service\Centrifugo;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
final class SendTimeOnSocketTask extends AbstractTask
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Centrifugo $centrifugo,
|
||||
ReloadableEntityManagerInterface $em,
|
||||
LoggerInterface $logger,
|
||||
) {
|
||||
parent::__construct($em, $logger);
|
||||
}
|
||||
|
||||
public static function getSchedulePattern(): string
|
||||
{
|
||||
return '* * * * *';
|
||||
}
|
||||
|
||||
public function run(bool $force = false): void
|
||||
{
|
||||
if (!$this->centrifugo->isSupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->centrifugo->sendTime();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\VueComponent;
|
||||
|
||||
use App\Entity\ApiGenerator\NowPlayingApiGenerator;
|
||||
use App\Http\ServerRequest;
|
||||
use App\Service\Centrifugo;
|
||||
|
||||
final class NowPlayingComponent implements VueComponentInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly NowPlayingApiGenerator $npApiGenerator,
|
||||
private readonly Centrifugo $centrifugo
|
||||
) {
|
||||
}
|
||||
|
||||
public function getProps(ServerRequest $request): array
|
||||
{
|
||||
$station = $request->getStation();
|
||||
|
||||
$baseUrl = $request->getRouter()->getBaseUrl();
|
||||
|
||||
$np = $this->npApiGenerator->currentOrEmpty($station);
|
||||
$np->resolveUrls($baseUrl);
|
||||
|
||||
$customization = $request->getCustomization();
|
||||
$router = $request->getRouter();
|
||||
|
||||
$backendConfig = $station->getBackendConfig();
|
||||
|
||||
$props = [
|
||||
'initialNowPlaying' => $np,
|
||||
'showAlbumArt' => !$customization->hideAlbumArt(),
|
||||
'autoplay' => !empty($request->getQueryParam('autoplay')),
|
||||
'showHls' => $backendConfig->getHlsEnableOnPublicPlayer(),
|
||||
'hlsIsDefault' => $backendConfig->getHlsIsDefault(),
|
||||
'nowPlayingUri' => $customization->useStaticNowPlaying()
|
||||
? '/api/nowplaying_static/' . urlencode($station->getShortName()) . '.json'
|
||||
: $router->named('api:nowplaying:index', ['station_id' => $station->getShortName()]),
|
||||
'timeUri' => $router->named('api:index:time'),
|
||||
'useSse' => false,
|
||||
];
|
||||
|
||||
if ($customization->useStaticNowPlaying() && $this->centrifugo->isSupported()) {
|
||||
$props['useSse'] = true;
|
||||
$props['sseUri'] = $this->centrifugo->getSseUrl($station);
|
||||
}
|
||||
|
||||
return $props;
|
||||
}
|
||||
}
|
|
@ -20,6 +20,13 @@ namespaces:
|
|||
allow_history_for_client: true
|
||||
allow_history_for_anonymous: true
|
||||
|
||||
- name: "global"
|
||||
history_size: 0
|
||||
allow_subscribe_for_client: true
|
||||
allow_subscribe_for_anonymous: true
|
||||
allow_history_for_client: true
|
||||
allow_history_for_anonymous: true
|
||||
|
||||
{{if isTrue .Env.ENABLE_REDIS }}
|
||||
engine: "redis"
|
||||
redis_address: "{{ .Env.REDIS_HOST }}:{{ default .Env.REDIS_PORT "6379" }}"
|
||||
|
|
Loading…
Reference in New Issue