Add API endpoint and frontend modal to view playlist's AutoDJ queue.

This commit is contained in:
Buster "Silver Eagle" Neece 2021-03-08 07:20:54 -06:00
parent 4417b7708e
commit 91c73de176
No known key found for this signature in database
GPG Key ID: 6D9E12FF03411F4E
7 changed files with 183 additions and 1 deletions

View File

@ -17,6 +17,9 @@ release channel, you can take advantage of these new features and fixes.
- You can now generate listener reports for specific time periods instead of just day ranges.
- For sequential or shuffled playlists, you can now view the internal queue that the AzuraCast AutoDJ uses to track its
song playback order from the "More" dropdown next to the playlist.
## Code Quality/Technical Changes
- We have removed the "?12345678" cache-busting timestamp query strings appended to the end of stream URLs. These have

View File

@ -361,6 +361,16 @@ return function (App $app) {
Controller\Api\Stations\Playlists\PutOrderAction::class
);
$group->get(
'/queue',
Controller\Api\Stations\Playlists\GetQueueAction::class
)->setName('api:stations:playlist:queue');
$group->delete(
'/queue',
Controller\Api\Stations\Playlists\DeleteQueueAction::class
);
$group->post(
'/import',
Controller\Api\Stations\Playlists\ImportAction::class

View File

@ -43,6 +43,10 @@
v-if="row.item.source === 'songs' && row.item.order === 'sequential'">
{{ langReorderButton }}
</b-dropdown-item>
<b-dropdown-item @click.prevent="doQueue(row.item.links.queue)"
v-if="row.item.source === 'songs' && row.item.order !== 'random'">
{{ langQueueButton }}
</b-dropdown-item>
<b-dropdown-item @click.prevent="doModify(row.item.links.reshuffle)"
v-if="row.item.order === 'shuffle'">
{{ langReshuffleButton }}
@ -110,6 +114,8 @@
<edit-modal ref="editModal" :create-url="listUrl" :station-time-zone="stationTimeZone"
:enable-advanced-features="enableAdvancedFeatures" @relist="relist"></edit-modal>
<reorder-modal ref="reorderModal"></reorder-modal>
<queue-modal ref="queueModal"></queue-modal>
<reorder-modal ref="reorderModal"></reorder-modal>
<import-modal ref="importModal" @relist="relist"></import-modal>
</div>
</template>
@ -120,11 +126,12 @@ import Schedule from './components/ScheduleView';
import EditModal from './station_playlists/PlaylistEditModal';
import ReorderModal from './station_playlists/PlaylistReorderModal';
import ImportModal from './station_playlists/PlaylistImportModal';
import QueueModal from './station_playlists/PlaylistQueueModal';
import axios from 'axios';
export default {
name: 'StationPlaylists',
components: { ImportModal, ReorderModal, EditModal, Schedule, DataTable },
components: { QueueModal, ImportModal, ReorderModal, EditModal, Schedule, DataTable },
props: {
listUrl: String,
scheduleUrl: String,
@ -156,6 +163,9 @@ export default {
langReorderButton () {
return this.$gettext('Reorder');
},
langQueueButton () {
return this.$gettext('Playback Queue');
},
langReshuffleButton () {
return this.$gettext('Reshuffle');
},
@ -226,6 +236,9 @@ export default {
doReorder (url) {
this.$refs.reorderModal.open(url);
},
doQueue (url) {
this.$refs.queueModal.open(url);
},
doImport (url) {
this.$refs.importModal.open(url);
},

View File

@ -0,0 +1,86 @@
<template>
<b-modal size="lg" id="queue_modal" ref="modal" :title="langTitle" :busy="loading">
<p>
<translate key="queue_info">This queue contains the remaining tracks in the order they will be queued by the AzuraCast AutoDJ (if the tracks are eligible to be played).</translate>
</p>
<b-overlay variant="card" :show="loading">
<b-table-simple striped class="sortable mb-0">
<b-thead>
<tr>
<th style="width: 50%;" key="lang_col_title" v-translate>Title</th>
<th style="width: 50%;" key="lang_col_artist" v-translate>Artist</th>
</tr>
</b-thead>
<b-tbody>
<tr class="align-middle" v-for="(row,index) in media" :key="row.id">
<td><big>{{ row.title }}</big></td>
<td>{{ row.artist }}</td>
</tr>
</b-tbody>
</b-table-simple>
</b-overlay>
<template v-slot:modal-footer>
<b-button variant="default" type="button" @click="close">
<translate key="lang_btn_close">Close</translate>
</b-button>
<b-button variant="danger" type="submit" @click="doClear">
<translate key="lang_btn_clear_queue">Clear Queue</translate>
</b-button>
</template>
</b-modal>
</template>
<script>
import axios from 'axios';
export default {
name: 'QueueModal',
data () {
return {
loading: true,
queueUrl: null,
media: []
};
},
computed: {
langTitle () {
return this.$gettext('Playback Queue');
},
},
methods: {
open (queueUrl) {
this.$refs.modal.show();
this.queueUrl = queueUrl;
this.loading = true;
axios.get(this.queueUrl).then((resp) => {
this.media = resp.data;
this.loading = false;
}).catch((err) => {
this.handleError(err);
});
},
doClear () {
axios.delete(this.queueUrl).then((resp) => {
notify('<b>' + this.$gettext('Playlist queue cleared.') + '</b>', 'success', false);
this.close();
}).catch((err) => {
this.handleError(err);
});
},
handleError (err) {
console.error(err);
if (err.response.message) {
notify('<b>' + err.response.message + '</b>', 'danger');
}
},
close () {
this.loading = false;
this.error = null;
this.queueUrl = null;
this.$refs.modal.hide();
}
}
};
</script>

View File

@ -0,0 +1,31 @@
<?php
namespace App\Controller\Api\Stations\Playlists;
use App\Entity;
use App\Http\Response;
use App\Http\ServerRequest;
use Psr\Http\Message\ResponseInterface;
class DeleteQueueAction extends AbstractPlaylistsAction
{
public function __invoke(
ServerRequest $request,
Response $response,
$id
): ResponseInterface {
$record = $this->requireRecord($request->getStation(), $id);
$record->setQueue(null);
$this->em->persist($record);
$this->em->flush();
return $response->withJson(
new Entity\Api\Status(
true,
__('Playlist queue cleared.')
)
);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Controller\Api\Stations\Playlists;
use App\Entity;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Paginator;
use Psr\Http\Message\ResponseInterface;
class GetQueueAction extends AbstractPlaylistsAction
{
public function __invoke(
ServerRequest $request,
Response $response,
$id
): ResponseInterface {
$record = $this->requireRecord($request->getStation(), $id);
if (Entity\StationPlaylist::SOURCE_SONGS !== $record->getSource()) {
throw new \InvalidArgumentException('This playlist does not have songs as its primary source.');
}
if (Entity\StationPlaylist::ORDER_RANDOM === $record->getOrder()) {
throw new \InvalidArgumentException('This playlist is always shuffled and has no visible queue.');
}
$queue = (array)$record->getQueue();
$paginator = Paginator::fromArray($queue, $request);
return $paginator->write($response);
}
}

View File

@ -209,6 +209,12 @@ class PlaylistsController extends AbstractScheduledEntityController
[],
!$isInternal
),
'queue' => $router->fromHere(
'api:stations:playlist:queue',
['id' => $record->getId()],
[],
!$isInternal
),
'import' => $router->fromHere('api:stations:playlist:import', ['id' => $record->getId()], [], !$isInternal),
'self' => $router->fromHere($this->resourceRouteName, ['id' => $record->getId()], [], !$isInternal),
];