4
0
mirror of https://github.com/AzuraCast/AzuraCast.git synced 2024-06-13 04:36:41 +00:00

Closes #158 -- Add next-playing song and song duration to nowplaying API [skip ci]

This commit is contained in:
Buster Silver 2017-06-17 21:11:01 -05:00
parent 4f682475be
commit 10b03cf218
12 changed files with 169 additions and 39 deletions

View File

@ -17,6 +17,14 @@ class NowPlaying
*/
public $station;
/**
* Listener details
*
* @SWG\Property
* @var NowPlayingListeners
*/
public $listeners;
/**
* Current Song
*
@ -26,12 +34,12 @@ class NowPlaying
public $now_playing;
/**
* Listener details
* Next Playing Song
*
* @SWG\Property
* @var NowPlayingListeners
* @var SongHistory
*/
public $listeners;
public $playing_next;
/**
* @SWG\Property

View File

@ -25,6 +25,14 @@ class SongHistory
*/
public $played_at;
/**
* Duration of the song in seconds
*
* @SWG\Property(example=180)
* @var int
*/
public $duration;
/**
* Indicates whether the song is a listener request.
*

View File

@ -0,0 +1,40 @@
<?php
namespace Migration;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20170618013019 extends AbstractMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE song_history ADD media_id INT DEFAULT NULL, ADD duration INT DEFAULT NULL');
$this->addSql('ALTER TABLE song_history ADD CONSTRAINT FK_2AD16164EA9FDD75 FOREIGN KEY (media_id) REFERENCES station_media (id) ON DELETE CASCADE');
$this->addSql('CREATE INDEX IDX_2AD16164EA9FDD75 ON song_history (media_id)');
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql',
'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE song_history DROP FOREIGN KEY FK_2AD16164EA9FDD75');
$this->addSql('DROP INDEX IDX_2AD16164EA9FDD75 ON song_history');
$this->addSql('ALTER TABLE song_history DROP media_id, DROP duration');
}
}

View File

@ -5,13 +5,40 @@ use Entity;
class SongHistoryRepository extends \App\Doctrine\Repository
{
public function getNextSongForStation(Entity\Station $station)
{
$threshold = 60 * 15;
$next_song = $this->_em->createQuery('SELECT sh, s, sm
FROM ' . $this->_entityName . ' sh JOIN sh.song s JOIN sh.media sm
WHERE sh.station_id = :station_id
AND sh.timestamp_cued >= :threshold
AND sh.timestamp_start = 0
AND sh.timestamp_end = 0
ORDER BY sh.id DESC')
->setParameter('station_id', $station->id)
->setParameter('threshold', time() - $threshold)
->setMaxResults(1)
->getOneOrNullResult();
if (!($next_song instanceof Entity\SongHistory)) {
/** @var Entity\Repository\StationMediaRepository $media_repo */
$media_repo = $this->_em->getRepository(Entity\StationMedia::class);
$next_song = $media_repo->getNextSong($station);
}
return $next_song;
}
/**
* @param int $num_entries
* @return array
*/
public function getHistoryForStation(Entity\Station $station, $num_entries = 5)
{
$history = $this->_em->createQuery('SELECT sh, s FROM ' . $this->_entityName . ' sh JOIN sh.song s
$history = $this->_em->createQuery('SELECT sh, s
FROM ' . $this->_entityName . ' sh JOIN sh.song s
WHERE sh.station_id = :station_id
AND sh.timestamp_end != 0
ORDER BY sh.id DESC')

View File

@ -81,7 +81,7 @@ class StationMediaRepository extends \App\Doctrine\Repository
* Determine the next-playing song for this station based on its playlist rotation rules.
*
* @param Entity\Station $station
* @return string
* @return Entity\SongHistory|null
*/
public function getNextSong(Entity\Station $station)
{
@ -236,6 +236,8 @@ class StationMediaRepository extends \App\Doctrine\Repository
}
}
}
return null;
}
protected function _playSongFromRequest(Entity\StationRequest $request)
@ -245,7 +247,9 @@ class StationMediaRepository extends \App\Doctrine\Repository
$sh->song = $request->track->song;
$sh->station = $request->station;
$sh->request = $request;
$sh->media = $request->track;
$sh->duration = $request->track->length;
$sh->timestamp_cued = time();
$this->_em->persist($sh);
@ -254,7 +258,7 @@ class StationMediaRepository extends \App\Doctrine\Repository
$this->_em->flush();
return $this->_playMedia($request->track);
return $sh;
}
protected function _getTimeCode()
@ -311,27 +315,17 @@ class StationMediaRepository extends \App\Doctrine\Repository
$sh->song = $random_song->song;
$sh->playlist = $playlist;
$sh->station = $playlist->station;
$sh->media = $random_song;
$sh->duration = $random_song->length;
$sh->timestamp_cued = time();
$this->_em->persist($sh);
$this->_em->flush();
return $this->_playMedia($random_song);
return $sh;
}
return $this->_playFallback();
}
protected function _playMedia(Entity\StationMedia $media)
{
// 'annotate:type=\"song\",album=\"$ALBUM\",display_desc=\"$FULLSHOWNAME\",liq_start_next=\"2.5\",liq_fade_in=\"3.5\",liq_fade_out=\"3.5\":$SONGPATH'
$song_path = $media->getFullPath();
return 'annotate:'.implode(',', $media->getAnnotations()).':'.$song_path;
}
protected function _playFallback()
{
$fallback_song_path = APP_INCLUDE_ROOT.'/resources/error.mp3';
return $fallback_song_path;
return null;
}
}

