AzuraCast/src/Entity/Repository/SongHistoryRepository.php

230 lines
7.6 KiB
PHP

<?php
namespace App\Entity\Repository;
use App\ApiUtilities;
use App\Doctrine\Repository;
use App\Entity;
use App\Settings;
use Carbon\CarbonInterface;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\UriInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Serializer;
class SongHistoryRepository extends Repository
{
protected ListenerRepository $listenerRepository;
protected StationQueueRepository $stationQueueRepository;
public function __construct(
EntityManagerInterface $em,
Serializer $serializer,
Settings $settings,
LoggerInterface $logger,
ListenerRepository $listenerRepository,
StationQueueRepository $stationQueueRepository
) {
$this->listenerRepository = $listenerRepository;
$this->stationQueueRepository = $stationQueueRepository;
parent::__construct($em, $serializer, $settings, $logger);
}
/**
* @param Entity\Station $station
* @param ApiUtilities $apiUtils
* @param UriInterface|null $baseUrl
*
* @return Entity\Api\SongHistory[]
*/
public function getHistoryApi(
Entity\Station $station,
ApiUtilities $apiUtils,
UriInterface $baseUrl = null
): array {
$num_entries = $station->getApiHistoryItems();
if ($num_entries === 0) {
return [];
}
$history = $this->em->createQuery(/** @lang DQL */ 'SELECT sh
FROM App\Entity\SongHistory sh
LEFT JOIN sh.media sm
WHERE sh.station_id = :station_id
AND sh.timestamp_end != 0
ORDER BY sh.id DESC')
->setParameter('station_id', $station->getId())
->setMaxResults($num_entries)
->execute();
$return = [];
foreach ($history as $sh) {
/** @var Entity\SongHistory $sh */
if ($sh->showInApis()) {
$return[] = $sh->api(new Entity\Api\SongHistory, $apiUtils, $baseUrl);
}
}
return $return;
}
public function getRecentlyPlayed(
Entity\Station $station,
CarbonInterface $now,
int $rows
): array {
$recentlyPlayed = $this->em->createQuery(/** @lang DQL */ 'SELECT sq
FROM App\Entity\StationQueue sq
WHERE sq.station = :station
ORDER BY sq.timestamp_cued DESC')
->setParameter('station', $station)
->setMaxResults($rows)
->getArrayResult();
$recentHistory = $this->em->createQuery(/** @lang DQL */ 'SELECT sh
FROM App\Entity\SongHistory sh
WHERE sh.station = :station
AND (sh.timestamp_start != 0 AND sh.timestamp_start IS NOT NULL)
AND sh.timestamp_start >= :threshold
ORDER BY sh.timestamp_start DESC')
->setParameter('station', $station)
->setParameter('threshold', $now->subDay()->getTimestamp())
->setMaxResults($rows)
->getArrayResult();
$recentlyPlayed = array_merge($recentlyPlayed, $recentHistory);
return array_slice($recentlyPlayed, 0, $rows);
}
public function getRecentlyPlayedByTimeRange(
Entity\Station $station,
CarbonInterface $now,
int $minutes
): array {
$timeRangeInSeconds = $minutes * 60;
$threshold = $now->getTimestamp() - $timeRangeInSeconds;
$recentlyPlayed = $this->em->createQuery(/** @lang DQL */ 'SELECT sq
FROM App\Entity\StationQueue sq
WHERE sq.station = :station
AND sq.timestamp_cued >= :threshold
ORDER BY sq.timestamp_cued DESC')
->setParameter('station', $station)
->setParameter('threshold', $threshold)
->getArrayResult();
$recentHistory = $this->em->createQuery(/** @lang DQL */ 'SELECT sh
FROM App\Entity\SongHistory sh
WHERE sh.station = :station
AND (sh.timestamp_start != 0 AND sh.timestamp_start IS NOT NULL)
AND sh.timestamp_start >= :threshold
ORDER BY sh.timestamp_start DESC')
->setParameter('station', $station)
->setParameter('threshold', $threshold)
->getArrayResult();
return array_merge($recentlyPlayed, $recentHistory);
}
public function register(
Entity\Song $song,
Entity\Station $station,
Entity\Api\NowPlaying $np
): Entity\SongHistory {
// Pull the most recent history item for this station.
$last_sh = $this->getCurrent($station);
$listeners = (int)$np->listeners->current;
if ($last_sh instanceof Entity\SongHistory) {
if ($last_sh->getSongId() === $song->getSongId()) {
// Updating the existing SongHistory item with a new data point.
$last_sh->addDeltaPoint($listeners);
$this->em->persist($last_sh);
$this->em->flush();
return $last_sh;
}
// Wrapping up processing on the previous SongHistory item (if present).
$last_sh->setTimestampEnd(time());
$last_sh->setListenersEnd($listeners);
// Calculate "delta" data for previous item, based on all data points.
$last_sh->addDeltaPoint($listeners);
$delta_points = (array)$last_sh->getDeltaPoints();
$delta_positive = 0;
$delta_negative = 0;
$delta_total = 0;
for ($i = 1, $iMax = count($delta_points); $i < $iMax; $i++) {
$current_delta = $delta_points[$i];
$previous_delta = $delta_points[$i - 1];
$delta_delta = $current_delta - $previous_delta;
$delta_total += $delta_delta;
if ($delta_delta > 0) {
$delta_positive += $delta_delta;
} elseif ($delta_delta < 0) {
$delta_negative += abs($delta_delta);
}
}
$last_sh->setDeltaPositive($delta_positive);
$last_sh->setDeltaNegative($delta_negative);
$last_sh->setDeltaTotal($delta_total);
$last_sh->setUniqueListeners($this->listenerRepository->getUniqueListeners($station,
$last_sh->getTimestampStart(),
time()));
$this->em->persist($last_sh);
}
// Look for an already cued but unplayed song.
$sq = $this->stationQueueRepository->getUpcomingFromSong($station, $song);
if ($sq instanceof Entity\StationQueue) {
$sh = Entity\SongHistory::fromQueue($sq);
$this->em->remove($sq);
} else {
// Processing a new SongHistory item.
$sh = new Entity\SongHistory($station, $song);
$currentStreamer = $station->getCurrentStreamer();
if ($currentStreamer instanceof Entity\StationStreamer) {
$sh->setStreamer($currentStreamer);
}
}
$sh->setTimestampStart(time());
$sh->setListenersStart($listeners);
$sh->addDeltaPoint($listeners);
$this->em->persist($sh);
$this->em->flush();
return $sh;
}
public function getCurrent(Entity\Station $station): ?Entity\SongHistory
{
return $this->em->createQuery(/** @lang DQL */ 'SELECT sh
FROM App\Entity\SongHistory sh
WHERE sh.station = :station
AND sh.timestamp_start != 0
AND (sh.timestamp_end IS NULL OR sh.timestamp_end = 0)
ORDER BY sh.timestamp_start DESC')
->setParameter('station', $station)
->setMaxResults(1)
->getOneOrNullResult();
}
}