Skip to content

Commit

Permalink
Paging for Episodes and Performance Optimizations (elan-ev#560)
Browse files Browse the repository at this point in the history
* add paging for episodes

* remove problematic debug

* further optimization and more aggresive caching

* fix some minor issues

* use same endpoint for counting as for retrieving

* keep episodes in local db

* ommit unnecessary calls to oc

* show wip episodes again

* update caniuse

* course specific debugging, remove unnecessary call to oc

* use caching

* search service takes other filter arguments than external api

* show debug option without connected series as well
  • Loading branch information
tgloeggl authored Mar 16, 2022
1 parent ae23670 commit a489655
Show file tree
Hide file tree
Showing 17 changed files with 598 additions and 496 deletions.
19 changes: 11 additions & 8 deletions OpenCast.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,11 @@ public function _n($string0, $string1, $n)
*/
public function getIconNavigation($course_id, $last_visit, $user_id = null)
{
$ocmodel = new OCCourseModel($course_id);
$visibility = OCSeriesModel::getVisibility($course_id);

if (!$this->isActivated($course_id)
|| (
$ocmodel->getSeriesVisibility() == 'invisible'
$visibility['visibility'] == 'invisible'
&& !OCPerm::editAllowed($course_id)
)
) {
Expand All @@ -158,7 +159,7 @@ public function getIconNavigation($course_id, $last_visit, $user_id = null)

$this->image_path = $this->getPluginURL() . '/images/';
if ($GLOBALS['perm']->have_studip_perm('user', $course_id)) {
$ocgetcount = $ocmodel->getCount($last_visit);
$ocgetcount = OCCourseModel::getCount($course_id, $last_visit);
$text = sprintf(
$this->_('Es gibt %s neue Opencast Aufzeichnung(en) seit ihrem letzten Besuch.'),
$ocgetcount
Expand Down Expand Up @@ -217,9 +218,10 @@ public function getTabNavigation($course_id)
return;
}

$ocmodel = new OCCourseModel($course_id);
$title = 'Opencast';
if ($ocmodel->getSeriesVisibility() == 'invisible') {
$visibility = OCSeriesModel::getVisibility($course_id);
$title = 'Opencast';

if ($visibility['visibility'] == 'invisible') {
$title .= " (". $this->_('versteckt'). ")";
}
$main = new Navigation($title);
Expand Down Expand Up @@ -247,7 +249,8 @@ public function getTabNavigation($course_id)
$scheduler = new Navigation($this->_('Aufzeichnungen planen'));
$scheduler->setURL(PluginEngine::getURL($this, [], 'course/scheduler'));

$series_metadata = OCSeminarSeries::getSeries($course_id);

$series_metadata = OCSeminarSeries::findBySeminar_id($course_id);
if ($series_metadata && $series_metadata[0]['schedule'] == '1') {
$main->addSubNavigation('scheduler', $scheduler);
}
Expand Down Expand Up @@ -277,7 +280,7 @@ public function getTabNavigation($course_id)
$main->addSubNavigation('linkedcourse', $linkedCourse);
}

if ($ocmodel->getSeriesVisibility() == 'visible' || OCPerm::editAllowed($course_id)) {
if ($visibility['visibility'] == 'visible' || OCPerm::editAllowed($course_id)) {
return ['opencast' => $main];
}
return [];
Expand Down
241 changes: 178 additions & 63 deletions classes/OCRestClient/ApiEventsClient.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use Opencast\Models\OCConfig;
use Opencast\Models\Pager;

class ApiEventsClient extends OCRestClient
{
Expand Down Expand Up @@ -28,88 +29,81 @@ public function getEpisode($episode_id, $with_publications = false)
return [$code, $data];
}

public function getACL($episode_id)
{
return json_decode(json_encode($this->getJSON('/' . $episode_id . '/acl')), true);
}

public function setACL($episode_id, $acl)
{
$data = [
'acl' => json_encode($acl->toArray())
];

$result = $this->putJSON('/' . $episode_id . '/acl', $data, true);

return $result[1] == 200;
}

/**
* getEpisodes() - retrieves episode metadata for a given series identifier
* Retrieves episode metadata for a given series identifier
* from connected Opencast
*
* @param string series_id Identifier for a Series
*
* @return array response of episodes
*/
public function getEpisodes($series_id, $refresh = false)
public function getBySeries($series_id)
{
$cache = StudipCacheFactory::getCache();
$cache_key = 'oc_episodesforseries/' . $series_id;
$episodes = $cache->read($cache_key);
static $static_events;

if ($refresh || $episodes === false || $GLOBALS['perm']->have_perm('tutor')) {
$service_url = '/?sign=false&withacl=false&withmetadata=false&withscheduling=false&withpublications=true&filter=is_part_of:'
. $series_id . '&sort=&limit=0&offset=0';
$cache = \StudipCacheFactory::getCache();

if ($episodes = $this->getJSON($service_url)) {
foreach ($episodes as $key => $val) {
$episodes[$key]->id = $val->identifier;
}
if (empty($static_events[$series_id])) {
$events = [];

$cache->write($cache_key, serialize($episodes), 7200);
return $episodes ?: [];
} else {
return [];
}
} else {
return unserialize($episodes) ?: [];
}
}
$offset = Pager::getOffset();
$limit = Pager::getLimit();
$sort = Pager::getSortOrder();
$search = Pager::getSearch();

public function getAclForEpisode($series_id, $episode_id)
{
static $acl;
$search_service = new SearchClient($this->config_id);

if (!$acl[$series_id]) {
$params = [
'withacl' => 'true',
'filter' => sprintf(
'is_part_of:%s,status:EVENTS.EVENTS.STATUS.PROCESSED',
$series_id
)
];
// first, get list of events ids from search service
$search_events = $search_service->getJSON('/episode.json?sid=' . $series_id
. ($search ? '&q='. $search : '')
. "&sort=$sort&limit=$limit&offset=$offset");

$data = $this->getJSON('?' . http_build_query($params));
Pager::setLength($search_events->{'search-results'}->total);

if (is_array($data)) foreach ($data as $episode) {
$acl[$series_id][$episode->identifier] = $episode->acl;
}
}
$results = is_array($search_events->{'search-results'}->result)
? $search_events->{'search-results'}->result
: [$search_events->{'search-results'}->result];

return $acl[$series_id][$episode_id];
}
// then, iterate over list and get each event from the external-api
foreach ($results as $s_event) {
$cache_key = 'sop/episodes/'. $s_event->id;
$event = $cache->read($cache_key);

if (empty($s_event->id)) {
continue;
}

public function getACL($episode_id)
{
return json_decode(json_encode($this->getJSON('/' . $episode_id . '/acl')), true);
}
if (!$event) {
$event = self::prepareEpisode(
$this->getJSON('/' . $s_event->id . '/?withpublications=true')
);

public function setACL($episode_id, $acl)
{
$data = [
'acl' => json_encode($acl->toArray())
];

$result = $this->putJSON('/' . $episode_id . '/acl', $data, true);
$cache->write($cache_key, $event, 86000);
}

return $result[1] == 200;
}
$events[$s_event->id] = $event;
}

public function getBySeries($series_id, $params = [])
{
$events = $this->getJSON('/?filter=is_part_of:' .
$series_id . ',status:EVENTS.EVENTS.STATUS.PROCESSED', $params);
$static_events[$series_id] = $events;
}

return array_reduce($events, function ($events, $event) {
$events[$event->identifier] = $event;
return $events;
}, []);
return $static_events[$series_id];
}

public function getAllScheduledEvents()
Expand All @@ -131,13 +125,14 @@ public function getAllScheduledEvents()
return $events;
}

public function getVisibilityForEpisode($series_id, $episode_id, $course_id = null)
public function getVisibilityForEpisode($episode, $course_id = null)
{
if (is_null($course_id)) {
$course_id = Context::getId();
}

$acls = self::getAclForEpisode($series_id, $episode_id);
$acls = $episode->acl;

$vis_conf = !is_null(CourseConfig::get($course_id)->COURSE_HIDE_EPISODES)
? boolval(CourseConfig::get($course_id)->COURSE_HIDE_EPISODES)
: \Config::get()->OPENCAST_HIDE_EPISODES;
Expand All @@ -146,7 +141,7 @@ public function getVisibilityForEpisode($series_id, $episode_id, $course_id = nu
: 'visible';

if (empty($acls)) {
OCModel::setVisibilityForEpisode($course_id, $episode_id, $default);
OCModel::setVisibilityForEpisode($course_id, $episode->id, $default);
return $default;
}

Expand Down Expand Up @@ -184,4 +179,124 @@ public function getVisibilityForEpisode($series_id, $episode_id, $course_id = nu
OCModel::setVisibilityForEpisode($course_id, $episode_id, $default);
return $default;
}

private function prepareEpisode($episode)
{
$new_episode = [];

if (!empty($episode->publications[0]->attachments)) {
$presentation_preview = false;
$preview = false;
$presenter_download = [];
$presentation_download = [];
$audio_download = [];
$annotation_tool = false;

foreach ((array) $episode->publications[0]->attachments as $attachment) {
if ($attachment->flavor === "presenter/search+preview") {
$preview = $attachment->url;
}
if ($attachment->flavor === "presentation/player+preview") {
$presentation_preview = $attachment->url;
}
}

foreach ($episode->publications[0]->media as $track) {
$parsed_url = parse_url($track->url);

if ($track->flavor === 'presenter/delivery') {
if (($track->mediatype === 'video/mp4' || $track->mediatype === 'video/avi')
&& ((in_array('atom', $track->tags) || in_array('engage-download', $track->tags))
&& $parsed_url['scheme'] != 'rtmp' && $parsed_url['scheme'] != 'rtmps')
&& !empty($track->has_video)
) {
$quality = $this->calculate_size(
$track->bitrate,
$track->duration
);
$presenter_download[$quality] = [
'url' => $track->url,
'info' => $this->getResolutionString($track->width, $track->height)
];
}

if (in_array($track->mediatype, ['audio/aac', 'audio/mp3', 'audio/mpeg', 'audio/m4a', 'audio/ogg', 'audio/opus'])
&& !empty($track->has_audio)
) {
$quality = $this->calculate_size(
$track->bitrate,
$track->duration
);
$audio_download[$quality] = [
'url' => $track->url,
'info' => round($track->audio->bitrate / 1000, 1) . 'kb/s, ' . explode('/', $track->mediatype)[1]
];
}
}

if ($track->flavor === 'presentation/delivery' && (
(
$track->mediatype === 'video/mp4'
|| $track->mediatype === 'video/avi'
) && (
(
in_array('atom', $track->tags)
|| in_array('engage-download', $track->tags)
)
&& $parsed_url['scheme'] != 'rtmp'
&& $parsed_url['scheme'] != 'rtmps'
)
&& !empty($track->has_video)
)) {
$quality = $this->calculate_size(
$track->bitrate,
$track->duration
);

$presentation_download[$quality] = [
'url' => $track->url,
'info' => $this->getResolutionString($track->width, $track->height)
];
}
}

foreach ($episode->publications as $publication) {
if ($publication->channel == 'annotation-tool') {
$annotation_tool = $publication->url;
}
}

ksort($presenter_download);
ksort($presentation_download);
ksort($audio_download);
$new_episode = [
'id' => $episode->identifier,
'series_id' => $episode->is_part_of,
'title' => $episode->title,
'start' => $episode->start,
'duration' => $episode->duration,
'description' => $episode->description,
'author' => $episode->creator,
'preview' => $preview,
'presentation_preview' => $presentation_preview,
'presenter_download' => $presenter_download,
'presentation_download' => $presentation_download,
'audio_download' => $audio_download,
'annotation_tool' => $annotation_tool,
'has_previews' => $episode->has_previews ?: false
];
}

return $new_episode;
}

private function calculate_size($bitrate, $duration)
{
return ($bitrate / 8) * ($duration / 1000);
}

private function getResolutionString($width, $height)
{
return $width .' * '. $height . ' px';
}
}
Loading

0 comments on commit a489655

Please sign in to comment.