View File

@ -42,6 +42,9 @@ class SongHistory extends \App\Doctrine\Entity
/** @Column(name="playlist_id", type="integer", nullable=true) */
protected $playlist_id;
/** @Column(name="media_id", type="integer", nullable=true) */
protected $media_id;
/** @Column(name="request_id", type="integer", nullable=true) */
protected $request_id;
@ -56,6 +59,9 @@ class SongHistory extends \App\Doctrine\Entity
return $this->timestamp_start;
}
/** @Column(name="duration", type="integer", nullable=true) */
protected $duration;
/** @Column(name="listeners_start", type="integer", nullable=true) */
protected $listeners_start;
@ -117,6 +123,14 @@ class SongHistory extends \App\Doctrine\Entity
*/
protected $request;
/**
* @ManyToOne(targetEntity="StationMedia")
* @JoinColumns({
* @JoinColumn(name="media_id", referencedColumnName="id", onDelete="CASCADE")
* })
*/
protected $media;
/**
* @return Api\SongHistory
*/
@ -125,6 +139,7 @@ class SongHistory extends \App\Doctrine\Entity
$response = new Api\SongHistory;
$response->sh_id = (int)$this->id;
$response->played_at = (int)$this->timestamp_start;
$response->duration = (int)$this->duration;
$response->is_request = (bool)(!empty($this->request_id));
$response->song = $this->song->api();
return $response;

View File

@ -23,9 +23,13 @@ class InternalController extends BaseController
throw new \App\Exception('Not a LiquidSoap station.');
}
$auth_key = $this->getParam('api_auth', '');
if (!$backend_adapter->validateApiPassword($auth_key)) {
throw new \App\Exception\PermissionDenied();
try {
$this->checkStationPermission($station, 'view administration');
} catch (\App\Exception\PermissionDenied $e) {
$auth_key = $this->getParam('api_auth', '');
if (!$backend_adapter->validateApiPassword($auth_key)) {
throw new \App\Exception\PermissionDenied();
}
}
$this->station = $station;
@ -58,10 +62,19 @@ class InternalController extends BaseController
public function nextsongAction()
{
/** @var Entity\Repository\StationMediaRepository $media_repo */
$media_repo = $this->em->getRepository(Entity\StationMedia::class);
/** @var Entity\Repository\SongHistoryRepository $history_repo */
$history_repo = $this->em->getRepository(Entity\SongHistory::class);
return $this->_return($media_repo->getNextSong($this->station));
/** @var Entity\SongHistory|null $sh */
$sh = $history_repo->getNextSongForStation($this->station);
if ($sh instanceof Entity\SongHistory) {
// 'annotate:type=\"song\",album=\"$ALBUM\",display_desc=\"$FULLSHOWNAME\",liq_start_next=\"2.5\",liq_fade_in=\"3.5\",liq_fade_out=\"3.5\":$SONGPATH'
$song_path = $sh->media->getFullPath();
return $this->_return('annotate:' . implode(',', $sh->media->getAnnotations()) . ':' . $song_path);
} else {
return $this->_return(APP_INCLUDE_ROOT . '/resources/error.mp3');
}
}
protected function _return($output)

View File

@ -39,10 +39,20 @@ class NextSong extends \App\Console\Command\CommandAbstract
return false;
}
/** @var Entity\Repository\StationMediaRepository $media_repo */
$media_repo = $em->getRepository(Entity\StationMedia::class);
/** @var Entity\Repository\SongHistoryRepository $history_repo */
$history_repo = $em->getRepository(Entity\SongHistory::class);
/** @var Entity\SongHistory|null $sh */
$sh = $history_repo->getNextSongForStation($station);
if ($sh instanceof Entity\SongHistory) {
// 'annotate:type=\"song\",album=\"$ALBUM\",display_desc=\"$FULLSHOWNAME\",liq_start_next=\"2.5\",liq_fade_in=\"3.5\",liq_fade_out=\"3.5\":$SONGPATH'
$song_path = $sh->media->getFullPath();
$result = 'annotate:' . implode(',', $sh->media->getAnnotations()) . ':' . $song_path;
} else {
$result = APP_INCLUDE_ROOT . '/resources/error.mp3';
}
$result = $media_repo->getNextSong($station);
$output->write($result);
return true;
}

