399 lines
17 KiB
PHP
399 lines
17 KiB
PHP
<?php
|
|
namespace App\Http\Controllers;
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
error_reporting(E_ALL);
|
|
ini_set('display_errors', true);
|
|
|
|
class ProgramController extends Controller
|
|
{
|
|
private static $SCHEDULE_SQL = <<<QUERY
|
|
SELECT `schedule`.`id` AS `scheduleid`, `programs`.`id` AS `id`, `programs`.`longname` AS `name`, `programs`.`tagline` AS `tagline`, `programs`.`description` AS `description`, `programs`.`email`, `schedule`.`priority`,
|
|
`schedule`.`startdate`, `schedule`.`startday`, `schedule`.`starttime`, `schedule`.`enddate`, `schedule`.`endday`, `schedule`.`endtime`,
|
|
`schedule`.`shortnamesuffix` AS `suffix`, `schedule`.`state` AS `state`
|
|
FROM `programs_schedule` AS `schedule` LEFT JOIN `programs` ON `schedule`.`program` = `programs`.`id`
|
|
WHERE ((`schedule`.`startdate` IS NULL) OR (`schedule`.`startdate` <= :enddate)) AND
|
|
((`schedule`.`enddate` IS NULL) OR (`schedule`.`enddate` >= :startdate)) AND
|
|
`schedule`.`active` = 1 AND
|
|
`schedule`.`final` = 1
|
|
ORDER BY `schedule`.`priority` DESC, `schedule`.`startday` ASC, `schedule`.`starttime` ASC
|
|
QUERY;
|
|
|
|
/**
|
|
* Create a new controller instance.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function __construct()
|
|
{
|
|
//
|
|
}
|
|
|
|
public function addToSchedule(&$schedule, $start, $end, $program, $scheduleid) {
|
|
if($start == $end) {
|
|
return;
|
|
}
|
|
|
|
$last = count($schedule) ? $schedule[count($schedule) - 1] : null;
|
|
if($last && ($last['program'] == $program) && ($last['end'] == $start))
|
|
{
|
|
// Merge with the previous entry
|
|
$last['end'] = $end;
|
|
}
|
|
else
|
|
{
|
|
$schedule[] = ['id' => $scheduleid, 'start' => clone $start, 'end' => clone $end, 'program' => new \Model\Program($program)];
|
|
}
|
|
}
|
|
|
|
|
|
public function createSchedule(\DateTimeImmutable $start, \DateTimeImmutable $end, $DEBUG = false) {
|
|
$startDay = $start->format('N');
|
|
$endDay = $end->format('N');
|
|
$schedule = [];
|
|
|
|
if($DEBUG) print "Creating schedule between {$start->format('Y-m-d H:i')} and {$end->format('Y-m-d H:i')}<br/>\n";
|
|
|
|
$START = 1; $END = -1;
|
|
$WEEK = new \DateInterval('P7D'); $DAY = new \DateInterval('P1D');
|
|
$WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" /* == 0 and 7 */];
|
|
$scheduleItems = app('db')->select(self::$SCHEDULE_SQL,
|
|
['enddate' => $end->format('Y-m-d'), 'startdate' => $start->format('Y-m-d')]);
|
|
$scheduleChanges = new \SplPriorityQueue();
|
|
$active = [];
|
|
|
|
foreach($scheduleItems as $item) {
|
|
if($item->startdate) { $item->startdate = new \DateTimeImmutable($item->startdate); }
|
|
if($item->enddate) { $item->enddate = (new \DateTimeImmutable($item->enddate))->add($DAY); }
|
|
|
|
// Find the start time and end time of the last occurrence on or before the start time
|
|
$itemStart = new \DateTimeImmutable("last " . $WEEKDAYS[$item->startday] . " " . $start->format('Y-m-d') . " " . $item->starttime);
|
|
$itemEnd = new \DateTimeImmutable("last " . $WEEKDAYS[$item->endday] . " " . $start->format('Y-m-d') . " " . $item->endtime);
|
|
|
|
if($itemEnd <= $itemStart) {
|
|
$itemEnd = $itemEnd->add($WEEK);
|
|
}
|
|
|
|
// If the first occurrence is completely before the period of interest, look at the next week
|
|
while($itemEnd < $start) {
|
|
$itemStart = $itemStart->add($WEEK);
|
|
$itemEnd = $itemEnd->add($WEEK);
|
|
}
|
|
|
|
if($DEBUG) print "<b>Have a schedule item for {$item->name} {$item->suffix} from {$itemStart->format('Y-m-d H:i')} to {$itemEnd->format('Y-m-d H:i')}</b><br/>\n";
|
|
while($itemStart <= $end) {
|
|
if($DEBUG) {
|
|
print "Considering {$item->name} {$item->suffix} (#$item->scheduleid) at {$itemStart->format('Y-m-d H:i')} to {$itemEnd->format('Y-m-d H:i')}.<br/>\n";
|
|
if($item->startdate) print "-- (Item start is {$item->startdate->format('Y-m-d H:i')}, end is ". ($item->enddate ? $item->enddate->format('Y-m-d H:i') : "never") . ")<br/>\n";
|
|
}
|
|
|
|
if($item->startdate && $itemStart < $item->startdate) {
|
|
if($DEBUG) print "-- Skipping {$item->name} {$item->suffix} (#$item->scheduleid) at {$itemStart->format('Y-m-d H:i')} because it is before the schedule instance start.<br/>\n";
|
|
} else if($item->enddate && $itemStart >= $item->enddate) {
|
|
if($DEBUG) print "-- Skipping {$item->name} {$item->suffix} (#$item->scheduleid) at {$itemStart->format('Y-m-d H:i')} because it is after the schedule instance end.<br/>\n";
|
|
} else {
|
|
// if($DEBUG) print "-- Adding {$item->name} {$item->suffix} (#$item->scheduleid) at {$itemStart->format('Y-m-d H:i')} <br/>\n";
|
|
// Create a list in which all start and end times are separate entries
|
|
$scheduleChanges->insert( [$START, $itemStart, $item], [-$itemStart->getTimestamp(), -$START] );
|
|
$scheduleChanges->insert( [$END, $itemEnd, $item], [-$itemEnd->getTimestamp(), -$END] );
|
|
}
|
|
|
|
// Also keep track of all priorities seen
|
|
$active[$item->priority] = null;
|
|
|
|
$itemStart = $itemStart->add($WEEK);
|
|
$itemEnd = $itemEnd->add($WEEK);
|
|
}
|
|
|
|
}
|
|
|
|
if($DEBUG) {
|
|
print "<h2>Schedule changes</h2><ul>";
|
|
foreach(clone $scheduleChanges as $c) {
|
|
print "<li>#{$c[2]->scheduleid}: {$c[1]->format('d-m-Y H:i')}: " . ($c[0] == $START ? 'Start' : 'Einde') . " {$c[2]->name} {$c[2]->suffix} (prio {$c[2]->priority})</li>";
|
|
}
|
|
print "</ul><hr/>";
|
|
}
|
|
|
|
// Go through all programs and keep going until we have one that starts after the end date.
|
|
$activeProgram = null;
|
|
$activeProgramStart = null;
|
|
while(!$scheduleChanges->isEmpty()) {
|
|
$item = $scheduleChanges->extract();
|
|
|
|
// If we passed the end time, we are (almost) done
|
|
if($item[1] > $end) {
|
|
if($DEBUG) { print "Reached end at {$item[1]->format('d-m-Y H:i')} ({$item[2]->name}).<br/>"; }
|
|
break;
|
|
}
|
|
|
|
$priority = $item[2]->priority;
|
|
if($item[0] == $START) {
|
|
if($active[$priority] != null) {
|
|
print( "<b>Error:</b>" . $item[2]->name . ' starts at ' . $item[1]->format('d-m-Y H:i') . ' (prio ' . $item[2]->id . ') but ' . $active[$priority]->name . ' is still active then.' );
|
|
if($DEBUG) {
|
|
foreach($active as $prio => $act) {
|
|
print "-- " . ($act ? $act->name : "[No program]") . " at prio $prio<br/>\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
// When a program starts, it becomes the active program in its layer
|
|
$active[$priority] = $item[2];
|
|
|
|
// In addition, if it has higher priority than the currently active program, it replaces that.
|
|
if($activeProgram == null || $priority < $activeProgram->priority) {
|
|
if(($activeProgram != null) && ($item[1] > $start)) {
|
|
if($DEBUG) print "{$activeProgramStart->format('d-m-Y H:i')} -- {$item[1]->format('d-m-Y H:i')}: {$activeProgram->name} (reason: {$item[2]->name} starts).<br/>";
|
|
$this->addToSchedule($schedule, $activeProgramStart, $item[1], $activeProgram, $item[2]->scheduleid);
|
|
}
|
|
|
|
$activeProgramStart = ($item[1] < $start) ? $start : clone $item[1];
|
|
$activeProgram = $item[2];
|
|
}
|
|
} else if($item[0] == $END) {
|
|
if($active[$priority] == null) {
|
|
print( "<b>Error:</b>" . $item[2]->name . ' ends at ' . $item[1]->format('d-m-Y H:i') . ' but no program is active at that priority and timestamp.');
|
|
if($DEBUG) {
|
|
foreach($active as $prio => $act) {
|
|
print "-- " . ($act ? $act->name : "[No program]") . " at prio $prio<br/>\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
// When the program ends, its layer becomes inactive.
|
|
$active[$priority] = null;
|
|
|
|
// In addition, when the ending program was the active program, we need to schedule it and find the new currently active program
|
|
if($activeProgram == $item[2]) {
|
|
if($DEBUG) print "{$activeProgramStart->format('d-m-Y H:i')} -- {$item[1]->format('d-m-Y H:i')}: {$activeProgram->name} {$activeProgram->suffix} (reason: {$item[2]->name} ends).<br/>";
|
|
$this->addToSchedule($schedule, $activeProgramStart, $item[1], $activeProgram, $item[2]->scheduleid);
|
|
|
|
$activeProgram = null;
|
|
foreach($active as $newPriority => $newActive) {
|
|
if(($newActive != null) && ($activeProgram == null || $newPriority < $activeProgram->priority)) {
|
|
$activeProgram = $newActive;
|
|
}
|
|
}
|
|
|
|
if($activeProgram != null) {
|
|
$activeProgramStart = clone $item[1];
|
|
}
|
|
}
|
|
} else {
|
|
return response()->abort(500, "Invalid item type: expected START ($START) or END ($END), but got {$item[0]}.");
|
|
}
|
|
}
|
|
|
|
if(($activeProgram != null) && ($activeProgramStart != $end)) {
|
|
if($DEBUG) print "{$activeProgramStart->format('d-m-Y H:i')} -- {$end->format('d-m-Y H:i')}: {$activeProgram->name} {$activeProgram->suffix} (reason: schedule finished).<br/>";
|
|
$this->addToSchedule($schedule, $activeProgramStart, $end, $activeProgram, $activeProgram->scheduleid);
|
|
}
|
|
|
|
return ['startdate' => $start, 'enddate' => $end, 'schedule' => $schedule];
|
|
}
|
|
|
|
public function period($from, $to)
|
|
{
|
|
$start = new \DateTimeImmutable(urldecode($from));
|
|
$end = new \DateTimeImmutable(urldecode($to));
|
|
return $this->createSchedule($start, $end, isset($_GET['debug']));
|
|
}
|
|
|
|
|
|
/**
|
|
* Details over een specifiek programma, inclusief schema en podcasts
|
|
*/
|
|
public function details($id) {
|
|
$programs = app('db')->select("SELECT `id`, `longname` AS `name`, `description`, `email` FROM `programs` WHERE `id` = :id", ['id' => (int)$id]);
|
|
if(count($programs) != 1) {
|
|
return abort(404);
|
|
}
|
|
|
|
$program = $programs[0];
|
|
|
|
$hosts = app('db')->select("SELECT `programs_users`.`user` AS `id`, `medewerkers`.`adressering` AS `name`, `user_emailaddresses`.`email` AS `email`
|
|
FROM `programs_users`
|
|
LEFT JOIN `users` ON `programs_users`.`user` = `users`.`id`
|
|
LEFT JOIN `medewerkers` ON `users`.`medewerkersid` = `medewerkers`.`id`
|
|
LEFT JOIN `user_emailaddresses` ON `user_emailaddresses`.`user` = `users`.`id`
|
|
WHERE `programs_users`.`program` = :program
|
|
AND `user_emailaddresses`.`preference` = 0
|
|
AND `programs_users`.`active` = 1
|
|
ORDER BY `name`", ['program' => $program->id]);
|
|
|
|
foreach($hosts as $host) {
|
|
$program->hosts[] = new \Model\ProgramHost($host);
|
|
}
|
|
|
|
$now = new \DateTimeImmutable('now');
|
|
$past = $this->createSchedule($now->add(\DateInterval::createFromDateString('-2 weeks')), $now->add(\DateInterval::createFromDateString('+1 week')));
|
|
$program->recent = [];
|
|
$program->next = [];
|
|
foreach(array_reverse($past['schedule']) as $item) {
|
|
if($item['program']->id == $id) {
|
|
if($item['end'] > $now) {
|
|
array_unshift($program->next, ['starts' => $item['start'], 'ends' => $item['end'], 'name' => $item['program']->name, 'nonstop' => $item['program']->nonstop, 'rerun' => $item['program']->rerun]);
|
|
} else if($item['program']->priority < 3 && $item['start'] < $now) {
|
|
$program->recent[] = ['starts' => $item['start'], 'ends' => $item['end'], 'name' => $item['program']->name, 'nonstop' => $item['program']->nonstop, 'rerun' => $item['program']->rerun];
|
|
}
|
|
}
|
|
}
|
|
|
|
return response()->json($program);
|
|
}
|
|
|
|
|
|
private function getTrack($studio = 'nonstop', $next = false) {
|
|
$prefix = $next ? "next" : "current";
|
|
$data = app('db')->select("SELECT {$prefix}_start AS start, {$prefix}_itemcode AS itemCode, {$prefix}_title AS title, {$prefix}_artist AS artist, {$prefix}_duration AS duration FROM `now_playing` WHERE `studio` = :studio", ['studio' => $studio]);
|
|
|
|
return new \Model\Track($data[0]);
|
|
}
|
|
|
|
private function isStreamEnabled() {
|
|
if(!($stream = env('STUDIO_STREAM'))) {
|
|
return false;
|
|
}
|
|
|
|
$enableCameraQuery = app('db')->select("SELECT camera FROM `programs_schedule_webcam` WHERE `active` = 1");
|
|
if(count($enableCameraQuery) && ($enableCamera = $enableCameraQuery[0]->camera)) {
|
|
$isStreamDisabled = 0;
|
|
$output = [];
|
|
exec("ffprobe -v quiet -show_streams '$stream'", $output, $isStreamDisabled);
|
|
if(!$isStreamDisabled) {
|
|
return $enableCamera;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function studiocam() {
|
|
return response()->json(['onair' => 'Scene1']);
|
|
}
|
|
|
|
/**
|
|
* Huidige programma of nummer
|
|
*/
|
|
public function onair() {
|
|
$start = new \DateTimeImmutable('now');
|
|
$einde = new \DateTimeImmutable('now + 1 second');
|
|
$schema = $this->createSchedule($start, $einde);
|
|
$program = $schema['schedule'][0]['program'];
|
|
|
|
$activeStudio = null;
|
|
$current = null;
|
|
if($program->nonstop) {
|
|
$activeStudio = 'nonstop';
|
|
} else {
|
|
$activeStudioQuery = app('db')->select("SELECT `studio` FROM `programs_schedule_webcam` WHERE `active` = 1");
|
|
if(count($activeStudioQuery)) {
|
|
$activeStudio = $activeStudioQuery[0]->studio;
|
|
}
|
|
}
|
|
|
|
if($activeStudio) {
|
|
$current = $this->getTrack($activeStudio);
|
|
$next = $this->getTrack($activeStudio, /* next: */ true);
|
|
$current->ends($next->start);
|
|
if($current->isLayout() /* || $current->secondsRemaining() < 0 */) {
|
|
if(!$next->isLayout()) {
|
|
$current = $next;
|
|
}
|
|
}
|
|
}
|
|
|
|
$data = [
|
|
'inProgram' => !$program->nonstop,
|
|
'program' => $program,
|
|
'stream' => $this->isStreamEnabled(),
|
|
];
|
|
|
|
if($current && !$current->isLayout() && $current->title) {
|
|
$data['current'] = $current;
|
|
}
|
|
|
|
return response()->json($data);
|
|
}
|
|
|
|
public function onair_text(Request $request) {
|
|
$start = new \DateTimeImmutable('now');
|
|
$einde = new \DateTimeImmutable('now + 1 second');
|
|
$schema = $this->createSchedule($start, $einde);
|
|
$program = $schema['schedule'][0]['program'];
|
|
|
|
$activeStudioQuery = app('db')->select("SELECT `studio` FROM `programs_schedule_webcam` WHERE `active` = 1");
|
|
if(!count($activeStudioQuery) || !($activeStudio = $activeStudioQuery[0]->studio)) {
|
|
$activeStudio = $program->nonstop ? 'nonstop' : null;
|
|
}
|
|
|
|
$text = [];
|
|
$current = null;
|
|
|
|
if($activeStudio) {
|
|
$current = $this->getTrack($activeStudio);
|
|
$next = $this->getTrack($activeStudio, /* next: */ true);
|
|
$current->ends($next->start);
|
|
if($current->isLayout() /* || $current->secondsRemaining() < 0 */) {
|
|
if(!$next->isLayout()) {
|
|
$current = $next;
|
|
}
|
|
}
|
|
}
|
|
|
|
if($request->get('program_only', false)) {
|
|
return $program->name;
|
|
}
|
|
|
|
if(!$request->get('program_only', false)) {
|
|
if(!$program->nonstop) $text[] = $program->name;
|
|
if($current && !$current->isLayout()) {
|
|
if($current->artist) $text[] = $current->artist;
|
|
if($current->title) $text[] = $current->title;
|
|
}
|
|
}
|
|
|
|
return join(' - ', $text);
|
|
}
|
|
|
|
/**
|
|
* Programmas nu en straks (24 uur vooruit)
|
|
*/
|
|
public function comingup() {
|
|
$start = new \DateTimeImmutable('now');
|
|
$einde = $start->add(\DateInterval::createFromDateString('today + 48 hours'));
|
|
$schema = $this->createSchedule($start, $einde);
|
|
|
|
return response()->json($schema);
|
|
}
|
|
|
|
/**
|
|
* Recente programma's (max. 2 weken geleden)
|
|
*/
|
|
public function recent() {
|
|
$einde = new \DateTimeImmutable('now');
|
|
$start = $einde->add(\DateInterval::createFromDateString('today - 13 days'));
|
|
$schema = $this->createSchedule($start, $einde);
|
|
|
|
return response()->json($schema);
|
|
}
|
|
|
|
/**
|
|
* Programmaschema per week
|
|
*/
|
|
public function schedule(Request $request, $shiftWeeks = null) {
|
|
$start = (new \DateTimeImmutable('Monday this week'))->add(\DateInterval::createFromDateString((int)$shiftWeeks . ' weeks'));
|
|
$einde = $start->add(\DateInterval::createFromDateString('1 week'));
|
|
|
|
return response()->json($this->createSchedule($start, $einde));
|
|
}
|
|
|
|
public function month($year, $month) {
|
|
$start = new \DateTimeImmutable($year . '-' . $month . '-01');
|
|
$einde = $start->add(\DateInterval::createFromDateString('1 month'));
|
|
return response()->json($this->createSchedule($start, $einde));
|
|
}
|
|
}
|