aboutsummaryrefslogtreecommitdiff
path: root/model
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--model/Relive.php24
-rw-r--r--model/Room.php193
-rw-r--r--model/RoomSelection.php52
-rw-r--r--model/RoomTab.php34
-rw-r--r--model/Schedule.php173
-rw-r--r--model/Stream.php174
-rw-r--r--model/StreamList.php55
7 files changed, 648 insertions, 57 deletions
diff --git a/model/Relive.php b/model/Relive.php
new file mode 100644
index 0000000..3ffdc1b
--- /dev/null
+++ b/model/Relive.php
@@ -0,0 +1,24 @@
+<?php
+
+class Relive extends ModelBase
+{
+ function relive_talks()
+ {
+ $talks = @file_get_contents(get('OVERVIEW.RELIVE_JSON'));
+ $talks = utf8_decode($talks);
+ $talks = (array)json_decode($talks, true);
+
+ usort($talks, function($a, $b) {
+ $sort = array('live', 'recorded', 'released');
+ return array_search($a['status'], $sort) > array_search($b['status'], $sort);
+ });
+
+ $talks_by_id = array();
+ foreach ($talks as $value)
+ {
+ $talks_by_id[$value['id']] = $value;
+ }
+
+ return $talks_by_id;
+ }
+}
diff --git a/model/Room.php b/model/Room.php
index c7e51f9..f428d87 100644
--- a/model/Room.php
+++ b/model/Room.php
@@ -35,11 +35,202 @@ class Room extends ModelBase
+ public function hasStereo() {
+ return $this->get('ROOMS.'.$this->getSlug().'.STEREO');
+ }
+
public function hasPreview() {
- return get('ROOMS.'.$this->getSlug().'.PREVIEW');
+ return $this->get('ROOMS.'.$this->getSlug().'.PREVIEW');
}
public function hasSchedule() {
return $this->get('ROOMS.'.$this->getSlug().'.SCHEDULE') && $this->has('SCHEDULE');
}
+
+ public function hasFeedback() {
+ return $this->get('ROOMS.'.$this->getSlug().'.FEEDBACK') && $this->has('FEEDBACK');
+ }
+
+
+ public function hasTwitter() {
+ return $this->get('ROOMS.'.$this->getSlug().'.TWITTER') && $this->has('TWITTER');
+ }
+
+ public function getTwitterDisplay() {
+ return sprintf(
+ $this->get('ROOMS.'.$this->getSlug().'.TWITTER_CONFIG.DISPLAY', $this->get('TWITTER.DISPLAY')),
+ $this->getSlug()
+ );
+ }
+
+ public function getTwitterUrl() {
+ return sprintf(
+ 'https://twitter.com/intent/tweet?text=%s',
+ rawurlencode($this->getTwitterText())
+ );
+ }
+
+ public function getTwitterText() {
+ return sprintf(
+ $this->get('ROOMS.'.$this->getSlug().'.TWITTER_CONFIG.TEXT', $this->get('TWITTER.TEXT')),
+ $this->getSlug()
+ );
+ }
+
+
+ public function hasIrc() {
+ return $this->get('ROOMS.'.$this->getSlug().'.IRC') && $this->has('IRC');
+ }
+
+ public function getIrcDisplay() {
+ return sprintf(
+ $this->get('ROOMS.'.$this->getSlug().'.IRC_CONFIG.DISPLAY', $this->get('IRC.DISPLAY')),
+ $this->getSlug()
+ );
+ }
+
+ public function getIrcUrl() {
+ return sprintf(
+ $this->get('ROOMS.'.$this->getSlug().'.IRC_CONFIG.URL', $this->get('IRC.URL')),
+ rawurlencode($this->getSlug())
+ );
+ }
+
+
+ public function hasChat() {
+ return $this->hasTwitter() || $this->hasIrc();
+ }
+
+
+ public function hasSdVideo() {
+ return $this->get('ROOMS.'.$this->getSlug().'.SD_VIDEO');
+ }
+
+ public function hasHdVideo() {
+ return $this->get('ROOMS.'.$this->getSlug().'.HD_VIDEO');
+ }
+
+ public function hasVideo() {
+ return $this->hasSdVideo() || $this->hasHdVideo();
+ }
+
+ public function hasAudio() {
+ return $this->get('ROOMS.'.$this->getSlug().'.AUDIO');
+ }
+
+ public function hasSlides() {
+ return $this->get('ROOMS.'.$this->getSlug().'.SLIDES');
+ }
+
+ public function hasMusic() {
+ return $this->get('ROOMS.'.$this->getSlug().'.MUSIC');
+ }
+
+ public function hasTranslation() {
+ return $this->get('ROOMS.'.$this->getSlug().'.TRANSLATION');
+ }
+
+ public function getSelectionNames()
+ {
+ $selections = array();
+ if($this->hasHdVideo())
+ $selections[] = 'hd';
+
+ if($this->hasSdVideo())
+ $selections[] = 'sd';
+
+ if($this->hasSlides())
+ $selections[] = 'slides';
+
+ if($this->hasAudio())
+ $selections[] = 'audio';
+
+ if($this->hasMusic())
+ $selections[] = 'music';
+
+ return $selections;
+ }
+
+ public function getTabNames()
+ {
+ $tabs = array();
+ if($this->hasVideo())
+ $tabs[] = 'video';
+
+ if($this->hasSlides())
+ $tabs[] = 'slides';
+
+ if($this->hasAudio())
+ $tabs[] = 'audio';
+
+ if($this->hasMusic())
+ $tabs[] = 'music';
+
+ return $tabs;
+ }
+
+ public function getSelections()
+ {
+ $selections = array();
+ foreach($this->getSelectionNames() as $selection)
+ $selections[$tab] = $this->createSelectionObject($selection);
+
+ return $selections;
+ }
+
+ public function createSelectionObject($selection)
+ {
+ return new RoomSelection($this, $selection);
+ }
+
+ public function getTabs()
+ {
+ $tabs = array();
+ foreach($this->getTabNames() as $tab)
+ $tabs[$tab] = $this->createTabObject($tab);
+
+ return $tabs;
+ }
+
+ public function createTabObject($tab)
+ {
+ return new RoomTab($this, $tab);
+ }
+
+ public function getVideoResolutions()
+ {
+ $res = array();
+ if($this->hasHdVideo())
+ $res[] = 'hd';
+
+ if($this->hasSdVideo())
+ $res[] = 'sd';
+
+ return $res;
+ }
+
+ public function selectStream($selection, $language = 'native')
+ {
+ $selections = $this->getSelectionNames();
+
+ // default page
+ if(!$selection)
+ $selection = $selections[0];
+
+ if(!in_array($selection, $selections))
+ throw new NotFoundException('Selection '.$selection.' in Room '.$this->getSlug());
+
+ if($language == 'translated' && !$this->hasTranslation())
+ throw new NotFoundException('Translated Streams of Room '.$this->getSlug());
+
+ return $this->createStreamObject($selection, $language);
+ }
+
+ public function createStreamObject($selection, $language = 'native')
+ {
+ if($language == 'native' && $this->hasStereo())
+ $language = 'stereo';
+
+ return new Stream($this, $selection, $language);
+ }
}
diff --git a/model/RoomSelection.php b/model/RoomSelection.php
new file mode 100644
index 0000000..c163321
--- /dev/null
+++ b/model/RoomSelection.php
@@ -0,0 +1,52 @@
+<?php
+
+class RoomSelection
+{
+ public function __construct(Room $room, $selection)
+ {
+ $this->room = $room;
+ $this->selection = $selection;
+ }
+
+ public function getRoom()
+ {
+ return $this->room;
+ }
+
+ public function getSelection()
+ {
+ return $this->selection;
+ }
+
+ public function getLink()
+ {
+ $selection = $this->getRoom()->getSelectionNames();
+ if($selection[0] == $this->getSelection())
+ return rawurlencode($this->getRoom()->getSlug()).'/';
+
+ return rawurlencode($this->getRoom()->getSlug()).'/'.rawurlencode($this->getSelection()).'/';
+ }
+
+ public function getTranslatedLink()
+ {
+ return $this->getLink().'translated/';
+ }
+
+ public function getDisplay()
+ {
+ switch($this->getSelection())
+ {
+ case 'sd':
+ case 'hd':
+ return strtoupper($this->getSelection());
+
+ default:
+ return ucfirst($this->getSelection());
+ }
+ }
+
+ public function getTech()
+ {
+ return $this->getSelection().'-tech';
+ }
+}
diff --git a/model/RoomTab.php b/model/RoomTab.php
new file mode 100644
index 0000000..d4f781b
--- /dev/null
+++ b/model/RoomTab.php
@@ -0,0 +1,34 @@
+<?php
+
+class RoomTab
+{
+ public function __construct(Room $room, $tab)
+ {
+ $this->room = $room;
+ $this->tab = $tab;
+ }
+
+ public function getRoom()
+ {
+ return $this->room;
+ }
+
+ public function getTab()
+ {
+ return $this->tab;
+ }
+
+ public function getLink()
+ {
+ $tabs = $this->getRoom()->getTabNames();
+ if($tabs[0] == $this->getTab())
+ return rawurlencode($this->getRoom()->getSlug()).'/';
+
+ return rawurlencode($this->getRoom()->getSlug()).'/'.rawurlencode($this->getTab()).'/';
+ }
+
+ public function getDisplay()
+ {
+ return ucfirst($this->getTab());
+ }
+}
diff --git a/model/Schedule.php b/model/Schedule.php
index db940ce..ca4881d 100644
--- a/model/Schedule.php
+++ b/model/Schedule.php
@@ -5,4 +5,177 @@ class Schedule extends ModelBase
public function getSimulationOffset() {
return $this->get('SCHEDULE.SIMULATE_OFFSET', 0);
}
+
+ private function strtoduration($str)
+ {
+ $parts = explode(':', $str);
+ return ((int)$parts[0] * 60 + (int)$parts[1]) * 60;
+ }
+
+ function program()
+ {
+ if(!has('SCHEDULE'))
+ return;
+
+ if(has('SCHEDULE.CACHE') && function_exists('apc_fetch'))
+ {
+ $program = apc_fetch('SCHEDULE.CACHE');
+ if($program) return $program;
+ }
+
+
+ $program = array();
+ $opts = array(
+ 'http' => array(
+ 'timeout' => 2,
+ 'user_agent' => 'C3Voc Universal Streaming-Website Backend @ '.$_SERVER['HTTP_HOST'],
+ )
+ );
+ $context = stream_context_create($opts);
+ $schedule = file_get_contents(get('SCHEDULE.URL'), false, $context);
+
+ // failed, give up
+ if(!$schedule)
+ return array();
+
+ $schedule = simplexml_load_string($schedule);
+
+ // re-calculate day-ends
+ // some schedules have long gaps before the first talk or talks that expand beyond the dayend
+ // (fiffkon, i look at you)
+ // so to be on the safer side we calculate our own daystart/end here
+ foreach($schedule->day as $day)
+ {
+ $daystart = PHP_INT_MAX;
+ $dayend = 0;
+
+ foreach($day->room as $room)
+ {
+ foreach($room->event as $event)
+ {
+ $start = strtotime((string)$event->date);
+ $duration = strtoduration((string)$event->duration);
+ $end = $start + $duration;
+
+ $daystart = min($daystart, $start);
+ $dayend = max($dayend, $end);
+ }
+ }
+
+ $day['start'] = $daystart;
+ $day['end'] = $dayend;
+ }
+
+ $dayidx = 0;
+ foreach($schedule->day as $day)
+ {
+ $dayidx++;
+ $daystart = (int)$day['start'];
+ $dayend = (int)$day['end'];
+
+ $roomidx = 0;
+ foreach($day->room as $room)
+ {
+ $roomidx++;
+ $lastend = false;
+ $name = (string)$room['name'];
+ if(isset($GLOBALS['CONFIG']['FAHRPLAN_ROOM_MAPPING'][$name]))
+ $name = $GLOBALS['CONFIG']['FAHRPLAN_ROOM_MAPPING'][$name];
+
+ foreach($room->event as $event)
+ {
+ $start = strtotime((string)$event->date);
+ $duration = strtoduration((string)$event->duration);
+ $end = $start + $duration;
+
+ if($lastend && $lastend < $start)
+ {
+ // synthesize pause event
+ $pauseduration = $start - $lastend;
+ $program[$name][] = array(
+ 'special' => 'pause',
+ 'title' => round($pauseduration / 60).' minutes pause',
+
+ 'fstart' => date('c', $lastend),
+ 'fend' => date('c', $start),
+
+ 'start' => $lastend,
+ 'end' => $start,
+ 'duration' => $pauseduration,
+ );
+ }
+ else if(!$lastend && $daystart < $start)
+ {
+ $program[$name][] = array(
+ 'special' => 'gap',
+
+ 'fstart' => date('c', $daystart),
+ 'fend' => date('c', $start),
+
+ 'start' => $daystart,
+ 'end' => $start,
+ 'duration' => $start - $daystart,
+ );
+ }
+
+ $personnames = array();
+ foreach($event->persons->person as $person)
+ $personnames[] = (string)$person;
+
+ $program[$name][] = array(
+ 'title' => (string)$event->title,
+ 'speaker' => implode(', ', $personnames),
+
+ 'fstart' => date('c', $start),
+ 'fend' => date('c', $end),
+
+ 'start' => $start,
+ 'end' => $end,
+ 'duration' => $duration,
+ );
+
+ $lastend = $end;
+ }
+
+ // synthesize daychange event
+ if(!$lastend) $lastend = $daystart;
+ if($lastend < $dayend)
+ {
+ $program[$name][] = array(
+ 'special' => 'gap',
+
+ 'fstart' => date('c', $lastend),
+ 'fend' => date('c', $dayend),
+
+ 'start' => $lastend,
+ 'end' => $dayend,
+ 'duration' => $dayend - $lastend,
+ );
+ }
+
+ if($dayidx < count($schedule->day))
+ {
+ $program[$name][] = array(
+ 'special' => 'daychange',
+ 'title' => 'Daychange from Day '.$dayidx.' to '.($dayidx+1),
+
+ 'start' => $dayend,
+ 'end' => (int)$schedule->day[$dayidx]['start'],
+ 'duration' => 60*60,
+ );
+ }
+ }
+ }
+
+ if(has('SCHEDULE.CACHE') && function_exists('apc_store'))
+ {
+ apc_store(
+ 'SCHEDULE.CACHE',
+ $program,
+ get('SCHEDULE.CACHE')
+ );
+ }
+
+ return $program;
+ }
}
diff --git a/model/Stream.php b/model/Stream.php
index a60284a..402dfff 100644
--- a/model/Stream.php
+++ b/model/Stream.php
@@ -1,5 +1,177 @@
<?php
-class Room {
+class Stream
+{
+ public function __construct(Room $room, $selection, $language)
+ {
+ $this->room = $room;
+ $this->selection = $selection;
+ $this->language = $language;
+ }
+ public function getRoom()
+ {
+ return $this->room;
+ }
+
+ public function getSelection()
+ {
+ return $this->selection;
+ }
+
+ public function getLanguage()
+ {
+ return $this->language;
+ }
+
+ public function isTranslated()
+ {
+ return $this->getLanguage() == 'translated';
+ }
+
+ public function getVideoSize()
+ {
+ switch($this->getSelection())
+ {
+ case 'sd':
+ case 'slides':
+ return array(1024, 576);
+
+ case 'hd':
+ return array(1920, 1080);
+
+ default:
+ return null;
+ }
+ }
+
+ public function getVideoWidth()
+ {
+ $sz = $this->getVideoSize();
+ return $sz[0];
+ }
+
+ public function getVideoHeight()
+ {
+ $sz = $this->getVideoSize();
+ return $sz[1];
+ }
+
+ public function getTab()
+ {
+ switch($this->getSelection())
+ {
+ case 'sd':
+ case 'hd':
+ return 'video';
+
+ default:
+ return $this->getSelection();
+ }
+ }
+
+ public function getPlayerType()
+ {
+ return $this->getTab();
+ }
+
+ public function getDisplay()
+ {
+ $display = $this->getRoom()->getDisplay().' ';
+ switch($this->getSelection())
+ {
+ case 'hd':
+ $display .= 'FullHD Video';
+ break;
+
+ case 'sd':
+ $display .= 'SD Video';
+ break;
+
+ default:
+ $display .= ucfirst($this->getSelection());
+ break;
+ }
+
+ if($this->isTranslated())
+ $display .= ' (Translation)';
+
+ return $display;
+ }
+
+ public function getVideoUrl($proto)
+ {
+ switch($proto)
+ {
+ case 'webm':
+ return 'http://cdn.c3voc.de/'.rawurlencode($this->getRoom()->getStream()).'_'.rawurlencode($this->getLanguage()).'_'.rawurlencode($this->getSelection()).'.webm';
+
+ case 'hls':
+ return 'http://cdn.c3voc.de/hls/'.rawurlencode($this->getRoom()->getStream()).'_'.rawurlencode($this->getLanguage()).'_'.rawurlencode($this->getSelection()).'.m3u8';
+
+ default:
+ return null;
+ }
+ }
+ public static function getVideoProtos()
+ {
+ return array(
+ 'webm' => 'WebM',
+ 'hls' => 'HLS',
+ );
+ }
+
+ public function getSlidesUrl($proto)
+ {
+ return $this->getVideoUrl($proto);
+ }
+ public static function getSlidesProtos()
+ {
+ return Stream::getVideoProtos();
+ }
+
+
+ public function getAudioUrl($proto)
+ {
+ switch($proto)
+ {
+ case 'mp3':
+ return 'http://cdn.c3voc.de/'.rawurlencode($this->getRoom()->getStream()).'_'.rawurlencode($this->getLanguage()).'.mp3';
+
+ case 'opus':
+ return 'http://cdn.c3voc.de/'.rawurlencode($this->getRoom()->getStream()).'_'.rawurlencode($this->getLanguage()).'.opus';
+
+ default:
+ return null;
+ }
+ }
+ public static function getAudioProtos()
+ {
+ return array(
+ 'mp3' => 'MP3',
+ 'opus' => 'Opus',
+ );
+ }
+
+ public function getMusicUrl($proto)
+ {
+ switch($proto)
+ {
+ case 'mp3':
+ return 'http://cdn.c3voc.de/'.rawurlencode($this->getRoom()->getStream()).'.mp3';
+
+ case 'opus':
+ return 'http://cdn.c3voc.de/'.rawurlencode($this->getRoom()->getStream()).'.opus';
+
+ default:
+ return null;
+ }
+ }
+ public static function getMusicProtos()
+ {
+ return array(
+ 'mp3' => 'MP3',
+ 'opus' => 'Opus',
+ );
+ }
}
diff --git a/model/StreamList.php b/model/StreamList.php
deleted file mode 100644
index d203a52..0000000
--- a/model/StreamList.php
+++ /dev/null
@@ -1,55 +0,0 @@
-<?php
-
-class StreamList extends ModelBase implements IteratorAggregate
-{
- private $streams = array();
-
- public function __construct($slug)
- {
- if(! $this->has('ROOMS.'.$slug))
- throw new NotFoundException('Room '.$slug);
-
- $this->slug = $slug;
- $this->streams = array();
-
- $streams = $this->get("ROOMS.$slug.STREAMS");
- foreach((array)$streams as $stream) {
- $this->streams[$stream] = explode('-', $stream);
- }
- }
-
- public function getRoomSlug() {
- return $this->slug;
- }
-
- public function getRoom() {
- return new Room($this->getRoomSlug());
- }
-
- public function getStreams() {
- return array_keys($this->streams);
- }
-
- public function getIterator() {
- return new ArrayIterator($this->streams);
- }
-
-
- public function hasTranslation() {
- }
-
- public function hasRTMP() {
- }
-
- public function hasHLS() {
- }
-
- public function hasWebM() {
- }
-
- public function hasHD() {
- }
-
- public function hasSD() {
- }
-}