Compare commits

...

15 Commits

Author SHA1 Message Date
Charles-Henri BERNARD 69ead40727
Merge fd2a0896c0 into c3e540eae7 2024-04-18 18:21:51 -03:00
Buster Neece c3e540eae7
#7010 -- Make public page background centered. 2024-04-17 05:18:26 -05:00
Buster Neece 4cf9c626ad
Allow nginx to accelerate the newer `/var/azuracast/storage` folders (backups, binaries, etc) 2024-04-17 04:38:26 -05:00
Buster Neece 08c5468562
Fixes #7037 -- Fix CarbonImmutable error on stats overview page. 2024-04-17 01:06:39 -05:00
Buster Neece bee576b5e7
Revert "Add support for rich searches with FusePHP (no separate search index required)."
This reverts commit 28e5faa7e4.
2024-04-16 02:45:52 -05:00
Buster Neece f28c472568
Set a chunk limit to allow Roadrunner to stream responses to reduce memory consumption. 2024-04-15 06:04:43 -05:00
Buster Neece e2ba8d35b8
Set Vite permissions to more permissive to work around an issue in newer versions. 2024-04-15 05:43:07 -05:00
Buster Neece 70503d8e57
Update frontend dependencies. 2024-04-15 05:42:54 -05:00
Charles-Henri BERNARD fd2a0896c0
Merge branch 'main' into main 2024-02-02 15:28:55 +01:00
Charles-Henri BERNARD 7ba5e5a437
Update ConfigWriter.php 2024-01-31 11:32:15 +01:00
Charles-Henri BERNARD 9aef2cf2d2
Update StationPortCheckerValidator.php 2024-01-31 11:07:08 +01:00
Charles-Henri BERNARD e49db10137
Update Configuration.php 2024-01-31 11:06:04 +01:00
Charles-Henri BERNARD 99c288a072
Update StreamersForm.vue 2024-01-31 11:04:02 +01:00
Charles-Henri BERNARD 460c49b01d
Update StationBackendConfiguration.php 2024-01-31 10:58:45 +01:00
Charles-Henri BERNARD 61db6e5735
Update Liquidsoap.php 2024-01-31 10:57:44 +01:00
19 changed files with 516 additions and 1004 deletions

View File

