Allow user uploaded intro files for mount points.
This commit is contained in:
parent
0a2fafa5ee
commit
7aefbb6d6e
|
@ -16,6 +16,8 @@ release channel, you can take advantage of these new features and fixes.
|
|||
- You can now embed the "Schedule" panel from the station's profile into your own web page as an embeddabl component.
|
||||
|
||||
- Mount point updates:
|
||||
- You can now upload an introduction file that will be played to listeners when they initially connect. This file
|
||||
must match the bitrate and format of the stream itself, and is thus uploaded on a per-mount-point basis.
|
||||
- You can now broadcast in Ogg FLAC format.
|
||||
- You can now specify a maximum connected time in seconds, after which listeners are automatically disconnected.
|
||||
|
||||
|
|
|
@ -477,6 +477,32 @@ return function (App $app) {
|
|||
->add(Middleware\Module\StationFiles::class)
|
||||
->add(new Middleware\Permissions(Acl::STATION_MEDIA, true));
|
||||
|
||||
$group->post(
|
||||
'/mounts/intro',
|
||||
Controller\Api\Stations\Mounts\Intro\PostIntroAction::class
|
||||
)->setName('api:stations:mounts:new-intro')
|
||||
->add(new Middleware\Permissions(Acl::STATION_MOUNTS, true));
|
||||
|
||||
$group->group(
|
||||
'/mount/{id}',
|
||||
function (RouteCollectorProxy $group) {
|
||||
$group->get(
|
||||
'/intro',
|
||||
Controller\Api\Stations\Mounts\Intro\GetIntroAction::class
|
||||
)->setName('api:stations:mounts:intro');
|
||||
|
||||
$group->post(
|
||||
'/intro',
|
||||
Controller\Api\Stations\Mounts\Intro\PostIntroAction::class
|
||||
);
|
||||
|
||||
$group->delete(
|
||||
'/intro',
|
||||
Controller\Api\Stations\Mounts\Intro\DeleteIntroAction::class
|
||||
);
|
||||
}
|
||||
)->add(new Middleware\Permissions(Acl::STATION_MOUNTS, true));
|
||||
|
||||
$group->get(
|
||||
'/playlists/schedule',
|
||||
Controller\Api\Stations\PlaylistsController::class . ':scheduleAction'
|
||||
|
|
|
@ -51,7 +51,8 @@
|
|||
</data-table>
|
||||
</b-card>
|
||||
|
||||
<edit-modal ref="editModal" :create-url="listUrl" :enable-advanced-features="enableAdvancedFeatures"
|
||||
<edit-modal ref="editModal" :create-url="listUrl" :new-intro-url="newIntroUrl"
|
||||
:enable-advanced-features="enableAdvancedFeatures"
|
||||
:station-frontend-type="stationFrontendType" @relist="relist"></edit-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -69,6 +70,7 @@ export default {
|
|||
components: { InfoCard, Icon, EditModal, DataTable },
|
||||
props: {
|
||||
listUrl: String,
|
||||
newIntroUrl: String,
|
||||
stationFrontendType: String,
|
||||
enableAdvancedFeatures: Boolean
|
||||
},
|
||||
|
|
|
@ -4,9 +4,15 @@
|
|||
<b-alert variant="danger" :show="error != null">{{ error }}</b-alert>
|
||||
<b-form class="form" @submit.prevent="doSubmit">
|
||||
<b-tabs content-class="mt-3">
|
||||
<mount-form-basic-info :form="$v.form" :station-frontend-type="stationFrontendType"></mount-form-basic-info>
|
||||
<mount-form-auto-dj :form="$v.form" :station-frontend-type="stationFrontendType"></mount-form-auto-dj>
|
||||
<mount-form-advanced v-if="enableAdvancedFeatures" :form="$v.form" :station-frontend-type="stationFrontendType"></mount-form-advanced>
|
||||
<mount-form-basic-info :form="$v.form"
|
||||
:station-frontend-type="stationFrontendType"></mount-form-basic-info>
|
||||
<mount-form-auto-dj :form="$v.form"
|
||||
:station-frontend-type="stationFrontendType"></mount-form-auto-dj>
|
||||
<mount-form-intro v-model="$v.form.intro_file.$model" :record-has-intro="record.intro_path !== null"
|
||||
:new-intro-url="newIntroUrl"
|
||||
:edit-intro-url="record.links.intro"></mount-form-intro>
|
||||
<mount-form-advanced v-if="enableAdvancedFeatures" :form="$v.form"
|
||||
:station-frontend-type="stationFrontendType"></mount-form-advanced>
|
||||
</b-tabs>
|
||||
|
||||
<invisible-submit-button/>
|
||||
|
@ -27,23 +33,35 @@ import required from 'vuelidate/src/validators/required';
|
|||
import InvisibleSubmitButton from '../../Common/InvisibleSubmitButton';
|
||||
import BaseEditModal from '../../Common/BaseEditModal';
|
||||
|
||||
import { FRONTEND_ICECAST, FRONTEND_SHOUTCAST } from '../../Entity/RadioAdapters';
|
||||
import {FRONTEND_ICECAST, FRONTEND_SHOUTCAST} from '../../Entity/RadioAdapters';
|
||||
import MountFormBasicInfo from './Form/BasicInfo';
|
||||
import MountFormAutoDj from './Form/AutoDj';
|
||||
import MountFormAdvanced from './Form/Advanced';
|
||||
import MountFormIntro from "./Form/Intro";
|
||||
|
||||
export default {
|
||||
name: 'EditModal',
|
||||
mixins: [BaseEditModal],
|
||||
components: { MountFormAdvanced, MountFormAutoDj, MountFormBasicInfo, InvisibleSubmitButton },
|
||||
components: {MountFormIntro, MountFormAdvanced, MountFormAutoDj, MountFormBasicInfo, InvisibleSubmitButton},
|
||||
props: {
|
||||
stationFrontendType: String,
|
||||
newIntroUrl: String,
|
||||
enableAdvancedFeatures: Boolean
|
||||
},
|
||||
validations () {
|
||||
data() {
|
||||
return {
|
||||
record: {
|
||||
intro_path: null,
|
||||
links: {
|
||||
intro: null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
validations() {
|
||||
let validations = {
|
||||
form: {
|
||||
name: { required },
|
||||
name: {required},
|
||||
display_name: {},
|
||||
is_visible_on_public_pages: {},
|
||||
is_default: {},
|
||||
|
@ -53,7 +71,8 @@ export default {
|
|||
autodj_format: {},
|
||||
autodj_bitrate: {},
|
||||
custom_listen_url: {},
|
||||
max_listener_duration: { required }
|
||||
max_listener_duration: {required},
|
||||
intro_file: {}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -76,6 +95,12 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
resetForm () {
|
||||
this.record = {
|
||||
intro_path: null,
|
||||
links: {
|
||||
intro: null
|
||||
}
|
||||
};
|
||||
this.form = {
|
||||
name: null,
|
||||
display_name: null,
|
||||
|
@ -90,10 +115,12 @@ export default {
|
|||
authhash: null,
|
||||
fallback_mount: '/error.mp3',
|
||||
max_listener_duration: 0,
|
||||
frontend_config: null
|
||||
frontend_config: null,
|
||||
intro_file: null
|
||||
};
|
||||
},
|
||||
populateForm (d) {
|
||||
this.record = d;
|
||||
this.form = {
|
||||
'name': d.name,
|
||||
'display_name': d.display_name,
|
||||
|
@ -108,7 +135,8 @@ export default {
|
|||
'authhash': d.authhash,
|
||||
'fallback_mount': d.fallback_mount,
|
||||
'max_listener_duration': d.max_listener_duration,
|
||||
'frontend_config': d.frontend_config
|
||||
'frontend_config': d.frontend_config,
|
||||
'intro_file': null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
<template>
|
||||
<b-tab :title="langTitle">
|
||||
<b-form-group>
|
||||
<b-row>
|
||||
<b-form-group class="col-md-6" label-for="intro_file">
|
||||
<template #label>
|
||||
<translate key="intro_file">Select Intro File</translate>
|
||||
</template>
|
||||
<template #description>
|
||||
<translate key="intro_file_desc">This introduction file should exactly match the bitrate and format of the mount point itself.</translate>
|
||||
</template>
|
||||
|
||||
<flow-upload :target-url="targetUrl" :valid-mime-types="acceptMimeTypes"
|
||||
@success="onFileSuccess"></flow-upload>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group class="col-md-6">
|
||||
<template #label>
|
||||
<translate key="existing_intro">Current Intro File</translate>
|
||||
</template>
|
||||
|
||||
<div v-if="hasIntro">
|
||||
<div class="buttons pt-3">
|
||||
<b-button v-if="editIntroUrl" block variant="bg" :href="editIntroUrl" target="_blank">
|
||||
<translate key="btn_download">Download</translate>
|
||||
</b-button>
|
||||
<b-button block variant="danger" @click="deleteIntro">
|
||||
<translate key="btn_delete_intro">Clear File</translate>
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<translate key="no_existing_intro">There is no existing intro file associated with this mount point.</translate>
|
||||
</div>
|
||||
</b-form-group>
|
||||
</b-row>
|
||||
</b-form-group>
|
||||
</b-tab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import handleAxiosError from '../../../Function/handleAxiosError';
|
||||
import FlowUpload from '../../../Common/FlowUpload';
|
||||
|
||||
export default {
|
||||
name: 'MountFormIntro',
|
||||
components: {FlowUpload},
|
||||
props: {
|
||||
value: Object,
|
||||
recordHasIntro: Boolean,
|
||||
editIntroUrl: String,
|
||||
newIntroUrl: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasIntro: this.recordHasIntro,
|
||||
acceptMimeTypes: ['audio/*']
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
recordHasIntro(newValue) {
|
||||
this.hasIntro = newValue;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
langTitle() {
|
||||
return this.$gettext('Intro');
|
||||
},
|
||||
targetUrl() {
|
||||
return (this.editIntroUrl)
|
||||
? this.editIntroUrl
|
||||
: this.newIntroUrl;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onFileSuccess(file, message) {
|
||||
this.hasIntro = true;
|
||||
if (!this.editIntroUrl) {
|
||||
this.$emit('input', message);
|
||||
}
|
||||
},
|
||||
deleteIntro() {
|
||||
if (this.editIntroUrl) {
|
||||
axios.delete(this.editIntroUrl).then((resp) => {
|
||||
this.hasIntro = false;
|
||||
}).catch((err) => {
|
||||
handleAxiosError(err);
|
||||
});
|
||||
} else {
|
||||
this.hasIntro = false;
|
||||
this.$emit('input', null);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -52,17 +52,22 @@ export default {
|
|||
editMediaUrl: String,
|
||||
newMediaUrl: String
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
hasMedia: this.recordHasMedia,
|
||||
acceptMimeTypes: ['audio/x-m4a', 'audio/mpeg']
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
recordHasMedia(newValue) {
|
||||
this.hasMedia = newValue;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
langTitle () {
|
||||
langTitle() {
|
||||
return this.$gettext('Media');
|
||||
},
|
||||
targetUrl () {
|
||||
targetUrl() {
|
||||
return (this.editMediaUrl)
|
||||
? this.editMediaUrl
|
||||
: this.newMediaUrl;
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Api\Stations\Mounts\Intro;
|
||||
|
||||
use App\Entity;
|
||||
use App\Http\Response;
|
||||
use App\Http\ServerRequest;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class DeleteIntroAction
|
||||
{
|
||||
public function __invoke(
|
||||
ServerRequest $request,
|
||||
Response $response,
|
||||
Entity\Repository\StationMountRepository $mountRepo,
|
||||
int $id
|
||||
): ResponseInterface {
|
||||
$station = $request->getStation();
|
||||
$mount = $mountRepo->find($station, $id);
|
||||
|
||||
if (null === $mount) {
|
||||
return $response->withStatus(404)
|
||||
->withJson(Entity\Api\Error::notFound());
|
||||
}
|
||||
|
||||
$mountRepo->clearIntro($mount);
|
||||
|
||||
return $response->withJson(new Entity\Api\Status());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Api\Stations\Mounts\Intro;
|
||||
|
||||
use App\Entity;
|
||||
use App\Flysystem\StationFilesystems;
|
||||
use App\Http\Response;
|
||||
use App\Http\ServerRequest;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class GetIntroAction
|
||||
{
|
||||
public function __invoke(
|
||||
ServerRequest $request,
|
||||
Response $response,
|
||||
Entity\Repository\StationMountRepository $mountRepo,
|
||||
int $id
|
||||
): ResponseInterface {
|
||||
set_time_limit(600);
|
||||
|
||||
$station = $request->getStation();
|
||||
$mount = $mountRepo->find($station, $id);
|
||||
|
||||
if ($mount instanceof Entity\StationMount) {
|
||||
$introPath = $mount->getIntroPath();
|
||||
|
||||
if (!empty($introPath)) {
|
||||
$fsConfig = (new StationFilesystems($station))->getConfigFilesystem();
|
||||
|
||||
if ($fsConfig->fileExists($introPath)) {
|
||||
return $response->streamFilesystemFile(
|
||||
$fsConfig,
|
||||
$introPath,
|
||||
basename($introPath)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $response->withStatus(404)
|
||||
->withJson(Entity\Api\Error::notFound());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Api\Stations\Mounts\Intro;
|
||||
|
||||
use App\Entity;
|
||||
use App\Http\Response;
|
||||
use App\Http\ServerRequest;
|
||||
use App\Service\Flow;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class PostIntroAction
|
||||
{
|
||||
public function __invoke(
|
||||
ServerRequest $request,
|
||||
Response $response,
|
||||
Entity\Repository\StationMountRepository $mountRepo,
|
||||
?int $id = null
|
||||
): ResponseInterface {
|
||||
$station = $request->getStation();
|
||||
|
||||
$flowResponse = Flow::process($request, $response, $station->getRadioTempDir());
|
||||
if ($flowResponse instanceof ResponseInterface) {
|
||||
return $flowResponse;
|
||||
}
|
||||
|
||||
if (null !== $id) {
|
||||
$mount = $mountRepo->find($station, $id);
|
||||
if (null === $mount) {
|
||||
return $response->withStatus(404)
|
||||
->withJson(Entity\Api\Error::notFound());
|
||||
}
|
||||
|
||||
$mountRepo->setIntro($mount, $flowResponse);
|
||||
|
||||
return $response->withJson(new Entity\Api\Status());
|
||||
}
|
||||
|
||||
return $response->withJson($flowResponse);
|
||||
}
|
||||
}
|
|
@ -6,9 +6,15 @@ namespace App\Controller\Api\Stations;
|
|||
|
||||
use App\Entity;
|
||||
use App\Exception\StationUnsupportedException;
|
||||
use App\Http\Response;
|
||||
use App\Http\Router;
|
||||
use App\Http\ServerRequest;
|
||||
use App\Service\Flow\UploadedFile;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use OpenApi\Annotations as OA;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
/**
|
||||
* @extends AbstractStationApiCrudController<Entity\StationMount>
|
||||
|
@ -18,6 +24,15 @@ class MountsController extends AbstractStationApiCrudController
|
|||
protected string $entityClass = Entity\StationMount::class;
|
||||
protected string $resourceRouteName = 'api:stations:mount';
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $em,
|
||||
Serializer $serializer,
|
||||
ValidatorInterface $validator,
|
||||
protected Entity\Repository\StationMountRepository $mountRepo
|
||||
) {
|
||||
parent::__construct($em, $serializer, $validator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(path="/station/{station_id}/mounts",
|
||||
* tags={"Stations: Mount Points"},
|
||||
|
@ -104,12 +119,19 @@ class MountsController extends AbstractStationApiCrudController
|
|||
|
||||
protected function viewRecord(object $record, ServerRequest $request): mixed
|
||||
{
|
||||
/** @var Entity\StationMount $record */
|
||||
$return = parent::viewRecord($record, $request);
|
||||
|
||||
$station = $request->getStation();
|
||||
$frontend = $request->getStationFrontend();
|
||||
$router = $request->getRouter();
|
||||
|
||||
$return['links']['intro'] = (string)$router->fromHere(
|
||||
route_name: 'api:stations:mounts:intro',
|
||||
route_params: ['id' => $record->getId()],
|
||||
absolute: true
|
||||
);
|
||||
|
||||
$return['links']['listen'] = (string)Router::resolveUri(
|
||||
$router->getBaseUrl(),
|
||||
$frontend->getUrlForMount($station, $record),
|
||||
|
@ -119,6 +141,44 @@ class MountsController extends AbstractStationApiCrudController
|
|||
return $return;
|
||||
}
|
||||
|
||||
public function createAction(
|
||||
ServerRequest $request,
|
||||
Response $response
|
||||
): ResponseInterface {
|
||||
$station = $request->getStation();
|
||||
|
||||
$parsedBody = (array)$request->getParsedBody();
|
||||
$record = $this->editRecord(
|
||||
$parsedBody,
|
||||
new Entity\StationMount($station)
|
||||
);
|
||||
|
||||
if (!empty($parsedBody['intro_file'])) {
|
||||
$intro = UploadedFile::fromArray($parsedBody['intro_file'], $station->getRadioTempDir());
|
||||
$this->mountRepo->setIntro($record, $intro);
|
||||
}
|
||||
|
||||
return $response->withJson($this->viewRecord($record, $request));
|
||||
}
|
||||
|
||||
public function deleteAction(
|
||||
ServerRequest $request,
|
||||
Response $response,
|
||||
int $station_id,
|
||||
int $id
|
||||
): ResponseInterface {
|
||||
$record = $this->getRecord($this->getStation($request), $id);
|
||||
|
||||
if (null === $record) {
|
||||
return $response->withStatus(404)
|
||||
->withJson(Entity\Api\Error::notFound());
|
||||
}
|
||||
|
||||
$this->mountRepo->destroy($record);
|
||||
|
||||
return $response->withJson(new Entity\Api\Status(true, __('Record deleted successfully.')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\Migration;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20210801020848 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add intro file support to station mounts.';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE station_mounts ADD intro_path VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE station_mounts DROP intro_path');
|
||||
}
|
||||
}
|
|
@ -6,9 +6,82 @@ namespace App\Entity\Repository;
|
|||
|
||||
use App\Doctrine\Repository;
|
||||
use App\Entity;
|
||||
use App\Flysystem\StationFilesystems;
|
||||
use App\Service\Flow\UploadedFile;
|
||||
use Azura\Files\ExtendedFilesystemInterface;
|
||||
|
||||
/**
|
||||
* @extends Repository<Entity\StationMount>
|
||||
*/
|
||||
class StationMountRepository extends Repository
|
||||
{
|
||||
public function find(Entity\Station $station, int $id): ?Entity\StationMount
|
||||
{
|
||||
return $this->repository->findOneBy(
|
||||
[
|
||||
'station' => $station,
|
||||
'id' => $id,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function setIntro(
|
||||
Entity\StationMount $mount,
|
||||
UploadedFile $file,
|
||||
?ExtendedFilesystemInterface $fs = null
|
||||
): void {
|
||||
$fs ??= (new StationFilesystems($mount->getStation()))->getConfigFilesystem();
|
||||
|
||||
if (!empty($mount->getIntroPath())) {
|
||||
$this->doDeleteIntro($mount, $fs);
|
||||
$mount->setIntroPath(null);
|
||||
}
|
||||
|
||||
$originalPath = $file->getOriginalFilename();
|
||||
$originalExt = pathinfo($originalPath, PATHINFO_EXTENSION);
|
||||
|
||||
$introPath = 'mount_' . $mount->getIdRequired() . '_intro.' . $originalExt;
|
||||
$fs->uploadAndDeleteOriginal($file->getUploadedPath(), $introPath);
|
||||
|
||||
$mount->setIntroPath($introPath);
|
||||
$this->em->persist($mount);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
protected function doDeleteIntro(
|
||||
Entity\StationMount $mount,
|
||||
?ExtendedFilesystemInterface $fs = null
|
||||
): void {
|
||||
$fs ??= (new StationFilesystems($mount->getStation()))->getConfigFilesystem();
|
||||
|
||||
$introPath = $mount->getIntroPath();
|
||||
if (empty($introPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fs->delete($introPath);
|
||||
}
|
||||
|
||||
public function clearIntro(
|
||||
Entity\StationMount $mount,
|
||||
?ExtendedFilesystemInterface $fs = null
|
||||
): void {
|
||||
$this->doDeleteIntro($mount, $fs);
|
||||
|
||||
$mount->setIntroPath(null);
|
||||
$this->em->persist($mount);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
public function destroy(
|
||||
Entity\StationMount $mount
|
||||
): void {
|
||||
$this->doDeleteIntro($mount);
|
||||
|
||||
$this->em->remove($mount);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entity\Station $station
|
||||
*
|
||||
|
|
|
@ -88,6 +88,9 @@ class StationMount implements
|
|||
#[ORM\Column(length: 255, nullable: true)]
|
||||
protected ?string $custom_listen_url = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
protected ?string $intro_path = null;
|
||||
|
||||
/** @OA\Property(type="array", @OA\Items()) */
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
protected ?string $frontend_config = null;
|
||||
|
@ -296,6 +299,16 @@ class StationMount implements
|
|||
$this->listeners_total = $listeners_total;
|
||||
}
|
||||
|
||||
public function getIntroPath(): ?string
|
||||
{
|
||||
return $this->intro_path;
|
||||
}
|
||||
|
||||
public function setIntroPath(?string $intro_path): void
|
||||
{
|
||||
$this->intro_path = $intro_path;
|
||||
}
|
||||
|
||||
public function getAutodjHost(): ?string
|
||||
{
|
||||
return '127.0.0.1';
|
||||
|
|
|
@ -188,6 +188,12 @@ class Icecast extends AbstractFrontend
|
|||
$mount['hidden'] = 1;
|
||||
}
|
||||
|
||||
if (!empty($mount_row->getIntroPath())) {
|
||||
$introPath = $mount_row->getIntroPath();
|
||||
// The intro path is appended to webroot, hence the 5 ../es. Amazingly, this works!
|
||||
$mount['intro'] = '../../../../../' . $station->getRadioConfigDir() . '/' . $introPath;
|
||||
}
|
||||
|
||||
if (!empty($mount_row->getFallbackMount())) {
|
||||
$mount['fallback-mount'] = $mount_row->getFallbackMount();
|
||||
$mount['fallback-override'] = 1;
|
||||
|
|
|
@ -147,6 +147,11 @@ class SHOUTcast extends AbstractFrontend
|
|||
$config['streamid_' . $i] = $i;
|
||||
$config['streampath_' . $i] = $mount_row->getName();
|
||||
|
||||
if (!empty($mount_row->getIntroPath())) {
|
||||
$introPath = $mount_row->getIntroPath();
|
||||
$config['streamintrofile_' . $i] = $station->getRadioConfigDir() . '/' . $introPath;
|
||||
}
|
||||
|
||||
if ($mount_row->getRelayUrl()) {
|
||||
$config['streamrelayurl_' . $i] = $mount_row->getRelayUrl();
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ $this->layout(
|
|||
/** @var App\Http\RouterInterface $router */
|
||||
$props = [
|
||||
'listUrl' => (string)$router->fromHere('api:stations:mounts'),
|
||||
'newIntroUrl' => (string)$router->fromHere('api:stations:mounts:new-intro'),
|
||||
'stationFrontendType' => $station->getFrontendType(),
|
||||
'enableAdvancedFeatures' => $enableAdvancedFeatures,
|
||||
];
|
||||
|
|
Loading…
Reference in New Issue