src/Controller/StationController.php line 440

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Event;
  4. use App\Entity\EventTag;
  5. use App\Entity\History;
  6. use App\Entity\Song;
  7. use App\Entity\SongStation;
  8. use App\Entity\Station;
  9. use App\Form\StationType;
  10. use App\Lib\AzuraCast;
  11. use App\Repository\HistoryRepository;
  12. use App\Repository\SongRepository;
  13. use AzuraCast\Api\Dto\MediaFileDto;
  14. use AzuraCast\Api\Dto\NowPlayingDto;
  15. use AzuraCast\Api\Dto\SongHistoryDto;
  16. use AzuraCast\Api\Dto\UploadFileDto;
  17. use AzuraCast\Api\Exception\AccessDeniedException;
  18. use AzuraCast\Api\Exception\ClientRequestException;
  19. use Doctrine\ORM\EntityManagerInterface;
  20. use Doctrine\ORM\NonUniqueResultException;
  21. use Exception;
  22. use Psr\Log\LoggerInterface;
  23. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
  24. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  25. use Symfony\Component\Cache\Adapter\FilesystemAdapter;
  26. use Symfony\Component\Form\FormInterface;
  27. use Symfony\Component\HttpFoundation\JsonResponse;
  28. use Symfony\Component\HttpFoundation\Request;
  29. use Symfony\Component\HttpFoundation\Response;
  30. use Symfony\Component\Routing\Annotation\Route;
  31. use function GuzzleHttp\Promise\all;
  32. use Symfony\Component\HttpKernel\KernelInterface;
  33. use Symfony\Bundle\FrameworkBundle\Console\Application;
  34. use Symfony\Component\Console\Input\ArrayInput;
  35. use Symfony\Component\Console\Output\BufferedOutput;
  36. /**
  37. * @Route("/station")
  38. */
  39. class StationController extends AbstractController
  40. {
  41. /**
  42. * @var LoggerInterface
  43. */
  44. private $logger;
  45. public function __construct(LoggerInterface $nextSongLogger)
  46. {
  47. $this->logger = $nextSongLogger;
  48. }
  49. /**
  50. * @Route("/action", name="station_action")
  51. * @param Request $request
  52. * @param EntityManagerInterface $em
  53. * @param AzuraCast $azuraCast
  54. * @return JsonResponse
  55. */
  56. public function action(Request $request, EntityManagerInterface $em, AzuraCast $azuraCast, KernelInterface $kernel)
  57. {
  58. $stationRepository = $em->getRepository(Station::class);
  59. $stationId = (int) $request->get('stationId', null);
  60. /** @var Station $station */
  61. $station = $stationRepository->findOneBy(['remoteId' => $stationId]);
  62. if (NULL === $station) {
  63. return $this->json(['success' => false, 'message' => 'Can\'t find station ' . $stationId], Response::HTTP_NOT_FOUND);
  64. }
  65. $this->initAllStations($kernel);
  66. try {
  67. $stationDto = $azuraCast->station($station->getId());
  68. $action = $request->get('action', 'restart');
  69. if ($action === 'restart') {
  70. $stationDto->restart();
  71. } else {
  72. $stationDto->frontend($action);
  73. $stationDto->backend($action);
  74. }
  75. } catch (AccessDeniedException $e) {
  76. return $this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_UNAUTHORIZED);
  77. } catch (ClientRequestException $e) {
  78. return $this->json(['success' => false, 'message' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
  79. }
  80. return $this->json(['success' => true, 'stationId' => $stationId]);
  81. }
  82. /**
  83. * @Route("/restart-all", name="station_restartall")
  84. * @param Request $request
  85. * @param EntityManagerInterface $em
  86. * @param AzuraCast $azuraCast
  87. * @return JsonResponse
  88. */
  89. public function restartAll(Request $request, EntityManagerInterface $entityManager, AzuraCast $azuraCast)
  90. {
  91. $stationRepository = $entityManager->getRepository(Station::class);
  92. $stations = $stationRepository->findAll();
  93. $force = $request->get('force', 'no') === "yes";
  94. /** @var Station $station */
  95. foreach ($stations as $station) {
  96. $stationDto = $azuraCast->station($station->getId());
  97. $nowPlayingData = $stationDto->nowPlaying();
  98. if (!$force && $stationDto->status()->getFrontendRunning() && $stationDto->status()->getBackendRunning()) {
  99. $songName = $nowPlayingData->getCurrentSong()->getSong()->getText();
  100. if (strstr($songName, '.mp3')) {
  101. dump(sprintf('Station %s is currently playing %s', $station->getName(), $songName));
  102. continue;
  103. }
  104. }
  105. dump(sprintf('=== >>> Restart %s', $station->getName()));
  106. $stationDto->restart();
  107. }
  108. exit;
  109. }
  110. private function initAllStations(KernelInterface $kernel)
  111. {
  112. $application = new Application($kernel);
  113. $application->setAutoExit(false);
  114. $input = new ArrayInput([
  115. 'command' => 'initAllStations'
  116. ]);
  117. $output = new BufferedOutput();
  118. $application->run($input, $output);
  119. $initResponse = $output->fetch();
  120. }
  121. /**
  122. * @Route("/edit", name="station_edit")
  123. * @param Request $request
  124. * @param EntityManagerInterface $entityManager
  125. * @param AzuraCast $azuraCast
  126. * @return JsonResponse
  127. * @throws AccessDeniedException
  128. * @throws ClientRequestException
  129. */
  130. public function edit(Request $request, EntityManagerInterface $entityManager, AzuraCast $azuraCast, KernelInterface $kernel)
  131. {
  132. $stationRemoteId = $request->get('stationRemoteId', null);
  133. if (NULL === $stationRemoteId) {
  134. $station = new Station();
  135. } else {
  136. $stationRepository = $entityManager->getRepository(Station::class);
  137. /** @var Station $station */
  138. $station = $stationRepository->findOneBy(['remoteId' => $stationRemoteId]);
  139. if (NULL === $station) {
  140. return $this->json(['success' => false, 'message' => 'Can\'t find station.'], Response::HTTP_NOT_FOUND);
  141. }
  142. }
  143. if ($request->getMethod() === 'DELETE') {
  144. return $this->deleteStation($station, $entityManager, $azuraCast);
  145. }
  146. $form = $this->createForm(StationType::class, $station, [
  147. 'action' => $this->generateUrl('station_edit', ['stationRemoteId' => $stationRemoteId])
  148. ]);
  149. if ($request->getMethod() === 'POST' && $request->get('station') !== NULL) {
  150. return $this->editStation($station, $form, $request, $entityManager, $azuraCast, $kernel);
  151. }
  152. return $this->json([
  153. 'success' => true,
  154. 'view' => $this->renderView('station/edit.html.twig', [
  155. 'form' => $form->createView(),
  156. 'station' => $station
  157. ])
  158. ]);
  159. }
  160. /**
  161. * @param Station $station
  162. * @param FormInterface $form
  163. * @param Request $request
  164. * @param EntityManagerInterface $entityManager
  165. * @param AzuraCast $azuraCast
  166. * @return JsonResponse
  167. * @throws AccessDeniedException
  168. * @throws ClientRequestException
  169. */
  170. private function editStation(Station $station, FormInterface $form, Request $request, EntityManagerInterface $entityManager, AzuraCast $azuraCast, KernelInterface $kernel)
  171. {
  172. $form->handleRequest($request);
  173. if ($form->isSubmitted() && $form->isValid()) {
  174. // Handle file uploads
  175. $logoFile = $form['channelLogo']->getData();
  176. $videoFile = $form['channelVideo']->getData();
  177. if ($logoFile) {
  178. $logoDir = $this->getParameter('kernel.project_dir') . '/public/uploads/stations/logo/';
  179. if (!is_dir($logoDir)) {
  180. mkdir($logoDir, 0777, true);
  181. }
  182. $logoFilename = uniqid('logo_') . '.' . $logoFile->guessExtension();
  183. $logoFile->move($logoDir, $logoFilename);
  184. $station->setChannelLogo('uploads/stations/logo/' . $logoFilename);
  185. }
  186. if ($videoFile) {
  187. $videoDir = $this->getParameter('kernel.project_dir') . '/public/uploads/stations/video/';
  188. if (!is_dir($videoDir)) {
  189. mkdir($videoDir, 0777, true);
  190. }
  191. $videoFilename = uniqid('video_') . '.' . $videoFile->guessExtension();
  192. $videoFile->move($videoDir, $videoFilename);
  193. $station->setChannelVideo('uploads/stations/video/' . $videoFilename);
  194. }
  195. $stationRepository = $entityManager->getRepository(Station::class);
  196. $exists = $stationRepository->findOneBy(['name' => $station->getName()]);
  197. $new = $station->getId() === null;
  198. if (!empty($exists) && $exists !== $station) {
  199. $error = 'A station with the same name already exists';
  200. return $this->json(['success' => false, 'errors' => $error]);
  201. } elseif ($station->getId() === null) {
  202. $data = $azuraCast->createStation($station);
  203. if ((int) @$data['id'] <= 0) {
  204. return $this->json(['success' => false, 'errors' => 'Can \'t create station']);
  205. }
  206. } else {
  207. $stationDto = $azuraCast->station($station->getId());
  208. $stationObject = $stationDto->get();
  209. $currentName = $stationObject->getName();
  210. $changeName = $currentName != $station->getName();
  211. $currentDescription = $stationObject->getDescription();
  212. $changeDescription = $currentDescription != $station->getDescription();
  213. if ($changeName || $changeDescription) {
  214. $this->logger->info(sprintf('Updating station %s: name change %s, description change %s', $station->getId(), var_export($changeName, true), var_export($changeDescription, true)));
  215. $data = $azuraCast->admin($station->getServer())
  216. ->request('PUT', '/api/admin/station/' . $station->getRemoteId(), [
  217. 'json' => [
  218. 'name' => $station->getName(),
  219. 'description' => $station->getDescription()
  220. ]
  221. ]);
  222. }
  223. }
  224. $entityManager->persist($station);
  225. $entityManager->flush();
  226. if ($new) {
  227. if ($station->getListenUrl() === 'temp') {
  228. $azuraCast->resetStations();
  229. }
  230. sleep(2);
  231. $this->initAllStations($kernel);
  232. }
  233. return $this->json([
  234. 'success' => true
  235. ]);
  236. } else {
  237. return $this->json(['success' => false, 'errors' => $form->getErrors()], Response::HTTP_BAD_REQUEST);
  238. }
  239. }
  240. private function deleteStation(Station $station, EntityManagerInterface $entityManager, AzuraCast $azuraCast)
  241. {
  242. if (NULL === $station->getId()) {
  243. return $this->json(['success' => false, 'message' => 'Can\'t find station.'], Response::HTTP_NOT_FOUND);
  244. }
  245. try {
  246. $data = $azuraCast->admin($station->getServer())
  247. ->request('DELETE', '/api/admin/station/' . $station->getRemoteId());
  248. } catch (Exception $e) {
  249. if (!strstr($e->getMessage(), 'Record not found')) {
  250. throw $e;
  251. }
  252. }
  253. foreach ($station->getEvents() as $event) {
  254. $station->removeEvent($event);
  255. }
  256. $entityManager->remove($station);
  257. $entityManager->flush();
  258. return $this->json(['success' => true]);
  259. }
  260. /**
  261. * @Route("/history/{stationRemoteId}", name="station_history")
  262. * @Entity("station", expr="repository.findOneByRemoteId(stationRemoteId)")
  263. * @param Station $station
  264. * @param AzuraCast $azuraCast
  265. * @return Response
  266. * @throws AccessDeniedException
  267. * @throws ClientRequestException
  268. */
  269. public function history(Station $station, AzuraCast $azuraCast)
  270. {
  271. $end = new \DateTime();
  272. $start = clone $end;
  273. //$start->modify('+1 hour');
  274. $history = array_slice($azuraCast->station($station)->history($start, $end), 0, 500);
  275. return $this->json([
  276. 'success' => true,
  277. 'view' => $this->renderView('station/history.html.twig', ['history' => $history])
  278. ]);
  279. }
  280. /**
  281. * @Route("/webhook/{stationRemoteId}", name="station_webhook")
  282. * @Entity("station", expr="repository.findOneByRemoteId(stationRemoteId)")
  283. * @param Request $request
  284. * @param Station $station
  285. * @param AzuraCast $azuraCast
  286. * @param EntityManagerInterface $entityManager
  287. * @return Response
  288. * @throws AccessDeniedException
  289. * @throws ClientRequestException
  290. */
  291. public function webhook(Request $request, Station $station, AzuraCast $azuraCast, EntityManagerInterface $entityManager)
  292. {
  293. file_put_contents(
  294. '/code/var/log/webhook.log',
  295. sprintf(
  296. '[%s] %s: %s',
  297. date('Y-m-d H:i:s'),
  298. $station->getName(),
  299. json_encode($_REQUEST)
  300. ) . "\n",
  301. FILE_APPEND | LOCK_EX
  302. );
  303. $nowPlaying = $azuraCast->station($station)->nowPlaying();
  304. $eventTagRepository = $entityManager->getRepository(EventTag::class);
  305. $eventTag = $eventTagRepository->findByRemoteNameFromStation($station, $nowPlaying->getCurrentSong()->getPlaylist());
  306. dump($eventTag);
  307. return $this->json([
  308. 'success' => true,
  309. 'request' => var_export($request->query->all(), true),
  310. 'station' => $station->getId(),
  311. 'nowPlaying' => $nowPlaying
  312. ]);
  313. }
  314. /**
  315. * @Route("/{stationRemoteId}/nowPlaying", name="station_nowPlaying")
  316. *
  317. * @param Request $request
  318. * @param AzuraCast $azuraCast
  319. * @param EntityManagerInterface $entityManager
  320. * @param int $stationRemoteId
  321. * @return Response
  322. */
  323. public function nowPlaying(Request $request, AzuraCast $azuraCast, EntityManagerInterface $entityManager, int $stationRemoteId)
  324. {
  325. $stationRepository = $entityManager->getRepository(Station::class);
  326. /** @var Station $station */
  327. $station = $stationRepository->findOneBy(['remoteId' => $stationRemoteId]);
  328. if (!$station instanceof Station) {
  329. return $this->json(['text' => 'Invalid Station - Please try again', 'error' => true]);
  330. }
  331. $data = $azuraCast->station($station->getId())->nowPlaying();
  332. return $this->json(['text' => $data->getCurrentSong()->getSong()->getText(), 'error' => false, 'for' => $stationRemoteId]);
  333. }
  334. /**
  335. * @Route("/nowPlaying", name="station_nowPlaying_all")
  336. *
  337. * @param AzuraCast $azuraCast
  338. * @return Response
  339. */
  340. public function nowPlayingAll(AzuraCast $azuraCast)
  341. {
  342. $nowPlaying = array_map(function ($station) {
  343. if (gettype($station) === 'array') {
  344. return [$station['station']['id'], 'Stream Offline'];
  345. }
  346. return [$station->getStation()->getId(), $station->getCurrentSong()->getSong()->getText()];
  347. }, $azuraCast->allStationsNowPlaying());
  348. return $this->json($nowPlaying);
  349. }
  350. /**
  351. * @Route("/nextsong", name="station_nextsong")
  352. *
  353. * @param Request $request
  354. * @param AzuraCast $azuraCast
  355. * @param EntityManagerInterface $entityManager
  356. * @return Response
  357. */
  358. public function nextsong(Request $request, AzuraCast $azuraCast, EntityManagerInterface $entityManager)
  359. {
  360. $stationRepository = $entityManager->getRepository(Station::class);
  361. $stationRemoteId = $request->get('station');
  362. /** @var Station $station */
  363. $station = $stationRepository->findOneBy(['remoteId' => $stationRemoteId]);
  364. set_time_limit(20);
  365. if (empty($station)) {
  366. return new Response(sprintf('annotate:title="No music",artist="Unconfigured Station (%s)",duration="30.",song_id="0",media_id="0",playlist_id="0":/usr/local/share/icecast/web/error.mp3', $stationRemoteId), Response::HTTP_OK, ['content-type' => 'text/plain']);
  367. }
  368. ini_set('memory_limit', '2G');
  369. $file = $this->nextEventTag($request, $azuraCast, $entityManager, $station);
  370. if (empty($file)) {
  371. return new Response('annotate:title="No music",artist="Schedule Empty - ' . $stationRemoteId . '/' . $station->getId() . '",duration="30.",song_id="0",media_id="0",playlist_id="0":/usr/local/share/icecast/web/error.mp3', Response::HTTP_OK, ['content-type' => 'text/plain']);
  372. //list($file, $playlistId) = $this->randomSongForStation($azuraCast, $station);
  373. // $content = sprintf(
  374. // 'annotate:title="No music",artist="Schedule Empty",duration="3.",song_id="0",media_id="0",playlist_id="0":%s',
  375. // '/var/azuracast/media/error.mp3'
  376. // );
  377. } else {
  378. $playlistId = $file[1];
  379. $file = $file[0];
  380. }
  381. $rootPath =
  382. $request->query->get('public') === "yes"
  383. ? sprintf(
  384. 'https://%s/songs/',
  385. $request->getHost()
  386. )
  387. : '/var/azuracast/media/';
  388. $content = sprintf(
  389. 'annotate:title="%s",artist="%s",duration="%s.",song_id="%s",media_id="%s",playlist_id="%s":%s%s',
  390. $file->getTitle(),
  391. $file->getArtist(),
  392. (int) $file->getLength(),
  393. $file->getSongId(),
  394. $file->getId(),
  395. $playlistId,
  396. $rootPath,
  397. $file->getPath()
  398. );
  399. return new Response($content, Response::HTTP_OK, ['content-type' => 'text/plain']);
  400. }
  401. private function getFileInfo(AzuraCast $azuraCast, Station $station, Request $request, SongStation $songToStation)
  402. {
  403. try {
  404. return $azuraCast->station($station)->media()->request('GET', '/api/station/' . $station->getRemoteId() . '/file/' . $songToStation->getMediaId());
  405. } catch (Exception $e) {
  406. $this->debug($request, sprintf('Cannot get file info %s', $e->getMessage()));
  407. }
  408. return NULL;
  409. }
  410. /**
  411. * @param Request $request
  412. * @param AzuraCast $azuraCast
  413. * @param EntityManagerInterface $entityManager
  414. * @param Station $station
  415. * @param int|null $allowedFrequency
  416. * @param int|null $allowedTime
  417. * @param int|null $testId
  418. * @return array|null |null
  419. */
  420. private function nextEventTag(Request $request, AzuraCast $azuraCast, EntityManagerInterface $entityManager, Station $station, ?int $allowedFrequency = NULL, ?int $allowedTime = NULL, ?int $testId = NULL)
  421. {
  422. ini_set('date.timezone', 'Europe/Bucharest');
  423. if ($allowedFrequency === NULL) {
  424. $allowedFrequency = [0, (int) $request->get('allowedFrequency', date('w'))];
  425. } else {
  426. $allowedFrequency = [0, $allowedFrequency];
  427. }
  428. if ($allowedTime === NULL) {
  429. $allowedTime = (int) $request->get('allowedTime', date('H'));
  430. }
  431. if ($request->get('dev') === 'yes' && $request->get('testId') != 'null') {
  432. $testId = (int) $request->get('testId');
  433. }
  434. $this->debug($request, sprintf('Find event for %s [Freq %s/%s]', $station->getName(), $allowedFrequency[1], $allowedTime));
  435. /** @var HistoryRepository $historyRepository */
  436. $historyRepository = $entityManager->getRepository(History::class);
  437. /** @var SongRepository $songRepository */
  438. $songsRepository = $entityManager->getRepository(Song::class);
  439. /** @var History $lastPlayed */
  440. $lastPlayed = $historyRepository->getLastEventForStation($station);
  441. /** @var EventTag $lastEventTag */
  442. $lastEventTag = $lastPlayed === NULL ? NULL : $lastPlayed->getEventTag();
  443. $this->debug($request, sprintf('Last event tag %s', $lastEventTag ? $lastEventTag->getEvent()->getName() : 'NONE'));
  444. /** @var HistoryRepository $historyRepository */
  445. $historyRepository = $entityManager->getRepository(History::class);
  446. $currentEvent = NULL;
  447. foreach ($station->getEventsInOrder() as $event) {
  448. if (!in_array($event->getFrequency(), $allowedFrequency)) {
  449. continue; //just events that play every day and this day of the week will be taken into account
  450. }
  451. $time = (int) $event->getTime();
  452. if ($time !== 0 && $time > $allowedTime) {
  453. continue; //this event is for later
  454. }
  455. if ($event->getType() === Event::TYPE_PLAY) {
  456. $plays = $historyRepository->countEventToday($station, $event, $testId);
  457. if ($plays < $event->getEventTags()->count()) {
  458. $currentEvent = $event;
  459. } else {
  460. $this->debug($request, sprintf('Skip play %s as it already played %s', $event->getName(), $plays));
  461. }
  462. } else {
  463. $currentEvent = $event;
  464. }
  465. }
  466. if (empty($currentEvent)) { //Get the next correct event
  467. $events = $station->getEvents()->toArray();
  468. foreach ($events as $event) {
  469. if ($event->getType() === Event::TYPE_PLAY) {
  470. if ($event->getTime() > $allowedTime) {
  471. $this->debug($request, sprintf('Skip event %s', $event->getName()));
  472. continue; //This play event is for later
  473. }
  474. $plays = $entityManager->getRepository(History::class)->countEventToday($station, $event, $testId);
  475. if ($plays < $event->getEventTags()->count()) {
  476. $currentEvent = $event;
  477. }
  478. }
  479. }
  480. if (empty($currentEvent)) {
  481. $first = $station->getFirstEventInADay();
  482. $last = $station->getLastEventInADay();
  483. if (!empty($first) && !empty($last)) {
  484. $currentEvent = $first->getTimeInt() > $allowedTime ? $last : $first;
  485. }
  486. }
  487. }
  488. if (empty($currentEvent)) { //If there are no events.. there are no events
  489. $this->debug($request, sprintf('No events for station'));
  490. return NULL;
  491. }
  492. $eventTagToPlay = NULL;
  493. $next = false;
  494. foreach ($currentEvent->getEventTags() as $eventTag) {
  495. if (empty($eventTag->getTag())) continue;
  496. $tag = $eventTag->getTag();
  497. if (count($tag->getSongs()->toArray()) < 1) {
  498. $this->debug($request, sprintf('No songs available on %s -> %s', $station->getName(), $tag->getName()));
  499. continue;
  500. }
  501. if ($eventTag === $lastEventTag) {
  502. $next = true;
  503. $this->debug($request, sprintf('Current eventTag %s', $eventTag->getEvent()->getName()));
  504. } elseif ($next || empty($lastEventTag)) {
  505. $eventTagToPlay = $eventTag;
  506. $this->debug($request, sprintf('Next eventTag %s', $eventTag->getEvent()->getName()));
  507. break;
  508. }
  509. }
  510. $songToPlay = NULL;
  511. if (empty($eventTagToPlay)) {
  512. /** @var EventTag $eventTagToPlay */
  513. $eventTagToPlay = $currentEvent->getEventTags()->first();
  514. $this->debug($request, sprintf(
  515. 'Empty eventTag => eventTag %s/%s',
  516. $eventTagToPlay->getEvent()->getName(),
  517. $eventTagToPlay->getTag() ?? 'deleted tag'
  518. ));
  519. } else {
  520. $this->debug($request, sprintf(
  521. 'Event tag to play %s/%s',
  522. $eventTagToPlay->getEvent()->getName(),
  523. $eventTagToPlay->getTag() ?? 'deleted tag'
  524. ));
  525. }
  526. if (!empty($eventTagToPlay)) {
  527. $tag = $eventTagToPlay->getTag();
  528. $songs = $tag->getSongs()->toArray();
  529. #$eventTagToPlay = $entityManager->getRepository(EventTag::class)->find(49);
  530. $songToPlay = $this->getNextBestSong($songsRepository, $request, $station, $eventTagToPlay, $testId);
  531. $this->debug($request, sprintf('Songs available %s', count($songs)));
  532. if ($testId !== NULL) { //We do not need to sync
  533. $history = new History();
  534. $history->setEventTag($eventTagToPlay);
  535. $history->setSong($songToPlay);
  536. $history->setTestId($testId ?? 0);
  537. $entityManager->persist($history);
  538. $entityManager->flush();
  539. $text = $path = $title = $songToPlay->getName();
  540. $tagName = $eventTagToPlay->getTag()->getName();
  541. $artist = implode('/', array_map(function ($element) use ($tagName) {
  542. return $tagName === $element[1] ? ('<strong style="color: brown">' . $element[1] . '</strong>') : $element[1];
  543. }, $songToPlay->getTagJson()));
  544. $file = [
  545. "id" => 10922,
  546. "album" => null,
  547. "lyrics" => null,
  548. "isrc" => null,
  549. "length" => $songToPlay->getLength(),
  550. "length_text" => $songToPlay->getPlaytime(),
  551. "path" => $path . ".mp3",
  552. "mtime" => 1603730441,
  553. "art_updated_at" => 0,
  554. "playlists" => [],
  555. "unique_id" => "f61b1f9930b6db4ab54135f8",
  556. "song_id" => "268bfa008999e6b0e94b3e4962a8226f",
  557. "text" => $text,
  558. "artist" => $artist,
  559. "custom_fields" => [],
  560. "title" => $title
  561. ];
  562. $mediaFileDto = MediaFileDto::fromArray($file);
  563. return [$mediaFileDto, !empty($eventTagToPlay) ? $eventTagToPlay->getId() : '-'];
  564. }
  565. } else {
  566. $this->debug($request, sprintf('No event tag'));
  567. return NULL;
  568. }
  569. if (empty($songToPlay)) {
  570. $this->debug($request, sprintf('No song to play'));
  571. return NULL;
  572. }
  573. $songStationRepository = $entityManager->getRepository(SongStation::class);
  574. $tries = 0;
  575. $historyTry = 5;
  576. $songToStation = NULL;
  577. while (empty($songToStation) && $tries < $historyTry) {
  578. $tries++;
  579. /** @var SongStation $songToStation */
  580. $songToStation = $songStationRepository->findOneBy(['song' => $songToPlay, 'station' => $station]);
  581. if (!empty($songToStation)) {
  582. $file = $this->getFileInfo($azuraCast, $station, $request, $songToStation);
  583. if ($file === NULL) {
  584. $songToStation = NULL;
  585. } else {
  586. if ($file['path'] !== $songToPlay->getNameWithExtension()) {
  587. $this->debug($request, sprintf(
  588. 'Invalid song to station link. %s (RefId: %s) to %s',
  589. $songToPlay->getNameWithExtension(),
  590. $songToPlay->getId(),
  591. var_export($file, true)
  592. ));
  593. $station->removeSongStation($songToStation);
  594. $entityManager->persist($station);
  595. $entityManager->remove($songToStation);
  596. $entityManager->flush();
  597. $file = NULL;
  598. $songToStation = NULL;
  599. }
  600. }
  601. }
  602. if (empty($songToStation)) { //Sync song to station
  603. ini_set('memory_limit', '2g');
  604. $this->debug($request, sprintf('Sync song to station %s', $songToPlay->getNameWithExtension()));
  605. $uploadFile = new UploadFileDto($songToPlay->getNameWithExtension(), file_get_contents($songToPlay->getFullPath()));
  606. try {
  607. $response = $azuraCast->station($station)->media()->upload($uploadFile);
  608. $songToStation = new SongStation();
  609. $songToStation->setStation($station);
  610. $songToStation->setSong($songToPlay);
  611. $songToStation->setMediaId($response->getId());
  612. $entityManager->persist($songToStation);
  613. $entityManager->flush();
  614. } catch (Exception $e) {
  615. $this->debug($request, sprintf('Unable to sync song %s: %s', $songToPlay->getNameWithExtension(), $e->getMessage()));
  616. $songs = array_filter($songs, function ($song) use ($songToPlay) { //Remove song from list as it has issues
  617. return $song->getId() !== $songToPlay->getId();
  618. });
  619. $songToStation = NULL;
  620. }
  621. } else {
  622. $this->debug($request, sprintf('Song already synced %s', $songToStation->getSong()->getName()));
  623. }
  624. if (empty($songToStation)) {
  625. /** @var Song $songToPlay */
  626. $songToPlay = $this->getNextBestSong($songsRepository, $request, $station, $eventTagToPlay, $testId);
  627. } else {
  628. $file = $this->getFileInfo($azuraCast, $station, $request, $songToStation);
  629. if ($file === NULL) {
  630. $songToStation = NULL;
  631. }
  632. }
  633. }
  634. if (empty($songToStation)) { //There is an error syncing this media => will try and use another song
  635. $this->debug($request, sprintf('Error sync media'));
  636. return NULL;
  637. }
  638. $history = new History();
  639. $history->setEventTag($eventTagToPlay);
  640. $history->setSong($songToPlay);
  641. $history->setTestId($testId ?? 0);
  642. $entityManager->persist($history);
  643. $entityManager->flush();
  644. $songTags = array_map(function ($element) {
  645. return $element[1];
  646. }, $songToPlay->getTagJson());
  647. $this->debug($request, sprintf(
  648. 'Will play [%s] with tags [%s] - current tag [%s]',
  649. $songToPlay->getName(),
  650. implode('/', $songTags),
  651. $eventTagToPlay
  652. ));
  653. $file['title'] .= ' ||| ' . $eventTagToPlay . ' ||| ' . implode('/', $songTags) . ' ||| ' . $file['path'];
  654. $mediaFileDto = MediaFileDto::fromArray($file);
  655. return [$mediaFileDto, !empty($eventTagToPlay) ? $eventTagToPlay->getRemoteId() : '-'];
  656. }
  657. private function getNextBestSong(SongRepository $songRepo, Request $request, Station $station, EventTag $eventTagToPlay, ?int $testId = NULL)
  658. {
  659. while (true) {
  660. $songs = $songRepo->getSongsHistoryForEventTag($eventTagToPlay);
  661. $maxSongs = count($songs);
  662. $top10Pgr = intval(round(0.1 * $maxSongs, 0));
  663. if ($top10Pgr < 1 && $maxSongs > 0) {
  664. $top10Pgr = 1;
  665. }
  666. $top10PgrSongs = array_slice($songs, 0, $top10Pgr);
  667. $this->debug($request, sprintf('YEY available %s %s', count($songs), count($top10PgrSongs)));
  668. if (empty($top10PgrSongs)) {
  669. return NULL;
  670. }
  671. $song = $top10PgrSongs[array_rand($top10PgrSongs)];
  672. if (file_exists($song->getFullPath())) {
  673. return $song;
  674. }
  675. //mark song as deleted
  676. $song->setDeleted(true);
  677. $em = $this->getDoctrine()->getManager();
  678. $em->persist($song);
  679. $em->flush();
  680. if (empty($top10PgrSongs)) {
  681. return NULL;
  682. }
  683. }
  684. }
  685. private function debug(Request $request, string $message)
  686. {
  687. if ($request->get('dev') === 'yes') {
  688. dump($message);
  689. }
  690. $function = isset(debug_backtrace()[1]) ? debug_backtrace()[1]['function'] : 'undefined';
  691. $this->logger->debug(sprintf(
  692. 'STATION%s - %s',
  693. $request->get('station') ?? '?',
  694. $message
  695. ), [get_called_class(), $function]);
  696. }
  697. /**
  698. * @Route("/{stationRemoteId}/preview", name="station_preview")
  699. * @Entity("station", expr="repository.findOneByRemoteId(stationRemoteId)")
  700. * @param Request $request
  701. * @param Station $station
  702. * @param AzuraCast $azuraCast
  703. * @param EntityManagerInterface $entityManager
  704. * @return Response
  705. * @throws AccessDeniedException
  706. */
  707. public function preview(Request $request, Station $station, AzuraCast $azuraCast, EntityManagerInterface $entityManager)
  708. {
  709. ini_set('date.timezone', 'Europe/Bucharest');
  710. $eventTagRepository = $entityManager->getRepository(EventTag::class);
  711. $historyRepository = $entityManager->getRepository(History::class);
  712. $today = (int) date('d');
  713. $timestamp = strtotime('today midnight');
  714. $testId = rand(1, 99999999);
  715. set_time_limit(120);
  716. $return = [];
  717. $total = 0;
  718. while ((int) date('d', $timestamp) === $today) {
  719. $total++;
  720. $file = $this->nextEventTag(
  721. $request,
  722. $azuraCast,
  723. $entityManager,
  724. $station,
  725. (int) date('w', $timestamp),
  726. (int) date('H', $timestamp),
  727. $testId
  728. );
  729. if ($file === NULL) {
  730. throw new \Exception('No songs available');
  731. //list($song, $eventTag) = $this->randomSongForStation($azuraCast, $station);
  732. } else {
  733. $eventTag = $eventTagRepository->find($file[1]);
  734. /** @var MediaFileDto $song */
  735. $song = $file[0];
  736. }
  737. $event = $eventTag === '-' ? NULL : $eventTag->getEvent();
  738. $tag = $eventTag === '-' ? NULL : $eventTag->getTag();
  739. $return[] = [
  740. 'when' => date('Y-m-d H:i:s', $timestamp),
  741. 'title' => $song->getTitle(),
  742. 'artist' => $song->getArtist(),
  743. 'length' => $song->getLengthText(),
  744. 'event' => $event ? $event->getName() . " - " . $event->getTimeName() : '-',
  745. 'frequency' => $event ? $event->getFrequencyName() : '-',
  746. 'type' => $event ? $event->getTypeName() : '-',
  747. 'tag' => $tag ? ($tag->getName() . ' (' . $tag->getSongs()->count() . ')') : '-'
  748. ];
  749. $timestamp += $song->getLength();
  750. }
  751. foreach ($historyRepository->findBy(['testId' => $testId]) as $history) {
  752. $entityManager->remove($history);
  753. }
  754. $entityManager->flush();
  755. return $this->json([
  756. 'success' => true,
  757. 'view' => $this->renderView('station/preview.html.twig', ['preview' => $return, 'total' => $total, 'station' => $station])
  758. ]);
  759. }
  760. /**
  761. * @param AzuraCast $azuraCast
  762. * @param Station $station
  763. * @return array
  764. * @throws AccessDeniedException
  765. */
  766. private function randomSongForStation(AzuraCast $azuraCast, Station $station)
  767. {
  768. $cacheFS = new FilesystemAdapter('station.randomsong', 60);
  769. $cache = $cacheFS->getItem('media.list.' . $station->getId());
  770. if (!$cache->isHit()) {
  771. try {
  772. $files = $azuraCast->station($station)->media()->list();
  773. } catch (ClientRequestException $e) {
  774. try {
  775. $files = $azuraCast->station($station)->media()->list();
  776. } catch (ClientRequestException $e) {
  777. try {
  778. $files = $azuraCast->station($station)->media()->list();
  779. } catch (ClientRequestException $e) {
  780. $files = NULL;
  781. }
  782. }
  783. }
  784. if ($files === NULL) {
  785. return [];
  786. }
  787. $cache->set($files);
  788. $cacheFS->save($cache);
  789. } else {
  790. $files = $cache->get();
  791. }
  792. $file = $files[array_rand($files)];
  793. $playlistId = '-';
  794. return [$file, $playlistId];
  795. }
  796. }