@ -5,10 +5,6 @@ release channel, you can take advantage of these new features and fixes.
## New Features/Changes
- **Improved Media Search**: We've overhauled our media search functions to add support
for [extended search](https://www.fusejs.io/examples.html#extended-search) queries. Searches now also include values
stored in custom fields.
## Code Quality/Technical Changes
## Bug Fixes

View File

@ -51,7 +51,6 @@
"league/mime-type-detection": "^1.7",
"league/oauth2-client": "^2.6",
"league/plates": "^3.1",
"loilo/fuse": "^7.0",
"lstrojny/fxmlrpc": "dev-master",
"marcw/rss-writer": "^0.4.0",
"matomo/device-detector": "^6",

65
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1a41be64f51e4bc965bcf2f53e06bc11",
"content-hash": "5bca0c641ba21645d05ab830394898ae",
"packages": [
{
"name": "aws/aws-crt-php",
@ -3426,69 +3426,6 @@
},
"time": "2023-01-16T20:25:45+00:00"
},
{
"name": "loilo/fuse",
"version": "7.0.1",
"source": {
"type": "git",
"url": "https://github.com/loilo/Fuse.git",
"reference": "3304788d2f30d677faea700e0e466006f1d70015"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/loilo/Fuse/zipball/3304788d2f30d677faea700e0e466006f1d70015",
"reference": "3304788d2f30d677faea700e0e466006f1d70015",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0"
},
"require-dev": {
"dms/phpunit-arraysubset-asserts": "^0.3.0 || ^0.4.0 || ^0.5.0",
"phpunit/phpunit": "^8.0 || ^9.0",
"psalm/plugin-phpunit": "^0.16.1 || ^0.17.0 || ^0.18.0",
"squizlabs/php_codesniffer": "^3.6",
"vimeo/psalm": "^4.9 || ^5.0"
},
"type": "library",
"autoload": {
"files": [
"src/Core/computeScore.php",
"src/Core/config.php",
"src/Core/format.php",
"src/Core/parse.php",
"src/Helpers/get.php",
"src/Helpers/sort.php",
"src/Helpers/types.php",
"src/Search/Bitap/computeScore.php",
"src/Search/Bitap/convertMaskToIndices.php",
"src/Search/Bitap/createPatternAlphabet.php",
"src/Search/Bitap/search.php",
"src/Search/Extended/parseQuery.php",
"src/Transform/transformMatches.php",
"src/Transform/transformScore.php"
],
"psr-4": {
"Fuse\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Florian Reuschel",
"email": "florian@loilo.de"
}
],
"description": "Fuzzy search for PHP based on Bitap algorithm",
"support": {
"issues": "https://github.com/loilo/Fuse/issues",
"source": "https://github.com/loilo/Fuse/tree/v7.0.1"
},
"time": "2023-11-27T17:21:41+00:00"
},
{
"name": "lstrojny/fxmlrpc",
"version": "dev-master",

File diff suppressed because it is too large Load Diff

View File

@ -90,6 +90,27 @@
</template>
</form-group-field>
<form-group-field
v-if="enableAdvancedFeatures"
id="edit_form_backend_dj_port_secondary"
class="col-md-6"
:field="v$.backend_config.dj_port_secondary"
input-type="number"
:input-attrs="{ min: '0' }"
advanced
:label="$gettext('Customize DJ/Streamer Port Secondary')"
>
<template #description>
{{
$gettext('No other program can be using this port. Leave blank to automatically assign a port.')
}}
<br>
{{
$gettext('Note: the port after this one will automatically be used for legacy connections.')
}}
</template>
</form-group-field>
<form-group-field
id="edit_form_backend_dj_buffer"
class="col-md-6"

View File

@ -4,6 +4,7 @@ body.page-minimal {
background: var(--bs-body-bg) var(--public-page-bg);
background-size: cover;
background-attachment: fixed;
background-position: center center;
footer {
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black, 0 0 4px #000;

View File

@ -64,6 +64,7 @@ export default defineConfig({
},
server: {
strictPort: true,
host: true,
fs: {
allow: ['..']
}

View File

@ -19,7 +19,6 @@ use App\Http\RouterInterface;
use App\Http\ServerRequest;
use App\Media\MimeType;
use App\Paginator;
use App\Service\FuseSearch;
use App\Utilities\Strings;
use App\Utilities\Types;
use Doctrine\Common\Collections\Order;
@ -37,8 +36,7 @@ final class ListAction implements SingleActionInterface
public function __construct(
private readonly CacheInterface $cache,
private readonly StationFilesystems $stationFilesystems,
private readonly FuseSearch $fuseSearch
private readonly StationFilesystems $stationFilesystems
) {
}
@ -71,12 +69,7 @@ final class ListAction implements SingleActionInterface
];
if ($isSearch) {
if ($playlist instanceof StationPlaylist) {
$cacheKeyParts[] = 'search_playlist_' . $playlist->getIdRequired();
}
if (!empty($special)) {
$cacheKeyParts[] = 'search_special_' . rawurlencode($special);
}
$cacheKeyParts[] = 'search_' . rawurlencode($searchPhraseFull);
}
$cacheKey = implode('.', $cacheKeyParts);
@ -120,7 +113,7 @@ final class ListAction implements SingleActionInterface
)->setParameter('storageLocation', $storageLocation)
->setParameter('path', $pathLike);
// Apply special searches (string searches happen below).
// Apply searching
if ($isSearch) {
if ('unprocessable' === $special) {
$mediaQueryBuilder = null;
@ -154,6 +147,12 @@ final class ListAction implements SingleActionInterface
)->setParameter('playlist', $playlist);
}
if (!empty($searchPhrase)) {
$mediaQueryBuilder->andWhere(
'(sm.title LIKE :query OR sm.artist LIKE :query OR sm.path LIKE :query)'
)->setParameter('query', '%' . $searchPhrase . '%');
}
$unprocessableMediaRaw = [];
}
@ -266,41 +265,11 @@ final class ListAction implements SingleActionInterface
$this->cache->set($cacheKey, $result, 300);
}
// Apply searching
if (!empty($searchPhrase)) {
$result = $this->fuseSearch->search(
$searchPhrase,
$result,
[
'keys' => [
'text',
'path',
'album',
'genre',
'custom_fields',
],
'threshold' => 0.2,
'useExtendedSearch' => true,
'getFn' => fn(FileList $record, array $key) => match ($key[0] ?? null) {
'text' => $record->media->text,
'path' => $record->path,
'album' => $record->media->album,
'genre' => $record->media->genre,
'custom_fields' => implode(' ', $record->media->custom_fields),
default => null,
},
],
$cacheKey,
300,
$flushCache
);
}
$propertyAccessor = self::getPropertyAccessor();
// Apply sorting
[$sort, $sortOrder] = $this->getSortFromRequest($request);
$propertyAccessor = self::getPropertyAccessor();
usort(
$result,
static fn(FileList $a, FileList $b) => self::sortRows(

View File

@ -65,8 +65,7 @@ final class ChartsAction extends AbstractReportAction
$daysOfWeek = [];
foreach ($dailyStats as $stat) {
/** @var CarbonImmutable $statTime */
$statTime = $stat['moment'];
$statTime = CarbonImmutable::instance($stat['moment']);
$statTime = $statTime->shiftTimezone($stationTz);
$avgRow = new stdClass();
@ -175,8 +174,7 @@ final class ChartsAction extends AbstractReportAction
}
foreach ($hourlyStats as $stat) {
/** @var CarbonImmutable $statTime */
$statTime = $stat['moment'];
$statTime = CarbonImmutable::instance($stat['moment']);
$statTime = $statTime->shiftTimezone($stationTz);
$hour = $statTime->hour;

View File

@ -12,15 +12,6 @@ trait HasMediaSearch
{
use EntityManagerAwareTrait;
/**
* @param Station $station
* @param string $query
* @return array{
* string,
* StationPlaylist|null,
* string|null
* }
*/
private function parseSearchQuery(
Station $station,
string $query

View File

@ -39,6 +39,18 @@ class StationBackendConfiguration extends AbstractStationConfiguration
$this->set(self::DJ_PORT, $port);
}
public const DJ_PORT_SECONDARY = 'dj_port_decondary';
public function getDjPortSecondary(): ?int
{
return Types::intOrNull($this->get(self::DJ_PORT_SECONDARY));
}
public function setDjPortSecondary(?int $port): void
{
$this->set(self::DJ_PORT_SECONDARY, $port);
}
public const TELNET_PORT = 'telnet_port';
public function getTelnetPort(): ?int

View File

@ -29,8 +29,9 @@ final class CustomUrls
public static function getXAccelPath(string $path): ?string
{
$specialPaths = [
'/var/azuracast/backups' => '/internal/backups',
'/var/azuracast/stations' => '/internal/stations',
'/var/azuracast/storage' => '/internal/storage',
'/var/azuracast/backups' => '/internal/backups',
];
foreach ($specialPaths as $diskPath => $nginxPath) {

View File

@ -59,6 +59,27 @@ final class Liquidsoap extends AbstractLocalAdapter
return $frontendPort + 5;
}
/**
* Returns the port used for a second DJs/Streamers to connect to LiquidSoap for broadcasting.
*
* @param Station $station
*
* @return int The port number to use for this station.
*/
public function getStreamPortSecondary(Station $station): int
{
$djPortSecondary = $station->getBackendConfig()->getDjPortSecondary();
if (null !== $djPortSecondary) {
return $djPortSecondary;
}
// Default to frontend port + 7
$frontendConfig = $station->getFrontendConfig();
$frontendPort = $frontendConfig->getPort() ?? (8000 + (($station->getId() - 1) * 10));
return $frontendPort + 7;
}
/**
* Execute the specified remote command on LiquidSoap via the telnet API.
*

View File

@ -1030,7 +1030,41 @@ final class ConfigWriter implements EventSubscriberInterface
# Live Broadcasting
live = input.harbor({$harborParams})
LIQ
);
// Live Secondary Port
$streamPortSecondary = $this->liquidsoap->getStreamPortSecondary($station);
// Paramètres pour live Secondary
$harborSecondary_params = [
'"' . self::cleanUpString($dj_mount) . '"', // Assurez-vous que $dj_mount est défini correctement
'id = "input_streamer_live_secondary"', // ID unique pour live1
'port = ' . $streamPortSecondary, // Utilisation de getStreamPort1 pour le port
'auth = dj_auth', // Authentification, assurez-vous que 'dj_auth' est correctement défini
'icy = true', // Paramètres ICY
'icy_metadata_charset = "' . $charset . '"', // Charset pour les métadonnées ICY, assurez-vous que $charset est défini
'metadata_charset = "' . $charset . '"', // Charset pour les métadonnées, assurez-vous que $charset est défini
'on_connect = live_connected', // Action à la connexion
'on_disconnect = live_disconnected', // Action à la déconnexion
];
// Ajouter des paramètres de buffer si nécessaire
$djBuffer = $settings->getDjBuffer(); // Assurez-vous que $settings est défini et a une méthode getDjBuffer
if (0 !== $djBuffer) {
$harborSecondary_params[] = 'buffer = ' . self::toFloat($djBuffer);
$harborSecondary_params[] = 'max = ' . self::toFloat(max($djBuffer + 5, 10));
}
// Concaténer les paramètres pour former la chaîne de configuration live1
$harborSecondaryParams = implode(', ', $harborSecondary_params);
// Ajout de la configuration live1 au fichier de configuration LiquidSoap
// Assurez-vous que cette ligne est placée au bon endroit dans votre script pour écrire dans le fichier de configuration
$event->appendBlock("live1 = input.harbor({$harbor1Params})\n");
$event->appendBlock(
<<<LIQ
def insert_missing(m) =
if m == [] then
[("title", "{$liveBroadcastText}"), ("is_live", "true")]
@ -1039,8 +1073,9 @@ final class ConfigWriter implements EventSubscriberInterface
end
end
live = metadata.map(insert_missing, live)
live1 = metadata.map(insert_missing, live1)
radio = fallback(id="live_fallback", track_sensitive=false, replay_metadata=true, [live, radio])
radio = fallback(id="live_fallback", track_sensitive=false, replay_metadata=true, [live, live1, radio])
# Skip non-live track when live DJ goes live.
def check_live() =

View File

@ -289,6 +289,12 @@ final class Configuration
$station->setBackendConfig($backendConfig);
}
$djPortSecondary = $backendConfig->getDjPortSecondary();
if ($force || null === $djPortSecondary) {
$backendConfig->setDjPortSecondary($basePort + 7);
$station->setBackendConfig($backendConfig);
}
$telnetPort = $backendConfig->getTelnetPort();
if ($force || null === $telnetPort) {
$backendConfig->setTelnetPort($basePort + 4);
@ -374,6 +380,11 @@ final class Configuration
$usedPorts[$port] = $stationReference;
$usedPorts[$port + 1] = $stationReference;
}
if (!empty($backendConfig['dj_port_secondary'])) {
$port = (int)$backendConfig['dj_port_secondary'];
$usedPorts[$port] = $stationReference;
$usedPorts[$port + 1] = $stationReference;
}
if (!empty($backendConfig['telnet_port'])) {
$port = (int)$backendConfig['telnet_port'];
$usedPorts[$port] = $stationReference;

View File

@ -1,65 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Service;
use Fuse\Fuse;
use InvalidArgumentException;
use Psr\SimpleCache\CacheInterface;
final class FuseSearch
{
public const int DEFAULT_TTL = 60;
public function __construct(
private readonly CacheInterface $cache,
) {
}
public function search(
?string $searchPhrase,
array $records,
array $options,
string $cacheKey,
int $cacheTtl = self::DEFAULT_TTL,
bool $flushCache = false
): array {
$fuse = $this->buildSearch($records, $options, $cacheKey, $cacheTtl, $flushCache);
return array_column($fuse->search($searchPhrase), 'item');
}
public function buildSearch(
array $records,
array $options,
string $cacheKey,
int $cacheTtl = self::DEFAULT_TTL,
bool $flushCache = false
): Fuse {
$keys = $options['keys'] ?? null;
if (null === $keys) {
throw new InvalidArgumentException('No keys provided for search.');
}
$keysHash = substr(md5(json_encode($keys, JSON_THROW_ON_ERROR)), 0, 6);
$cacheKey .= '_fuse_' . $keysHash;
if (!$flushCache && $this->cache->has($cacheKey)) {
$indexData = $this->cache->get($cacheKey);
$index = Fuse::parseIndex(
$indexData,
$options
);
} else {
$index = Fuse::createIndex($keys, $records, $options);
$this->cache->set(
$cacheKey,
$index->jsonSerialize(),
$cacheTtl
);
}
return new Fuse($records, $options, $index);
}
}

View File

@ -32,6 +32,7 @@ final class StationPortCheckerValidator extends ConstraintValidator
$portsToCheck = [
'frontend_config_port' => $frontendConfig->getPort(),
'backend_config_dj_port' => $backendConfig->getDjPort(),
'backend_config_dj_port_secondary' => $backendConfig->getDjPortSecondary(),
'backend_config_telnet_port' => $backendConfig->getTelnetPort(),
];

View File

@ -149,17 +149,19 @@ server {
# Internal handlers used by the application to perform X-Accel-Redirect's for higher performance.
location /internal/backups/ {
internal;
add_header Access-Control-Allow-Origin "$upstream_http_access_control_allow_origin";
alias /var/azuracast/backups/;
}
location /internal/storage/ {
internal;
add_header Access-Control-Allow-Origin "$upstream_http_access_control_allow_origin";
alias /var/azuracast/storage/;
}
location /internal/stations/ {
internal;
add_header Access-Control-Allow-Origin "$upstream_http_access_control_allow_origin";
alias /var/azuracast/stations/;
}

View File

@ -16,8 +16,12 @@ $diBuilder = AppFactory::createContainerBuilder($environment);
$httpFactory = new App\Http\HttpFactory();
$worker = \Spiral\RoadRunner\Worker::create();
$psr7Worker = new Spiral\RoadRunner\Http\PSR7Worker($worker, $httpFactory, $httpFactory, $httpFactory);
// Allow for streaming larger responses back to Roadrunner.
$psr7Worker->chunkSize = 1024 * 25;
while (true) {
try {
$request = $psr7Worker->waitRequest();