View File

@ -13,7 +13,7 @@ class RestartRadio extends \App\Console\Command\CommandAbstract
*/
protected function configure()
{
$this->setName('radio:restart')
$this->setName('azuracast:radio:restart')
->setDescription('Restart all radio stations.');
}

View File

@ -124,19 +124,30 @@ class NowPlaying extends SyncAbstract
$offline_sh->song = $song_obj->api();
$np->now_playing = $offline_sh;
$np->playing_next = null;
$np->song_history = $this->history_repo->getHistoryForStation($station);
} else {
// Pull from current NP data if song details haven't changed.
$current_song_hash = Entity\Song::getSongHash($np_raw['current_song']);
if (strcmp($current_song_hash, $np_old['now_playing']['song']['id']) == 0) {
$np->song_history = $np_old['song_history'];
$song_obj = $this->song_repo->find($current_song_hash);
$sh_obj = $this->history_repo->register($song_obj, $station, $np_raw);
$np->song_history = $np_old['song_history'];
$np->playing_next = $np_old['playing_next'];
} else {
// SongHistory registration must ALWAYS come before the history/nextsong calls
// otherwise they will not have up-to-date database info!
$song_obj = $this->song_repo->getOrCreate($np_raw['current_song'], true);
$sh_obj = $this->history_repo->register($song_obj, $station, $np_raw);
$np->song_history = $this->history_repo->getHistoryForStation($station);
$song_obj = $this->song_repo->getOrCreate($np_raw['current_song'], true);
$next_song = $this->history_repo->getNextSongForStation($station);
if ($next_song instanceof Entity\SongHistory) {
$np->playing_next = $next_song->api();
}
}
// Update detailed listener statistics, if they exist for the station
@ -145,7 +156,7 @@ class NowPlaying extends SyncAbstract
}
// Register a new item in song history.
$sh_obj = $this->history_repo->register($song_obj, $station, $np_raw);
$np->now_playing = $sh_obj->api();
}

View File

@ -29,5 +29,5 @@
- name: Restart Radio Stations
become: true
become_user: azuracast
shell: php {{ util_base }}/cli.php radio:restart
shell: php {{ util_base }}/cli.php azuracast:radio:restart

View File

@ -259,7 +259,7 @@
"connected_on": {
"description": "",
"type": "integer",
"example": 1497656552
"example": 1497737400
},
"connected_time": {
"description": "",
@ -283,6 +283,10 @@
"description": "",
"$ref": "#/definitions/SongHistory"
},
"playing_next": {
"description": "",
"$ref": "#/definitions/SongHistory"
},
"listeners": {
"description": "",
"$ref": "#/definitions/NowPlayingListeners"
@ -360,7 +364,7 @@
"played_at": {
"description": "",
"type": "integer",
"example": 1497656552
"example": 1497737400
},
"is_request": {
"description": "",
@ -479,7 +483,7 @@
"timestamp": {
"description": "",
"type": "integer",
"example": 1497656552
"example": 1497737400
}
},
"type": "object"