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:
parent
4f682475be
commit
10b03cf218
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
40
app/models/Migration/Version20170618013019.php
Normal file
40
app/models/Migration/Version20170618013019.php
Normal 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');
|
||||
}
|
||||
}
|
|
@ -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')
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.');
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue
Block a user