Simplify routing file and add Playlist API endpoints.
This commit is contained in:
parent
9f263a297e
commit
8dc072d1c8
|
@ -200,42 +200,6 @@ return function(App $app)
|
|||
$this->group('/admin', function() {
|
||||
/** @var App $this */
|
||||
|
||||
$this->group('', function() {
|
||||
/** @var App $this */
|
||||
$this->get('/custom_fields', Controller\Api\Admin\CustomFieldsController::class.':listAction')
|
||||
->setName('api:admin:custom_fields');
|
||||
$this->post('/custom_fields', Controller\Api\Admin\CustomFieldsController::class.':createAction');
|
||||
|
||||
$this->get('/custom_field/{id}', Controller\Api\Admin\CustomFieldsController::class.':getAction')
|
||||
->setName('api:admin:custom_field');
|
||||
$this->put('/custom_field/{id}', Controller\Api\Admin\CustomFieldsController::class.':editAction');
|
||||
$this->delete('/custom_field/{id}', Controller\Api\Admin\CustomFieldsController::class.':deleteAction');
|
||||
})->add([Middleware\Permissions::class, Acl::GLOBAL_CUSTOM_FIELDS]);
|
||||
|
||||
$this->group('', function() {
|
||||
/** @var App $this */
|
||||
$this->get('/users', Controller\Api\Admin\UsersController::class.':listAction')
|
||||
->setName('api:admin:users');
|
||||
$this->post('/users', Controller\Api\Admin\UsersController::class.':createAction');
|
||||
|
||||
$this->get('/user/{id}', Controller\Api\Admin\UsersController::class.':getAction')
|
||||
->setName('api:admin:user');
|
||||
$this->put('/user/{id}', Controller\Api\Admin\UsersController::class.':editAction');
|
||||
$this->delete('/user/{id}', Controller\Api\Admin\UsersController::class.':deleteAction');
|
||||
})->add([Middleware\Permissions::class, Acl::GLOBAL_USERS]);
|
||||
|
||||
$this->group('', function() {
|
||||
/** @var App $this */
|
||||
$this->get('/roles', Controller\Api\Admin\RolesController::class.':listAction')
|
||||
->setName('api:admin:roles');
|
||||
$this->post('/roles', Controller\Api\Admin\RolesController::class.':createAction');
|
||||
|
||||
$this->get('/role/{id}', Controller\Api\Admin\RolesController::class.':getAction')
|
||||
->setName('api:admin:role');
|
||||
$this->put('/role/{id}', Controller\Api\Admin\RolesController::class.':editAction');
|
||||
$this->delete('/role/{id}', Controller\Api\Admin\RolesController::class.':deleteAction');
|
||||
})->add([Middleware\Permissions::class, Acl::GLOBAL_PERMISSIONS]);
|
||||
|
||||
$this->get('/permissions', Controller\Api\Admin\PermissionsController::class)
|
||||
->add([Middleware\Permissions::class, Acl::GLOBAL_PERMISSIONS]);
|
||||
|
||||
|
@ -247,18 +211,26 @@ return function(App $app)
|
|||
$this->put('/settings', Controller\Api\Admin\SettingsController::class.':updateAction');
|
||||
})->add([Middleware\Permissions::class, Acl::GLOBAL_SETTINGS]);
|
||||
|
||||
$this->group('', function() {
|
||||
/** @var App $this */
|
||||
$this->get('/stations', Controller\Api\Admin\StationsController::class.':listAction')
|
||||
->setName('api:admin:stations');
|
||||
$this->post('/stations', Controller\Api\Admin\StationsController::class.':createAction');
|
||||
$admin_api_endpoints = [
|
||||
['custom_field', 'custom_fields', Controller\Api\Admin\CustomFieldsController::class, Acl::GLOBAL_CUSTOM_FIELDS],
|
||||
['role', 'roles', Controller\Api\Admin\RolesController::class, Acl::GLOBAL_PERMISSIONS],
|
||||
['station', 'stations', Controller\Api\Admin\StationsController::class, Acl::GLOBAL_STATIONS],
|
||||
['user', 'users', Controller\Api\Admin\UsersController::class, Acl::GLOBAL_USERS],
|
||||
];
|
||||
|
||||
$this->get('/station/{id}', Controller\Api\Admin\StationsController::class.':getAction')
|
||||
->setName('api:admin:station');
|
||||
$this->put('/station/{id}', Controller\Api\Admin\StationsController::class.':editAction');
|
||||
$this->delete('/station/{id}', Controller\Api\Admin\StationsController::class.':deleteAction');
|
||||
})->add([Middleware\Permissions::class, Acl::GLOBAL_STATIONS]);
|
||||
foreach($admin_api_endpoints as [$singular, $plural, $class, $permission]) {
|
||||
$this->group('', function() use ($singular, $plural, $class) {
|
||||
/** @var App $this */
|
||||
$this->get('/'.$plural, $class.':listAction')
|
||||
->setName('api:admin:'.$plural);
|
||||
$this->post('/'.$plural, $class.':createAction');
|
||||
|
||||
$this->get('/'.$singular.'/{id}', $class.':getAction')
|
||||
->setName('api:admin:'.$singular);
|
||||
$this->put('/'.$singular.'/{id}', $class.':editAction');
|
||||
$this->delete('/'.$singular.'/{id}', $class.':deleteAction');
|
||||
})->add([Middleware\Permissions::class, $permission]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->group('/station/{station}', function () {
|
||||
|
@ -301,41 +273,26 @@ return function(App $app)
|
|||
|
||||
$this->get('/art/{media_id:[a-zA-Z0-9]+}', Controller\Api\Stations\MediaController::class.':artAction');
|
||||
|
||||
$this->group('', function() {
|
||||
/** @var App $this */
|
||||
$this->get('/mounts', Controller\Api\Stations\MountsController::class.':listAction')
|
||||
->setName('api:stations:mounts');
|
||||
$this->post('/mounts', Controller\Api\Stations\MountsController::class.':createAction');
|
||||
$station_api_endpoints = [
|
||||
['mount', 'mounts', Controller\Api\Stations\MountsController::class, Acl::STATION_MOUNTS],
|
||||
['playlist', 'playlists', Controller\Api\Stations\PlaylistsController::class, Acl::STATION_MEDIA],
|
||||
['remote', 'remotes', Controller\Api\Stations\RemotesController::class, Acl::STATION_REMOTES],
|
||||
['streamer', 'streamers', Controller\Api\Stations\StreamersController::class, Acl::STATION_STREAMERS],
|
||||
];
|
||||
|
||||
$this->get('/mount/{id}', Controller\Api\Stations\MountsController::class.':getAction')
|
||||
->setName('api:stations:mount');
|
||||
$this->put('/mount/{id}', Controller\Api\Stations\MountsController::class.':editAction');
|
||||
$this->delete('/mount/{id}', Controller\Api\Stations\MountsController::class.':deleteAction');
|
||||
})->add([Middleware\Permissions::class, Acl::STATION_MOUNTS, true]);
|
||||
foreach($station_api_endpoints as [$singular, $plural, $class, $permission]) {
|
||||
$this->group('', function() use ($singular, $plural, $class) {
|
||||
/** @var App $this */
|
||||
$this->get('/'.$plural, $class.':listAction')
|
||||
->setName('api:stations:'.$plural);
|
||||
$this->post('/'.$plural, $class.':createAction');
|
||||
|
||||
$this->group('', function() {
|
||||
/** @var App $this */
|
||||
$this->get('/remotes', Controller\Api\Stations\RemotesController::class.':listAction')
|
||||
->setName('api:stations:remotes');
|
||||
$this->post('/remotes', Controller\Api\Stations\RemotesController::class.':createAction');
|
||||
|
||||
$this->get('/remote/{id}', Controller\Api\Stations\RemotesController::class.':getAction')
|
||||
->setName('api:stations:remote');
|
||||
$this->put('/remote/{id}', Controller\Api\Stations\RemotesController::class.':editAction');
|
||||
$this->delete('/remote/{id}', Controller\Api\Stations\RemotesController::class.':deleteAction');
|
||||
})->add([Middleware\Permissions::class, Acl::STATION_REMOTES, true]);
|
||||
|
||||
$this->group('', function() {
|
||||
/** @var App $this */
|
||||
$this->get('/streamers', Controller\Api\Stations\StreamersController::class.':listAction')
|
||||
->setName('api:stations:streamers');
|
||||
$this->post('/streamers', Controller\Api\Stations\StreamersController::class.':createAction');
|
||||
|
||||
$this->get('/streamer/{id}', Controller\Api\Stations\StreamersController::class.':getAction')
|
||||
->setName('api:stations:streamer');
|
||||
$this->put('/streamer/{id}', Controller\Api\Stations\StreamersController::class.':editAction');
|
||||
$this->delete('/streamer/{id}', Controller\Api\Stations\StreamersController::class.':deleteAction');
|
||||
})->add([Middleware\Permissions::class, Acl::STATION_STREAMERS, true]);
|
||||
$this->get('/'.$singular.'/{id}', $class.':getAction')
|
||||
->setName('api:stations:'.$singular);
|
||||
$this->put('/'.$singular.'/{id}', $class.':editAction');
|
||||
$this->delete('/'.$singular.'/{id}', $class.':deleteAction');
|
||||
})->add([Middleware\Permissions::class, $permission, true]);
|
||||
}
|
||||
|
||||
$this->get('/status', Controller\Api\Stations\ServicesController::class.':statusAction')
|
||||
->setName('api:stations:status')
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
namespace App\Controller\Api\Stations;
|
||||
|
||||
use App\Entity;
|
||||
use App\Http\Request;
|
||||
use OpenApi\Annotations as OA;
|
||||
|
||||
/**
|
||||
* @see \App\Provider\ApiProvider
|
||||
*/
|
||||
class PlaylistsController extends AbstractStationCrudController
|
||||
{
|
||||
protected $entityClass = Entity\StationPlaylist::class;
|
||||
protected $resourceRouteName = 'api:stations:playlist';
|
||||
|
||||
/**
|
||||
* @OA\Get(path="/station/{station_id}/playlists",
|
||||
* tags={"Stations: Playlists"},
|
||||
* description="List all current playlists.",
|
||||
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
|
||||
* @OA\Response(response=200, description="Success",
|
||||
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/StationPlaylist"))
|
||||
* ),
|
||||
* @OA\Response(response=403, description="Access denied"),
|
||||
* security={{"api_key": {}}},
|
||||
* )
|
||||
*
|
||||
* @OA\Post(path="/station/{station_id}/playlists",
|
||||
* tags={"Stations: Playlists"},
|
||||
* description="Create a new playlist.",
|
||||
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
|
||||
* @OA\RequestBody(
|
||||
* @OA\JsonContent(ref="#/components/schemas/StationPlaylist")
|
||||
* ),
|
||||
* @OA\Response(response=200, description="Success",
|
||||
* @OA\JsonContent(ref="#/components/schemas/StationPlaylist")
|
||||
* ),
|
||||
* @OA\Response(response=403, description="Access denied"),
|
||||
* security={{"api_key": {}}},
|
||||
* )
|
||||
*
|
||||
* @OA\Get(path="/station/{station_id}/playlist/{id}",
|
||||
* tags={"Stations: Playlists"},
|
||||
* description="Retrieve details for a single playlist.",
|
||||
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="Playlist ID",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", format="int64")
|
||||
* ),
|
||||
* @OA\Response(response=200, description="Success",
|
||||
* @OA\JsonContent(ref="#/components/schemas/StationPlaylist")
|
||||
* ),
|
||||
* @OA\Response(response=403, description="Access denied"),
|
||||
* security={{"api_key": {}}},
|
||||
* )
|
||||
*
|
||||
* @OA\Put(path="/station/{station_id}/playlist/{id}",
|
||||
* tags={"Stations: Playlists"},
|
||||
* description="Update details of a single playlist.",
|
||||
* @OA\RequestBody(
|
||||
* @OA\JsonContent(ref="#/components/schemas/StationPlaylist")
|
||||
* ),
|
||||
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="Playlist ID",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", format="int64")
|
||||
* ),
|
||||
* @OA\Response(response=200, description="Success",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Api_Status")
|
||||
* ),
|
||||
* @OA\Response(response=403, description="Access denied"),
|
||||
* security={{"api_key": {}}},
|
||||
* )
|
||||
*
|
||||
* @OA\Delete(path="/station/{station_id}/playlist/{id}",
|
||||
* tags={"Stations: Playlists"},
|
||||
* description="Delete a single playlist relay.",
|
||||
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="Playlist ID",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", format="int64")
|
||||
* ),
|
||||
* @OA\Response(response=200, description="Success",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Api_Status")
|
||||
* ),
|
||||
* @OA\Response(response=403, description="Access denied"),
|
||||
* security={{"api_key": {}}},
|
||||
* )
|
||||
*/
|
||||
}
|
|
@ -46,7 +46,7 @@ class RemotesController extends AbstractStationCrudController
|
|||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="Streamer ID",
|
||||
* description="Remote Relay ID",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", format="int64")
|
||||
* ),
|
||||
|
@ -67,7 +67,7 @@ class RemotesController extends AbstractStationCrudController
|
|||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="Streamer ID",
|
||||
* description="Remote Relay ID",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", format="int64")
|
||||
* ),
|
||||
|
@ -85,7 +85,7 @@ class RemotesController extends AbstractStationCrudController
|
|||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="StationRemote ID",
|
||||
* description="Remote Relay ID",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", format="int64")
|
||||
* ),
|
||||
|
|
|
@ -1,29 +1,24 @@
|
|||
<?php
|
||||
namespace App\Controller\Stations;
|
||||
|
||||
use App\Radio\PlaylistParser;
|
||||
use App\Form\EntityForm;
|
||||
use Cake\Chronos\Chronos;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use App\Entity;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Slim\Http\UploadedFile;
|
||||
use App\Http\Request;
|
||||
use App\Http\Response;
|
||||
use App\Http\Router;
|
||||
|
||||
class PlaylistsController
|
||||
{
|
||||
/** @var EntityManager */
|
||||
protected $em;
|
||||
|
||||
/** @var Router */
|
||||
protected $router;
|
||||
|
||||
/** @var string */
|
||||
protected $csrf_namespace = 'stations_playlists';
|
||||
|
||||
/** @var array */
|
||||
protected $form_config;
|
||||
/** @var EntityForm */
|
||||
protected $form;
|
||||
|
||||
/** @var \Azura\Doctrine\Repository */
|
||||
protected $playlist_repo;
|
||||
|
@ -32,16 +27,14 @@ class PlaylistsController
|
|||
protected $playlist_media_repo;
|
||||
|
||||
/**
|
||||
* @param EntityManager $em
|
||||
* @param Router $router
|
||||
* @param array $form_config
|
||||
* @param EntityForm $form
|
||||
*
|
||||
* @see \App\Provider\StationsProvider
|
||||
*/
|
||||
public function __construct(EntityManager $em, Router $router, array $form_config)
|
||||
public function __construct(EntityForm $form)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->router = $router;
|
||||
$this->form_config = $form_config;
|
||||
$this->form = $form;
|
||||
$this->em = $form->getEntityManager();
|
||||
|
||||
$this->playlist_repo = $this->em->getRepository(Entity\StationPlaylist::class);
|
||||
$this->playlist_media_repo = $this->em->getRepository(Entity\StationPlaylistMedia::class);
|
||||
|
@ -90,7 +83,7 @@ class PlaylistsController
|
|||
'playlists' => $playlists,
|
||||
'csrf' => $request->getSession()->getCsrf()->generate($this->csrf_namespace),
|
||||
'schedule_now' => Chronos::now()->toIso8601String(),
|
||||
'schedule_url' => $this->router->named('stations:playlists:schedule', ['station' => $station_id]),
|
||||
'schedule_url' => $request->getRouter()->named('stations:playlists:schedule', ['station' => $station_id]),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -152,7 +145,7 @@ class PlaylistsController
|
|||
'allDay' => $playlist_start->eq($playlist_end),
|
||||
'start' => $playlist_start->toIso8601String(),
|
||||
'end' => $playlist_end->toIso8601String(),
|
||||
'url' => (string)$this->router->named('stations:playlists:edit', ['station' => $station_id, 'id' => $playlist->getId()]),
|
||||
'url' => (string)$request->getRouter()->named('stations:playlists:edit', ['station' => $station_id, 'id' => $playlist->getId()]),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -236,13 +229,7 @@ class PlaylistsController
|
|||
|
||||
$record->setIsEnabled($new_value);
|
||||
$this->em->persist($record);
|
||||
|
||||
$station = $request->getStation();
|
||||
$station->setNeedsRestart(true);
|
||||
$this->em->persist($station);
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->refresh($station);
|
||||
|
||||
$flash_message = ($new_value)
|
||||
? __('Playlist enabled.')
|
||||
|
@ -258,136 +245,23 @@ class PlaylistsController
|
|||
public function editAction(Request $request, Response $response, $station_id, $id = null): ResponseInterface
|
||||
{
|
||||
$station = $request->getStation();
|
||||
$this->form->setStation($station);
|
||||
|
||||
$form = new \AzuraForms\Form($this->form_config);
|
||||
|
||||
if (!empty($id)) {
|
||||
$record = $this->_getRecord($id, $station_id);
|
||||
$data = $this->playlist_repo->toArray($record);
|
||||
$form->populate($data);
|
||||
} else {
|
||||
$record = null;
|
||||
}
|
||||
|
||||
if (!empty($_POST) && $form->isValid($_POST)) {
|
||||
$data = $form->getValues();
|
||||
|
||||
if (!($record instanceof Entity\StationPlaylist)) {
|
||||
$record = new Entity\StationPlaylist($station);
|
||||
}
|
||||
|
||||
$this->playlist_repo->fromArray($record, $data);
|
||||
|
||||
// Handle importing a playlist file, if necessary.
|
||||
$files = $request->getUploadedFiles();
|
||||
|
||||
/** @var UploadedFile $import_file */
|
||||
$import_file = $files['import'];
|
||||
if ($import_file->getError() == UPLOAD_ERR_OK) {
|
||||
$matches = $this->_importPlaylist($record, $import_file, $station_id);
|
||||
|
||||
if (is_int($matches)) {
|
||||
$request->getSession()->flash('<b>' . __('Existing playlist imported.') . '</b><br>' . __('%d song(s) were imported into the playlist.', $matches), 'blue');
|
||||
}
|
||||
}
|
||||
|
||||
$this->em->persist($record);
|
||||
$this->em->flush();
|
||||
|
||||
// Reshuffle "shuffled" playlists and clear cache.
|
||||
$this->playlist_media_repo->reshuffleMedia($record);
|
||||
$this->playlist_media_repo->clearMediaQueue($record->getId());
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
$this->em->refresh($station);
|
||||
$record = (null !== $id)
|
||||
? $this->_getRecord($id, $station_id)
|
||||
: null;
|
||||
|
||||
if (false !== ($result = $this->form->process($request, $record))) {
|
||||
$request->getSession()->flash('<b>' . sprintf(($id) ? __('%s updated.') : __('%s added.'), __('Playlist')) . '</b>', 'green');
|
||||
|
||||
return $response->withRedirect($request->getRouter()->fromHere('stations:playlists:index'));
|
||||
}
|
||||
|
||||
return $request->getView()->renderToResponse($response, 'stations/playlists/edit', [
|
||||
'form' => $form,
|
||||
'form' => $this->form,
|
||||
'title' => sprintf(($id) ? __('Edit %s') : __('Add %s'), __('Playlist'))
|
||||
]);
|
||||
}
|
||||
|
||||
protected function _importPlaylist(
|
||||
Entity\StationPlaylist $playlist,
|
||||
UploadedFile $playlist_file,
|
||||
$station_id
|
||||
)
|
||||
{
|
||||
$playlist_raw = (string)$playlist_file->getStream();
|
||||
if (empty($playlist_raw)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$paths = PlaylistParser::getSongs($playlist_raw);
|
||||
|
||||
if (empty($paths)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Assemble list of station media to match against.
|
||||
$media_lookup = [];
|
||||
|
||||
$media_info_raw = $this->em->createQuery(/** @lang DQL */'SELECT sm.id, sm.path
|
||||
FROM App\Entity\StationMedia sm
|
||||
WHERE sm.station_id = :station_id')
|
||||
->setParameter('station_id', $station_id)
|
||||
->getArrayResult();
|
||||
|
||||
foreach($media_info_raw as $row) {
|
||||
$path_hash = md5($row['path']);
|
||||
$media_lookup[$path_hash] = $row['id'];
|
||||
}
|
||||
|
||||
// Run all paths against the lookup list of hashes.
|
||||
$matches = [];
|
||||
|
||||
foreach($paths as $path_raw) {
|
||||
// De-Windows paths (if applicable)
|
||||
$path_raw = str_replace('\\', '/', $path_raw);
|
||||
|
||||
// Work backwards from the basename to try to find matches.
|
||||
$path_parts = explode('/', $path_raw);
|
||||
for($i = 1; $i <= count($path_parts); $i++) {
|
||||
$path_attempt = implode('/', array_slice($path_parts, 0-$i));
|
||||
$path_hash = md5($path_attempt);
|
||||
|
||||
if (isset($media_lookup[$path_hash])) {
|
||||
$matches[] = $media_lookup[$path_hash];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign all matched media to the playlist.
|
||||
if (!empty($matches)) {
|
||||
$matched_media = $this->em->createQuery(/** @lang DQL */'SELECT sm
|
||||
FROM App\Entity\StationMedia sm
|
||||
WHERE sm.station_id = :station_id AND sm.id IN (:matched_ids)')
|
||||
->setParameter('station_id', $station_id)
|
||||
->setParameter('matched_ids', $matches)
|
||||
->execute();
|
||||
|
||||
$weight = $this->playlist_media_repo->getHighestSongWeight($playlist);
|
||||
|
||||
foreach($matched_media as $media) {
|
||||
$weight++;
|
||||
|
||||
/** @var Entity\StationMedia $media */
|
||||
$this->playlist_media_repo->addMediaToPlaylist($media, $playlist, $weight);
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
$this->playlist_media_repo->reshuffleMedia($playlist);
|
||||
}
|
||||
|
||||
return count($matches);
|
||||
}
|
||||
|
||||
public function deleteAction(Request $request, Response $response, $station_id, $id, $csrf_token): ResponseInterface
|
||||
{
|
||||
$request->getSession()->getCsrf()->verify($csrf_token, $this->csrf_namespace);
|
||||
|
|
|
@ -4,6 +4,8 @@ namespace App\Entity;
|
|||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use OpenApi\Annotations as OA;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
use DateTime;
|
||||
|
@ -12,6 +14,8 @@ use DateTime;
|
|||
* @ORM\Table(name="station_playlists")
|
||||
* @ORM\Entity
|
||||
* @ORM\HasLifecycleCallbacks
|
||||
*
|
||||
* @OA\Schema(type="object")
|
||||
*/
|
||||
class StationPlaylist
|
||||
{
|
||||
|
@ -42,6 +46,8 @@ class StationPlaylist
|
|||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue(strategy="IDENTITY")
|
||||
*
|
||||
* @OA\Property(example=1)
|
||||
* @var int
|
||||
*/
|
||||
protected $id;
|
||||
|
@ -63,138 +69,212 @@ class StationPlaylist
|
|||
|
||||
/**
|
||||
* @ORM\Column(name="name", type="string", length=200)
|
||||
*
|
||||
* @Assert\NotBlank()
|
||||
* @OA\Property(example="Test Playlist")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="type", type="string", length=50)
|
||||
*
|
||||
* @Assert\Choice(choices={"default", "scheduled", "once_per_x_songs", "once_per_x_minutes", "once_per_hour", "once_per_day", "custom"})
|
||||
* @OA\Property(example="default")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = self::TYPE_DEFAULT;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="source", type="string", length=50)
|
||||
*
|
||||
* @Assert\Choice(choices={"songs", "remote_url"})
|
||||
* @OA\Property(example="songs")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $source = self::SOURCE_SONGS;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="playback_order", type="string", length=50)
|
||||
*
|
||||
* @Assert\Choice(choices={"random", "shuffle", "sequential"})
|
||||
* @OA\Property(example="shuffle")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $order = self::ORDER_SHUFFLE;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="remote_url", type="string", length=255, nullable=true)
|
||||
*
|
||||
* @OA\Property(example="http://remote-url.example.com/stream.mp3")
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $remote_url;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="remote_type", type="string", length=25, nullable=true)
|
||||
*
|
||||
* @Assert\Choice(choices={"stream", "playlist"})
|
||||
* @OA\Property(example="stream")
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $remote_type = self::REMOTE_TYPE_STREAM;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="remote_timeout", type="smallint")
|
||||
*
|
||||
* @OA\Property(example=0)
|
||||
*
|
||||
* @var int The total time (in seconds) that Liquidsoap should buffer remote URL streams.
|
||||
*/
|
||||
protected $remote_buffer = 0;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="is_enabled", type="boolean")
|
||||
*
|
||||
* @OA\Property(example=true)
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $is_enabled = true;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="is_jingle", type="boolean")
|
||||
*
|
||||
* @OA\Property(example=false)
|
||||
*
|
||||
* @var bool If yes, do not send jingle metadata to AutoDJ or trigger web hooks.
|
||||
*/
|
||||
protected $is_jingle = false;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="play_per_songs", type="smallint")
|
||||
*
|
||||
* @OA\Property(example=5)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $play_per_songs = 0;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="play_per_minutes", type="smallint")
|
||||
*
|
||||
* @OA\Property(example=120)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $play_per_minutes = 0;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="play_per_hour_minute", type="smallint")
|
||||
*
|
||||
* @OA\Property(example=15)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $play_per_hour_minute = 0;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="schedule_start_time", type="smallint")
|
||||
*
|
||||
* @OA\Property(example=900)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $schedule_start_time = 0;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="schedule_end_time", type="smallint")
|
||||
*
|
||||
* @OA\Property(example=2200)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $schedule_end_time = 0;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="schedule_days", type="string", length=50, nullable=true)
|
||||
*
|
||||
* @OA\Property(example="0,1,2,3")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $schedule_days;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="play_once_time", type="smallint")
|
||||
*
|
||||
* @OA\Property(example=1500)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $play_once_time = 0;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="play_once_days", type="string", length=50, nullable=true)
|
||||
*
|
||||
* @OA\Property(example="0,1,2,3")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $play_once_days;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="weight", type="smallint")
|
||||
*
|
||||
* @OA\Property(example=3)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $weight = self::DEFAULT_WEIGHT;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="include_in_requests", type="boolean")
|
||||
*
|
||||
* @OA\Property(example=true)
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $include_in_requests = true;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="include_in_automation", type="boolean")
|
||||
*
|
||||
* @OA\Property(example=false)
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $include_in_automation = false;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="interrupt_other_songs", type="boolean")
|
||||
*
|
||||
* @OA\Property(example=false)
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $interrupt_other_songs = false;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="loop_playlist_once", type="boolean")
|
||||
*
|
||||
* @OA\Property(example=false)
|
||||
*
|
||||
* @var bool Whether to loop the playlist at the end of its playback.
|
||||
*/
|
||||
protected $loop_playlist_once = false;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="play_single_track", type="boolean")
|
||||
*
|
||||
* @OA\Property(example=false)
|
||||
*
|
||||
* @var bool Whether to only play a single track from the specified playlist when scheduled.
|
||||
*/
|
||||
protected $play_single_track = false;
|
||||
|
|
|
@ -12,7 +12,11 @@ use Symfony\Component\Validator\ConstraintViolation;
|
|||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
/**
|
||||
* A generic class that handles binding an entity to a
|
||||
* A generic class that handles binding an entity to an AzuraForms
|
||||
* instance and moving the data back and forth.
|
||||
*
|
||||
* This class exists primarily to facilitate the switch to Symfony's
|
||||
* Serializer and Validator classes, to allow for API parity.
|
||||
*/
|
||||
class EntityForm extends \AzuraForms\Form
|
||||
{
|
||||
|
@ -31,6 +35,9 @@ class EntityForm extends \AzuraForms\Form
|
|||
/** @var array The default context sent to form normalization/denormalization functions. */
|
||||
protected $defaultContext = [];
|
||||
|
||||
/** @var Station|null */
|
||||
protected $station;
|
||||
|
||||
/**
|
||||
* @param EntityManager $em
|
||||
* @param Serializer $serializer
|
||||
|
@ -131,6 +138,12 @@ class EntityForm extends \AzuraForms\Form
|
|||
|
||||
$this->em->persist($record);
|
||||
$this->em->flush($record);
|
||||
|
||||
// Intentionally refresh the station entity in case it didn't refresh elsewhere.
|
||||
if ($this->station instanceof Station && APP_TESTING_MODE) {
|
||||
$this->em->refresh($this->station);
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
|
@ -210,6 +223,8 @@ class EntityForm extends \AzuraForms\Form
|
|||
*/
|
||||
public function setStation(Station $station): void
|
||||
{
|
||||
$this->station = $station;
|
||||
|
||||
$this->defaultContext[ObjectNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS] = [
|
||||
$this->entityClass => [
|
||||
'station' => $station,
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity;
|
||||
use App\Http\Request;
|
||||
use App\Radio\PlaylistParser;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Slim\Http\UploadedFile;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
class PlaylistForm extends EntityForm
|
||||
{
|
||||
/** @var Entity\Repository\StationPlaylistMediaRepository */
|
||||
protected $playlist_media_repo;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $em,
|
||||
Serializer $serializer,
|
||||
ValidatorInterface $validator,
|
||||
array $options = [],
|
||||
?array $defaults = null
|
||||
) {
|
||||
parent::__construct($em, $serializer, $validator, $options, $defaults);
|
||||
|
||||
$this->entityClass = Entity\StationPlaylist::class;
|
||||
$this->playlist_media_repo = $em->getRepository(Entity\StationPlaylistMedia::class);
|
||||
}
|
||||
|
||||
public function process(Request $request, $record = null)
|
||||
{
|
||||
$record = parent::process($request, $record);
|
||||
|
||||
if ($record instanceof Entity\StationPlaylist) {
|
||||
$files = $request->getUploadedFiles();
|
||||
|
||||
/** @var UploadedFile $import_file */
|
||||
$import_file = $files['import'];
|
||||
if (UPLOAD_ERR_OK === $import_file->getError()) {
|
||||
$matches = $this->_importPlaylist($record, $import_file);
|
||||
|
||||
if (is_int($matches)) {
|
||||
$request->getSession()->flash('<b>' . __('Existing playlist imported.') . '</b><br>' . __('%d song(s) were imported into the playlist.', $matches), 'blue');
|
||||
}
|
||||
}
|
||||
|
||||
$this->playlist_media_repo->reshuffleMedia($record);
|
||||
$this->playlist_media_repo->clearMediaQueue($record->getId());
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entity\StationPlaylist $playlist
|
||||
* @param UploadedFile $playlist_file
|
||||
* @return bool|int
|
||||
*/
|
||||
protected function _importPlaylist(Entity\StationPlaylist $playlist, UploadedFile $playlist_file)
|
||||
{
|
||||
$station_id = $this->station->getId();
|
||||
|
||||
$playlist_raw = (string)$playlist_file->getStream();
|
||||
if (empty($playlist_raw)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$paths = PlaylistParser::getSongs($playlist_raw);
|
||||
|
||||
if (empty($paths)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Assemble list of station media to match against.
|
||||
$media_lookup = [];
|
||||
|
||||
$media_info_raw = $this->em->createQuery(/** @lang DQL */'SELECT sm.id, sm.path
|
||||
FROM App\Entity\StationMedia sm
|
||||
WHERE sm.station_id = :station_id')
|
||||
->setParameter('station_id', $station_id)
|
||||
->getArrayResult();
|
||||
|
||||
foreach($media_info_raw as $row) {
|
||||
$path_hash = md5($row['path']);
|
||||
$media_lookup[$path_hash] = $row['id'];
|
||||
}
|
||||
|
||||
// Run all paths against the lookup list of hashes.
|
||||
$matches = [];
|
||||
|
||||
foreach($paths as $path_raw) {
|
||||
// De-Windows paths (if applicable)
|
||||
$path_raw = str_replace('\\', '/', $path_raw);
|
||||
|
||||
// Work backwards from the basename to try to find matches.
|
||||
$path_parts = explode('/', $path_raw);
|
||||
for($i = 1; $i <= count($path_parts); $i++) {
|
||||
$path_attempt = implode('/', array_slice($path_parts, 0-$i));
|
||||
$path_hash = md5($path_attempt);
|
||||
|
||||
if (isset($media_lookup[$path_hash])) {
|
||||
$matches[] = $media_lookup[$path_hash];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign all matched media to the playlist.
|
||||
if (!empty($matches)) {
|
||||
$matched_media = $this->em->createQuery(/** @lang DQL */'SELECT sm
|
||||
FROM App\Entity\StationMedia sm
|
||||
WHERE sm.station_id = :station_id AND sm.id IN (:matched_ids)')
|
||||
->setParameter('station_id', $station_id)
|
||||
->setParameter('matched_ids', $matches)
|
||||
->execute();
|
||||
|
||||
$weight = $this->playlist_media_repo->getHighestSongWeight($playlist);
|
||||
|
||||
foreach($matched_media as $media) {
|
||||
$weight++;
|
||||
|
||||
/** @var Entity\StationMedia $media */
|
||||
$this->playlist_media_repo->addMediaToPlaylist($media, $playlist, $weight);
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
$this->playlist_media_repo->reshuffleMedia($playlist);
|
||||
}
|
||||
|
||||
return count($matches);
|
||||
}
|
||||
}
|
|
@ -102,6 +102,7 @@ class ApiProvider implements ServiceProviderInterface
|
|||
Api\Admin\SettingsController::class,
|
||||
Api\Admin\StationsController::class,
|
||||
Api\Stations\MountsController::class,
|
||||
Api\Stations\PlaylistsController::class,
|
||||
Api\Stations\RemotesController::class,
|
||||
Api\Stations\StreamersController::class,
|
||||
];
|
||||
|
|
|
@ -33,6 +33,20 @@ class FormProvider implements ServiceProviderInterface
|
|||
);
|
||||
};
|
||||
|
||||
$di[Form\PlaylistForm::class] = function($di) {
|
||||
/** @var \Azura\Config $config */
|
||||
$config = $di[\Azura\Config::class];
|
||||
|
||||
return new Form\PlaylistForm(
|
||||
$di[EntityManager::class],
|
||||
$di[Serializer::class],
|
||||
$di[ValidatorInterface::class],
|
||||
$config->get('forms/playlist', [
|
||||
'customization' => $di[\App\Customization::class]
|
||||
])
|
||||
);
|
||||
};
|
||||
|
||||
$di[Form\StationForm::class] = function($di) {
|
||||
/** @var \Azura\Config $config */
|
||||
$config = $di[\Azura\Config::class];
|
||||
|
@ -85,6 +99,7 @@ class FormProvider implements ServiceProviderInterface
|
|||
Entity\Station::class => Form\StationForm::class,
|
||||
Entity\User::class => Form\UserForm::class,
|
||||
Entity\RolePermission::class => Form\PermissionsForm::class,
|
||||
Entity\StationPlaylist::class => Form\PlaylistForm::class,
|
||||
];
|
||||
|
||||
return new Form\EntityFormManager(
|
||||
|
|
|
@ -89,16 +89,7 @@ class StationsProvider implements ServiceProviderInterface
|
|||
};
|
||||
|
||||
$di[Stations\PlaylistsController::class] = function($di) {
|
||||
/** @var Azura\Config $config */
|
||||
$config = $di[Azura\Config::class];
|
||||
|
||||
return new Stations\PlaylistsController(
|
||||
$di[EntityManager::class],
|
||||
$di['router'],
|
||||
$config->get('forms/playlist', [
|
||||
'customization' => $di[App\Customization::class]
|
||||
])
|
||||
);
|
||||
return new Stations\PlaylistsController($di[App\Form\PlaylistForm::class]);
|
||||
};
|
||||
|
||||
$di[Stations\RemotesController::class] = function($di) {
|
||||
|
|
|
@ -851,6 +851,139 @@ paths:
|
|||
security:
|
||||
-
|
||||
api_key: []
|
||||
'/station/{station_id}/playlists':
|
||||
get:
|
||||
tags:
|
||||
- 'Stations: Playlists'
|
||||
description: 'List all current playlists.'
|
||||
parameters:
|
||||
-
|
||||
$ref: '#/components/parameters/station_id_required'
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/StationPlaylist'
|
||||
'403':
|
||||
description: 'Access denied'
|
||||
security:
|
||||
-
|
||||
api_key: []
|
||||
post:
|
||||
tags:
|
||||
- 'Stations: Playlists'
|
||||
description: 'Create a new playlist.'
|
||||
parameters:
|
||||
-
|
||||
$ref: '#/components/parameters/station_id_required'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/StationPlaylist'
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/StationPlaylist'
|
||||
'403':
|
||||
description: 'Access denied'
|
||||
security:
|
||||
-
|
||||
api_key: []
|
||||
'/station/{station_id}/playlist/{id}':
|
||||
get:
|
||||
tags:
|
||||
- 'Stations: Playlists'
|
||||
description: 'Retrieve details for a single playlist.'
|
||||
parameters:
|
||||
-
|
||||
$ref: '#/components/parameters/station_id_required'
|
||||
-
|
||||
name: id
|
||||
in: path
|
||||
description: 'Playlist ID'
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/StationPlaylist'
|
||||
'403':
|
||||
description: 'Access denied'
|
||||
security:
|
||||
-
|
||||
api_key: []
|
||||
put:
|
||||
tags:
|
||||
- 'Stations: Playlists'
|
||||
description: 'Update details of a single playlist.'
|
||||
parameters:
|
||||
-
|
||||
$ref: '#/components/parameters/station_id_required'
|
||||
-
|
||||
name: id
|
||||
in: path
|
||||
description: 'Playlist ID'
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/StationPlaylist'
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Api_Status'
|
||||
'403':
|
||||
description: 'Access denied'
|
||||
security:
|
||||
-
|
||||
api_key: []
|
||||
delete:
|
||||
tags:
|
||||
- 'Stations: Playlists'
|
||||
description: 'Delete a single playlist relay.'
|
||||
parameters:
|
||||
-
|
||||
$ref: '#/components/parameters/station_id_required'
|
||||
-
|
||||
name: id
|
||||
in: path
|
||||
description: 'Playlist ID'
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Api_Status'
|
||||
'403':
|
||||
description: 'Access denied'
|
||||
security:
|
||||
-
|
||||
api_key: []
|
||||
'/station/{station_id}/queue':
|
||||
get:
|
||||
tags:
|
||||
|
@ -988,7 +1121,7 @@ paths:
|
|||
-
|
||||
name: id
|
||||
in: path
|
||||
description: 'Streamer ID'
|
||||
description: 'Remote Relay ID'
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
|
@ -1015,7 +1148,7 @@ paths:
|
|||
-
|
||||
name: id
|
||||
in: path
|
||||
description: 'Streamer ID'
|
||||
description: 'Remote Relay ID'
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
|
@ -1047,7 +1180,7 @@ paths:
|
|||
-
|
||||
name: id
|
||||
in: path
|
||||
description: 'StationRemote ID'
|
||||
description: 'Remote Relay ID'
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
|
@ -1438,7 +1571,7 @@ components:
|
|||
connected_on:
|
||||
description: 'UNIX timestamp that the user first connected.'
|
||||
type: integer
|
||||
example: 1554805409
|
||||
example: 1555103144
|
||||
connected_time:
|
||||
description: 'Number of seconds that the user has been connected.'
|
||||
type: integer
|
||||
|
@ -1534,7 +1667,7 @@ components:
|
|||
cued_at:
|
||||
description: 'UNIX timestamp when the item was cued for playback.'
|
||||
type: integer
|
||||
example: 1554805409
|
||||
example: 1555103144
|
||||
autodj_custom_uri:
|
||||
description: 'Custom AutoDJ playback URI, if it exists.'
|
||||
type: string
|
||||
|
@ -1589,7 +1722,7 @@ components:
|
|||
played_at:
|
||||
description: 'UNIX timestamp when playback started.'
|
||||
type: integer
|
||||
example: 1554805409
|
||||
example: 1555103144
|
||||
duration:
|
||||
description: 'Duration of the song in seconds'
|
||||
type: integer
|
||||
|
@ -1720,7 +1853,7 @@ components:
|
|||
timestamp:
|
||||
description: 'The current UNIX timestamp'
|
||||
type: integer
|
||||
example: 1554805409
|
||||
example: 1555103144
|
||||
type: object
|
||||
Api_Time:
|
||||
properties:
|
||||
|
@ -1912,6 +2045,85 @@ components:
|
|||
type: string
|
||||
items: { }
|
||||
type: object
|
||||
StationPlaylist:
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
example: 1
|
||||
name:
|
||||
type: string
|
||||
example: 'Test Playlist'
|
||||
type:
|
||||
type: string
|
||||
example: default
|
||||
source:
|
||||
type: string
|
||||
example: songs
|
||||
order:
|
||||
type: string
|
||||
example: shuffle
|
||||
remote_url:
|
||||
type: string
|
||||
example: 'http://remote-url.example.com/stream.mp3'
|
||||
remote_type:
|
||||
type: string
|
||||
example: stream
|
||||
remote_buffer:
|
||||
description: 'The total time (in seconds) that Liquidsoap should buffer remote URL streams.'
|
||||
type: integer
|
||||
example: 0
|
||||
is_enabled:
|
||||
type: boolean
|
||||
example: true
|
||||
is_jingle:
|
||||
description: 'If yes, do not send jingle metadata to AutoDJ or trigger web hooks.'
|
||||
type: boolean
|
||||
example: false
|
||||
play_per_songs:
|
||||
type: integer
|
||||
example: 5
|
||||
play_per_minutes:
|
||||
type: integer
|
||||
example: 120
|
||||
play_per_hour_minute:
|
||||
type: integer
|
||||
example: 15
|
||||
schedule_start_time:
|
||||
type: integer
|
||||
example: 900
|
||||
schedule_end_time:
|
||||
type: integer
|
||||
example: 2200
|
||||
schedule_days:
|
||||
type: string
|
||||
example: '0,1,2,3'
|
||||
play_once_time:
|
||||
type: integer
|
||||
example: 1500
|
||||
play_once_days:
|
||||
type: string
|
||||
example: '0,1,2,3'
|
||||
weight:
|
||||
type: integer
|
||||
example: 3
|
||||
include_in_requests:
|
||||
type: boolean
|
||||
example: true
|
||||
include_in_automation:
|
||||
type: boolean
|
||||
example: false
|
||||
interrupt_other_songs:
|
||||
type: boolean
|
||||
example: false
|
||||
loop_playlist_once:
|
||||
description: 'Whether to loop the playlist at the end of its playback.'
|
||||
type: boolean
|
||||
example: false
|
||||
play_single_track:
|
||||
description: 'Whether to only play a single track from the specified playlist when scheduled.'
|
||||
type: boolean
|
||||
example: false
|
||||
type: object
|
||||
StationRemote:
|
||||
properties:
|
||||
id:
|
||||
|
@ -1982,7 +2194,7 @@ components:
|
|||
example: true
|
||||
reactivate_at:
|
||||
type: integer
|
||||
example: 1554805409
|
||||
example: 1555103144
|
||||
type: object
|
||||
User:
|
||||
properties:
|
||||
|
@ -2012,10 +2224,10 @@ components:
|
|||
example: A1B2C3D4
|
||||
created_at:
|
||||
type: integer
|
||||
example: 1554805409
|
||||
example: 1555103144
|
||||
updated_at:
|
||||
type: integer
|
||||
example: 1554805409
|
||||
example: 1555103144
|
||||
roles:
|
||||
items: { }
|
||||
type: object
|
||||
|
|
Loading…
Reference in New Issue