// mediaelement-player $(function() { function deserialize(string) { var result = {}; if (string) { var parts = string.split(/&|\?/); for (var i = 0; i < parts.length; i++) { var part = parts[i].split("="); if (part.length === 2) result[decodeURIComponent(part[0])] = decodeURIComponent(part[1]); } } return result; } (function(strings) { strings['en-US'] = { 'Download File': 'Open Stream in Desktop-Player' } })(mejs.i18n.locale.strings); var feat = ['playpause', 'volume', 'fullscreen']; if($('.video-wrap').hasClass('has-subtitles')) feat.push('subtitles'); $('body.room video.mejs, body.embed video.mejs').mediaelementplayer({ features: feat, enableAutosize: true }); $('body.room audio.mejs, body.embed audio.mejs').mediaelementplayer({ features: ['playpause', 'volume', 'current'] }); var $player = $('.video-wrap[data-voc-player]'); if ($player.length > 0) { var config = { parent: $player.get(0), plugins: [], baseUrl: 'assets/voc-player/', autoPlay: true, poster: $player.data("poster"), audioOnly: !!$player.data("audio-only"), h264Only: !!$player.data("h264-only"), preferredAudioLanguage: $player.data("preferred-language"), events: { onReady: function() { var player = this; var playback = player.core.getCurrentContainer().playback; var params = deserialize(location.href) playback.once(Clappr.Events.PLAYBACK_PLAY, function() { // Allow custom skip via URL var seek = parseFloat(params.t); if (!isNaN(seek)) { player.seek(seek); // skip forward to scheduled beginning of the talk at // ~ 0:14:30 (30 sec offset, if speaker starts on time) } else if (playback.getPlaybackType() == 'vod') { player.seek(15 * 60); } }); } } } // Select source if ($player.data("stream")) { config.vocStream = $player.data("stream"); } else if ($player.data("source")) { config.source = $player.data('source'); config.playbackRateConfig = { defaultValue: 1, options: [ {value: 0.75, label: '0.75x'}, {value: 1, label: '1x'}, {value: 1.25, label: '1.25x'}, {value: 1.5, label: '1.5x'}, {value: 2, label: '2x'}, ], }; config.plugins.push(PlaybackRatePlugin); } // Show timeline previews if present if ($player.data("sprites")) { var sprites = ClapprThumbnailsPlugin.buildSpriteConfig( $player.data("sprites"), $player.data("sprites-n"), 160, 90, $player.data("sprites-cols"), $player.data("sprites-interval") ); config.plugins.push(ClapprThumbnailsPlugin); config.scrubThumbnails = { backdropHeight: 64, spotlightHeight: 84, thumbs: sprites }; } new VOCPlayer.Player(config); } $(window).on('load', function() { $(window).trigger('resize'); }); }); // tabs $(function() { // activate tab via hash and default to video function setTabToHash() { var activeTab = $('.nav-tabs a[href=' + window.location.hash + ']').tab('show'); } // change hash on tab change $('.nav-tabs').on('shown.bs.tab', 'a', function (e) { window.location.hash = e.target.hash; }); // adjust tabs when hash changes $(window).on('hashchange', setTabToHash).trigger('hashchange'); }); // schedule-timeline $(function() { var $schedule = $('body .schedule'), $time = $('.navbar-time'), $now = $schedule.find('.now'), scrollLock = false, rewindTimeout, /* 10 seconds after manual navigation */ rewindTime = 10000, /* 1/2s animation on the scolling element */ scrollDuration = 500, /* update now-pointer placement every 1/2s */ updateTimer = 500, /* offset to the browsers realtime (for simulation) */ offset = $('.js-schedule-settings').data('scheduleoffset'); $schedule.on('mouseenter mouseleave touchstart touchend', function(e) { if(e.type == 'mouseleave' || e.type == 'touchend') { rewindTimeout = setTimeout(function() { scrollLock = false; }, 5000); } else { clearTimeout(rewindTimeout); scrollLock = true; } }); function formatLocalTime(timestamp, offset) { const d = new Date(timestamp * 1000); // js timezone offset is negative const diff = -d.getTimezoneOffset() - offset; d.setUTCMinutes(d.getUTCMinutes() - diff); return String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0'); } function formatOffset(offset) { const sign = offset < 0 ? "-" : "+"; const hours = String(Math.floor(Math.abs(offset)/60)).padStart(2, '0'); const minutes = String(Math.abs(offset)%60).padStart(2, '0'); return sign + hours + ":" + minutes; } // schedule now-marker & scrolling function updateProgramView(initial) { var // corrected "now" timestamp in unix-counting (seconds, not microseconds) now = (Date.now() / 1000) + offset; // only check the first room (shouldn't matter anyway) // find the newest block that starts in the past // that's the one that is most probably currently still running var $block = $schedule .find('.room') .first() .find('.block') .filter(function(i, el) { return $(this).data('start') < now; }).last(); // if we are yet to start find first block as reference if (!$block.length) $block = $schedule .find('.room').first() .find('.block').first(); // still no luck if(!$block.length) { $now.find('.overlay').css('width', 0); $now.find('.label').text('now'); return; } var // start & end-timestamp start = $block.data('start'), end = $block.data('end'), // place of the now-marker between 0 and 1 within this block normalized = Math.max(0, Math.min(1, (now - start) / (end - start))), // projected to pixels with respect to the schedules left end px = $block.position().left + ($block.outerWidth() * normalized), // visible width of the schedule display displayw = $schedule.width(), // current scroll position scrollx = $schedule.scrollLeft(), // distance of the now-marker to the left border of the schedule display px_in_display = px - scrollx; //console.log($block.get(0), new Date(start*1000), new Date(now*1000), new Date(end*1000), normalized, px); const eventOffset = parseFloat($block.data('offset')) || 0; const eventTime = formatLocalTime(now, eventOffset); $now.find('.overlay').css('width', px); $now.find('.label').text("now (" + eventTime + ")"); $time.text(eventTime + " (" + formatOffset(eventOffset) + ")"); // scrolling is locked by manual interaction if (scrollLock) return; if( // now marker is > 2/3 of the schedule-display-width px_in_display > (displayw * 2/3) || // now marker is <1/7 of the schedule-display-width px_in_display < (displayw/7) ) { // scroll schedule so that now-marker is as 1/5 of the screen $schedule.stop().scrollTo(px - displayw/6, { axis: 'x', duration: initial ? 0 : scrollDuration, }); } } // when on schedules tab var updateInterval; function on() { // initial trigger updateProgramView(true); // timed triggers updateInterval = setInterval(updateProgramView, updateTimer); } function off() { clearInterval(updateInterval); } if(window.location.hash == '' || window.location.hash == '#schedule' || window.location.href.indexOf('/schedule') != -1) on(); // trigger when a tab was changed $('.nav-tabs').on('shown.bs.tab', 'a', function(e) { if(e.target.hash == '#schedule') on(); else off(); }); }); // feedback form $(function() { $('.feedback-form').on('submit', function(e) { e.preventDefault(); var $form = $(this); $('.feedback-form').hide(); $.ajax({ url: $form.prop('action'), method: $form.prop('method'), data: $form.serialize(), success: function() { $('.feedback-thankyou').show(); }, error: function() { $('.feedback-error').show(); } }); }); }); // update teaser images $(function() { setInterval(function() { $('.rooms .lecture .teaser').each(function() { var $teaser = $(this), $preload = $(''), src = $teaser.data('src'); if(!src) { src = $teaser.prop('src'); $teaser.data('src', src); } $preload.on('load', function() { $teaser.prop('src', $preload.prop('src')); }).prop('src', src + '?'+(new Date()).getTime()); }); }, 1000*60); }); // multiviewer $(function() { var audioMeter = !!window.chrome; $('body.multiview') .find('audio, video') .each(function(idx, player) { var $player = $(player), $meter = $player.closest('.cell').find('.meter'), $timer = $player.closest('.cell').find('.timer'); $player.on("timeupdate", function(e) { var s = Math.floor(this.currentTime % 60), m = Math.floor(this.currentTime / 60) % 60, h = Math.floor(this.currentTime / 60 / 60) % 24, d = Math.floor(this.currentTime / 60 / 60 / 24), f = Math.floor((this.currentTime - Math.floor(this.currentTime)) * 1000), txt = ''; txt += d+'d '; if(h < 10) txt += '0'; txt += h+'h '; if(m < 10) txt += '0'; txt += m+'m '; if(s < 10) txt += '0'; txt += s+'s '; if(f < 10) txt += '00'; else if(f < 100) txt += '0'; txt += f+'ms'; $timer.text(txt); }); if(!audioMeter) { $player.prop('muted', true); $meter.hide(); return; } var ctx = new AudioContext(), audioSrc = ctx.createMediaElementSource(player), analyser = ctx.createAnalyser(); // we have to connect the MediaElementSource with the analyser audioSrc.connect(analyser); // we could configure the analyser: e.g. analyser.fftSize (for further infos read the spec) analyser.fftSize = 64; var w = 100 / analyser.frequencyBinCount; for (var i = 0; i < analyser.frequencyBinCount; i++) { var c = Math.floor( i * 255 / analyser.frequencyBinCount ); $('