Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. /**
  2. * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
  3. *
  4. * Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
  5. *
  6. * This program is free software; you can redistribute it and/or modify it under the
  7. * terms of the GNU Lesser General Public License as published by the Free Software
  8. * Foundation; either version 3.0 of the License, or (at your option) any later
  9. * version.
  10. *
  11. * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
  12. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
  13. * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public License along
  16. * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. //const defaultCopyright = '<p>Recorded with <a target="_blank" href="https://bigbluebutton.org/">BigBlueButton</a>.</p><p>Use <a target="_blank" href="http://mozilla.org/firefox">Mozilla Firefox</a> or <a target="_blank" href="http://google.com/chrome/">Google Chrome</a> to play this recording.</p>';
  20. const defaultCopyright = '<p>DigitalCSE</p>';
  21. const defaultLogo = 'logo.png';
  22. // Playback events
  23. var dataReady = false;
  24. var mediaReady = false;
  25. var contentReady = false;
  26. // Content events
  27. var DOMLoaded = false;
  28. var metadataLoaded = false;
  29. var mediasChecked = false;
  30. // Media control
  31. var hasVideo = false;
  32. var hasDeskshare = false;
  33. var mediaSyncing = false;
  34. var mediaSeeked = false;
  35. var primaryMedia;
  36. var secondaryMedias;
  37. var allMedias;
  38. function goToSlide(time) {
  39. var pop = Popcorn("#video");
  40. pop.currentTime(time);
  41. };
  42. /*
  43. * From: http://stackoverflow.com/questions/1634748/how-can-i-delete-a-query-string-parameter-in-javascript/4827730#4827730
  44. */
  45. function removeURLParameter(url, param) {
  46. var urlparts= url.split('?');
  47. if (urlparts.length>=2) {
  48. var prefix= encodeURIComponent(param)+'=';
  49. var pars= urlparts[1].split(/[&;]/g);
  50. for (var i=pars.length; i-- > 0;) {
  51. if (pars[i].indexOf(prefix, 0)==0)
  52. pars.splice(i, 1);
  53. }
  54. if (pars.length > 0) {
  55. return urlparts[0]+'?'+pars.join('&');
  56. } else {
  57. return urlparts[0];
  58. }
  59. } else {
  60. return url;
  61. }
  62. };
  63. function addURLParameter(url, param, value) {
  64. var s = encodeURIComponent(param) + '=' + encodeURIComponent(value);
  65. logger.info("==Adding URL parameter", s);
  66. if (url.indexOf('?') == -1) {
  67. return url + '?' + s;
  68. } else {
  69. return url + '&' + s;
  70. }
  71. };
  72. /*
  73. * Converts seconds to HH:MM:SS
  74. * From: http://stackoverflow.com/questions/6312993/javascript-seconds-to-time-with-format-hhmmss#6313008
  75. */
  76. function secondsToHHMMSS(secs) {
  77. var hours = Math.floor(secs / 3600);
  78. var minutes = Math.floor((secs - (hours * 3600)) / 60);
  79. var seconds = secs - (hours * 3600) - (minutes * 60);
  80. if (hours < 10) {hours = "0"+hours;}
  81. if (minutes < 10) {minutes = "0"+minutes;}
  82. if (seconds < 10) {seconds = "0"+seconds;}
  83. var time = hours+':'+minutes+':'+seconds;
  84. return time;
  85. };
  86. secondsToYouTubeFormat = function(secs) {
  87. var hours = Math.floor(secs / 3600);
  88. var minutes = Math.floor((secs - (hours * 3600)) / 60);
  89. var seconds = secs - (hours * 3600) - (minutes * 60);
  90. var time = "";
  91. if (hours > 0) {time += hours+"h";}
  92. if (minutes > 0) {time += minutes+"m";}
  93. if (seconds > 0) {time += seconds+"s";}
  94. if (secs == 0) {time = "0s";}
  95. return time;
  96. };
  97. /*
  98. * Full word version of the above function for screen readers
  99. */
  100. function secondsToHHMMSSText(secs) {
  101. var hours = Math.floor(secs / 3600);
  102. var minutes = Math.floor((secs - (hours * 3600)) / 60);
  103. var seconds = secs - (hours * 3600) - (minutes * 60);
  104. var time = "";
  105. if (hours > 1) {time += hours + " hours ";}
  106. else if (hours == 1) {time += hours + " hour ";}
  107. if (minutes > 1) {time += minutes + " minutes ";}
  108. else if (minutes == 1) {time += minutes + " minute ";}
  109. if (seconds > 1) {time += seconds + " seconds ";}
  110. else if (seconds == 1) {time += seconds + " second ";}
  111. return time;
  112. };
  113. function replaceTimeOnURL(secs) {
  114. var newUrl = addURLParameter(removeURLParameter(document.URL, 't'), 't', secondsToYouTubeFormat(secs));
  115. window.history.replaceState({}, "", newUrl);
  116. };
  117. /*
  118. * Sets the title attribute in a thumbnail.
  119. */
  120. function setTitleOnThumbnail($thumb) {
  121. var src = $thumb.attr("src");
  122. if (src !== undefined) {
  123. var num = "?";
  124. var name = "undefined";
  125. var match = src.match(/slide-(.*).png/);
  126. if (match) { num = match[1]; }
  127. match = src.match(/([^/]*)\/slide-.*\.png/);
  128. if (match) { name = match[1]; }
  129. $thumb.attr("title", name + " (" + num + ")");
  130. }
  131. };
  132. /*
  133. * Associates several events on a thumbnail, e.g. click to change slide,
  134. * mouse over/out functions, etc.
  135. */
  136. function setEventsOnThumbnail($thumb) {
  137. // Note: use ceil() so it jumps to a part of the video that actually is showing
  138. // this slide, while floor() would most likely jump to the previously slide
  139. // Popcorn event to mark a thumbnail when its slide is being shown
  140. var timeIn = $thumb.attr("data-in");
  141. var timeOut = $thumb.attr("data-out");
  142. var pop = Popcorn("#video");
  143. pop.code({
  144. start: timeIn,
  145. end: timeOut,
  146. onStart: function(options) {
  147. $parent = $(".thumbnail-wrapper").removeClass("active");
  148. $parent = $("#thumbnail-" + Math.ceil(options.start)).parent();
  149. $parent.addClass("active");
  150. animateToCurrentSlide();
  151. },
  152. onEnd: function(options) {
  153. $parent = $("#thumbnail-" + Math.ceil(options.start)).parent();
  154. $parent.removeClass("active");
  155. }
  156. });
  157. // Click on thumbnail changes the slide in popcorn
  158. $thumb.parent().on("click", function() {
  159. var time = Math.ceil($thumb.attr("data-in"));
  160. goToSlide(time);
  161. replaceTimeOnURL(time);
  162. });
  163. // Mouse over/out to show/hide the label over the thumbnail
  164. $wrapper = $thumb.parent();
  165. $wrapper.on("mouseover", function() {
  166. $(this).addClass("hovered");
  167. });
  168. $wrapper.on("mouseout", function() {
  169. $(this).removeClass("hovered");
  170. });
  171. };
  172. function animateToCurrentSlide() {
  173. var $container = $("#thumbnails").parents(".left-off-canvas-menu");
  174. var currentThumb = $(".thumbnail-wrapper.active");
  175. // Animate the scroll of thumbnails to center the current slide
  176. var thumbnailOffset = currentThumb.prop('offsetTop') - $container.prop('offsetTop') +
  177. (currentThumb.prop('offsetHeight') - $container.prop('offsetHeight')) / 2;
  178. $container.stop();
  179. $container.animate({scrollTop: thumbnailOffset}, 200);
  180. };
  181. /*
  182. * Generates the list of thumbnails using shapes.svg
  183. */
  184. function generateThumbnails() {
  185. logger.info("==Generating thumbnails");
  186. var elementsMap = {};
  187. var imagesList = new Array();
  188. xmlList = shapesSVGContent.getElementsByTagName("image");
  189. var slideCount = 0;
  190. logger.info("==Setting title on thumbnails");
  191. for (var i = 0; i < xmlList.length; i++) {
  192. var element = xmlList[i];
  193. if (!$(element).attr("xlink:href"))
  194. continue;
  195. var src = element.getAttribute("xlink:href");
  196. // If it's a full url, leave it as it is
  197. if (!src.match(/^http[s]?:\/\//)) {
  198. src = url + '/' + src;
  199. }
  200. if (src.match(/\/presentation\/.*slide-.*\.png/)) {
  201. var timeInList = xmlList[i].getAttribute("in").split(" ");
  202. var timeOutList = xmlList[i].getAttribute("out").split(" ");
  203. for (var j = 0; j < timeInList.length; j++) {
  204. var timeIn = Math.ceil(timeInList[j]);
  205. var timeOut = Math.ceil(timeOutList[j]);
  206. var img = $(document.createElement('img'));
  207. img.attr("src", src);
  208. img.attr("id", "thumbnail-" + timeIn);
  209. img.attr("data-in", timeIn);
  210. img.attr("data-out", timeOut);
  211. img.addClass("thumbnail");
  212. img.attr("alt", " ");
  213. // Doesn't need to be focusable for blind users
  214. img.attr("aria-hidden", "true");
  215. // A label with the time the slide starts
  216. var label = $(document.createElement('span'));
  217. label.addClass("thumbnail-label");
  218. // Doesn't need to be focusable for blind users
  219. label.attr("aria-hidden", "true");
  220. label.html(secondsToHHMMSS(timeIn));
  221. var hiddenDesc = $(document.createElement('span'));
  222. hiddenDesc.attr("id", img.attr("id") + "description");
  223. hiddenDesc.attr("class", "visually-hidden");
  224. hiddenDesc.html("Slide " + ++slideCount + " " + secondsToHHMMSSText(timeIn));
  225. // A wrapper around the img and label
  226. var div = $(document.createElement('div'));
  227. div.addClass("thumbnail-wrapper");
  228. // Tells accessibility software it can be clicked
  229. div.attr("role", "link");
  230. div.attr("aria-describedby", img.attr("id") + "description");
  231. div.append(img);
  232. div.append(label);
  233. div.append(hiddenDesc);
  234. if (parseFloat(timeIn) == 0) {
  235. div.addClass("active");
  236. }
  237. imagesList.push(timeIn);
  238. elementsMap[timeIn] = div;
  239. setEventsOnThumbnail(img);
  240. setTitleOnThumbnail(img);
  241. }
  242. }
  243. }
  244. imagesList.sort(function(a,b){return a - b});
  245. for (var i in imagesList) {
  246. $("#thumbnails").append(elementsMap[imagesList[i]]);
  247. }
  248. };
  249. function loadCaptions(video) {
  250. asyncRequest('GET', captionsJSON).then(function (response) {
  251. logger.info("==Processing captions.json");
  252. var captions = JSON.parse(response.responseText);
  253. const video = document.getElementById("video");
  254. captions.forEach(function(caption) {
  255. var track = document.createElement("track");
  256. track.setAttribute('kind', 'captions');
  257. track.setAttribute('label', caption['localeName']);
  258. track.setAttribute('srclang', caption['locale']);
  259. track.setAttribute('src', url + '/caption_' + caption['locale'] + '.vtt');
  260. video.appendChild(track);
  261. });
  262. document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'captions'}));
  263. }, function (response) {
  264. logger.info("==Video has no captions");
  265. document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'captions'}));
  266. });
  267. };
  268. function loadVideo() {
  269. logger.info("==Loading video");
  270. var video = document.createElement("video");
  271. video.setAttribute('id','video');
  272. video.setAttribute('class','webcam');
  273. video.setAttribute('preload','auto');
  274. video.setAttribute('playsinline',true);
  275. video.setAttribute('crossorigin','anonymous');
  276. let webmsource = document.createElement("source");
  277. webmsource.setAttribute('src', url + '/video/webcams.webm');
  278. webmsource.setAttribute('type','video/webm; codecs="vp8.0, vorbis"');
  279. video.appendChild(webmsource);
  280. let mp4source = document.createElement("source");
  281. mp4source.setAttribute('src', url + '/video/webcams.mp4');
  282. mp4source.setAttribute('type','video/mp4; codecs="avc1.42E01E"');
  283. video.appendChild(mp4source);
  284. video.setAttribute('data-timeline-sources', chatXML);
  285. document.getElementById("video-area").appendChild(video);
  286. loadCaptions();
  287. checkLoadedMedia();
  288. };
  289. function loadAudio() {
  290. logger.info("==Loading audio")
  291. var audio = document.createElement("audio") ;
  292. audio.setAttribute('id', 'video');
  293. audio.setAttribute('preload', 'auto');
  294. // The webm file will work in IE with WebM components installed,
  295. // and should load faster in Chrome too
  296. var webmsource = document.createElement("source");
  297. webmsource.setAttribute('src', url + '/audio/audio.webm');
  298. webmsource.setAttribute('type', 'audio/webm; codecs="vorbis"');
  299. // Need to keep the ogg source around for compat with old recordings
  300. var oggsource = document.createElement("source");
  301. oggsource.setAttribute('src', url + '/audio/audio.ogg');
  302. oggsource.setAttribute('type', 'audio/ogg; codecs="vorbis"');
  303. // Browser Bug Workaround: The ogg file should be preferred in Firefox,
  304. // since it can't seek in audio-only webm files.
  305. if (navigator.userAgent.indexOf("Firefox") != -1) {
  306. audio.appendChild(oggsource);
  307. audio.appendChild(webmsource);
  308. } else {
  309. audio.appendChild(webmsource);
  310. audio.appendChild(oggsource);
  311. }
  312. audio.setAttribute('data-timeline-sources', chatXML);
  313. // Audio.setAttribute('controls','');
  314. // Leave auto play turned off for accessiblity support
  315. // Audio.setAttribute('autoplay','autoplay');
  316. document.getElementById("audio-area").appendChild(audio);
  317. checkLoadedMedia();
  318. };
  319. function loadDeskshare() {
  320. logger.info("==Loading deskshare");
  321. var deskshareVideo = document.createElement("video");
  322. deskshareVideo.setAttribute('id','deskshare-video');
  323. deskshareVideo.setAttribute('preload','auto');
  324. deskshareVideo.setAttribute('playsinline',true);
  325. var webmsource = document.createElement("source");
  326. webmsource.setAttribute('src', url + '/deskshare/deskshare.webm');
  327. webmsource.setAttribute('type','video/webm; codecs="vp8.0, vorbis"');
  328. deskshareVideo.appendChild(webmsource);
  329. var mp4source = document.createElement("source");
  330. mp4source.setAttribute('src', url + '/deskshare/deskshare.mp4');
  331. mp4source.setAttribute('type','video/mp4; codecs="avc1.42E01E"');
  332. deskshareVideo.appendChild(mp4source);
  333. var presentationArea = document.getElementById("presentation-area");
  334. presentationArea.insertBefore(deskshareVideo,presentationArea.childNodes[0]);
  335. checkLoadedDeskshare();
  336. };
  337. function setMediaSync() {
  338. if (!hasDeskshare) {
  339. return;
  340. }
  341. // Master video
  342. primaryMedia = Popcorn("#video");
  343. // Slave videos
  344. secondaryMedias = [Popcorn("#deskshare-video")];
  345. allMedias = [primaryMedia].concat(secondaryMedias);
  346. // When we play the master video, we play all other videos as well...
  347. primaryMedia.on("play", function() {
  348. for (i = 0; i < secondaryMedias.length ; i++) {
  349. secondaryMedias[i].play();
  350. }
  351. });
  352. // When we pause the master video, we sync
  353. primaryMedia.on("pause", function() {
  354. sync();
  355. });
  356. primaryMedia.on("seeking", function() {
  357. if (primaryMedia.played().length != 0) {
  358. mediaSeeked = true;
  359. }
  360. });
  361. // When finished seeking, we sync all medias...
  362. primaryMedia.on("seeked", function() {
  363. if (primaryMedia.paused()) {
  364. sync();
  365. } else {
  366. primaryMedia.pause();
  367. }
  368. });
  369. for (i = 0; i < allMedias.length ; i++) {
  370. allMedias[i].on("waiting", function() {
  371. // If one of the medias is 'waiting', we must sync
  372. if (!primaryMedia.seeking() && !mediaSyncing) {
  373. mediaSyncing = true;
  374. // Pause the master video, causing to pause and sync all videos...
  375. logger.debug("==Syncing videos");
  376. primaryMedia.pause();
  377. }
  378. });
  379. allMedias[i].on("canplay", function() {
  380. if (mediaSyncing || mediaSeeked) {
  381. var allMediasAreReady = true;
  382. for (i = 0; i < allMedias.length ; i++) {
  383. allMediasAreReady &= (allMedias[i].media.readyState == 4);
  384. }
  385. if (allMediasAreReady) {
  386. mediaSyncing = false;
  387. mediaSeeked = false;
  388. // Play the master video, causing to play all videos...
  389. logger.debug("==Resuming");
  390. primaryMedia.play();
  391. }
  392. }
  393. });
  394. }
  395. };
  396. function sync() {
  397. for (var i = 0; i < secondaryMedias.length ; i++) {
  398. if (secondaryMedias[i].media.readyState > 1) {
  399. secondaryMedias[i].pause();
  400. // Set the current time will fire a "canplay" event to tell us that the video can be played...
  401. secondaryMedias[i].currentTime(primaryMedia.currentTime());
  402. }
  403. }
  404. };
  405. function setMediaListeners() {
  406. // Solo media
  407. primaryMedia = Popcorn("#video");
  408. primaryMedia.on("seeking", function() {
  409. if (primaryMedia.played().length != 0) {
  410. mediaSeeked = true;
  411. }
  412. });
  413. // When finished seeking
  414. primaryMedia.on("seeked", function() {
  415. if (!primaryMedia.paused()) {
  416. primaryMedia.pause();
  417. }
  418. });
  419. primaryMedia.on("canplay", function() {
  420. if (mediaSeeked) {
  421. mediaSeeked = false;
  422. logger.debug("==Resuming");
  423. primaryMedia.play();
  424. }
  425. });
  426. };
  427. // Hack for mobile devices that not load media unless they are visible
  428. function forceMediaEvents() {
  429. // When the medias were loaded
  430. if (mediaReady) return;
  431. if (hasVideo) {
  432. logger.debug("==Forcing video/captions ready event");
  433. document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'video'}));
  434. document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'captions'}));
  435. } else {
  436. logger.debug("==Forcing audio ready event");
  437. document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'audio'}));
  438. }
  439. if (hasDeskshare) {
  440. logger.debug("==Forcing deskshare ready event");
  441. document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'deskshare'}));
  442. }
  443. }
  444. document.addEventListener("DOMContentLoaded", function() {
  445. logger.info("==DOM content loaded");
  446. loadMetadata();
  447. checkMedias();
  448. document.dispatchEvent(new CustomEvent('content-ready', {'detail': 'dom'}));
  449. }, false);
  450. function loadPlayback() {
  451. logger.info("==Loading playback");
  452. var appName = navigator.appName;
  453. var appVersion = navigator.appVersion;
  454. // Hack to force mobile devices to show the playback when media do not load while hidden
  455. var isMobile = mobileAndTabletCheck();
  456. if (isMobile) {
  457. logger.info("==Device is mobile");
  458. setTimeout(forceMediaEvents, mobileTimeout);
  459. }
  460. startLoadingBar();
  461. loadData();
  462. loadBranding();
  463. if (hasVideo) {
  464. $("#audio-area").attr("style", "display:none;");
  465. loadVideo();
  466. } else {
  467. $("#video-area").attr("style", "display:none;");
  468. loadAudio();
  469. }
  470. // load up the acorn controls
  471. logger.info("==Loading acorn media player");
  472. $('#video').acornMediaPlayer({
  473. theme: 'bigbluebutton',
  474. volumeSlider: 'vertical'
  475. });
  476. $('#video').on("swap", function() {
  477. swapVideoPresentation();
  478. });
  479. if (hasDeskshare) {
  480. loadDeskshare();
  481. } else {
  482. setMediaListeners();
  483. logger.debug("==Recording has no deskshare");
  484. document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'deskshare'}));
  485. }
  486. };
  487. function isMediaReady(media) {
  488. if (media !== undefined && media !== null && media.readyState === 4) {
  489. return true;
  490. }
  491. return false;
  492. };
  493. function checkLoadedMedia() {
  494. // We use the video tag both for audio or video
  495. let media = $('#video')[0];
  496. if (isMediaReady(media)) {
  497. if (hasVideo) {
  498. document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'video'}));
  499. } else {
  500. document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'audio'}));
  501. }
  502. } else {
  503. setTimeout(checkLoadedMedia, mediaCheckInterval);
  504. }
  505. };
  506. function checkLoadedDeskshare() {
  507. let deskshare = $('#deskshare-video')[0];
  508. if (isMediaReady(deskshare)) {
  509. document.dispatchEvent(new CustomEvent('media-ready', {'detail': 'deskshare'}));
  510. } else {
  511. setTimeout(checkLoadedDeskshare, mediaCheckInterval);
  512. }
  513. };
  514. var secondsToWait = 0;
  515. function addTime(time){
  516. if (secondsToWait === 0) {
  517. Popcorn('#video').pause();
  518. window.setTimeout("Tick()", 1000);
  519. }
  520. secondsToWait += time;
  521. };
  522. function loadBranding() {
  523. let logo = undefined;
  524. let copyright = undefined;
  525. let metadata = metadataXMLContent.getElementsByTagName("meta");
  526. if (metadata.length > 0) {
  527. metadata = metadata[0];
  528. let logoCandidates = metadata.getElementsByTagName("playback-logo-url");
  529. if (logoCandidates.length > 0) {
  530. logo = logoCandidates[0].textContent;
  531. }
  532. let copyrightCandidates = metadata.getElementsByTagName("playback-copyright");
  533. if (copyrightCandidates.length > 0) {
  534. copyright = copyrightCandidates[0].textContent;
  535. }
  536. }
  537. loadLogo(logo);
  538. loadCopyright(copyright);
  539. };
  540. function loadLogo(logo) {
  541. let logoURL = typeof logo !== 'undefined' ? logo : defaultLogo;
  542. logger.info("==Loaded logo from", logoURL);
  543. $("#slide").css('background-image', 'url(' + logoURL + ')');
  544. document.getElementById("load-img").src = logoURL;
  545. }
  546. function loadCopyright(copyright) {
  547. let copyrightText = typeof copyright !== 'undefined' ? copyright : defaultCopyright;
  548. $("#copyright").html(copyrightText);
  549. };
  550. function Tick() {
  551. if (secondsToWait <= 0 || !($("#accEnabled").is(':checked'))) {
  552. secondsToWait = 0;
  553. Popcorn('#video').play();
  554. $('#countdown').html(""); // Remove the timer
  555. return;
  556. }
  557. secondsToWait -= 1;
  558. $('#countdown').html(secondsToWait);
  559. window.setTimeout("Tick()", 1000);
  560. };
  561. // Swap the position of the DOM elements `elm1` and `elm2`.
  562. function swapElements(elm1, elm2) {
  563. var parent1, next1,
  564. parent2, next2;
  565. parent1 = elm1.parentNode;
  566. next1 = elm1.nextSibling;
  567. parent2 = elm2.parentNode;
  568. next2 = elm2.nextSibling;
  569. parent1.insertBefore(elm2, next1);
  570. parent2.insertBefore(elm1, next2);
  571. resizeComponents();
  572. };
  573. // Swaps the positions of the presentation and the video
  574. function swapVideoPresentation() {
  575. var pop = Popcorn("#video");
  576. var wasPaused = pop.paused();
  577. var mainSectionChild = $("#main-section").children("[data-swap]");
  578. var sideSectionChild = $("#side-section").children("[data-swap]");
  579. swapElements(mainSectionChild[0], sideSectionChild[0]);
  580. if (!wasPaused) {
  581. pop.play();
  582. }
  583. // Hide the cursor so it doesn't appear in the wrong place (e.g. on top of the video)
  584. // if the cursor is currently being useful, he we'll be redrawn automatically soon
  585. showCursor(false);
  586. // Wait for the svg with the slides to be fully loaded, then restore slides state and resize them
  587. function checkSVGLoaded() {
  588. var done = false;
  589. var svg = document.getElementsByTagName("object")[0];
  590. if (svg !== undefined && svg !== null && currentImage && svg.getSVGDocument('svgfile')) {
  591. var img = svg.getSVGDocument('svgfile').getElementById(currentImage.getAttribute("id"));
  592. if (img !== undefined && img !== null) {
  593. restoreSlidesState(img);
  594. done = true;
  595. }
  596. }
  597. if (!done) {
  598. setTimeout(checkSVGLoaded, 50);
  599. }
  600. };
  601. checkSVGLoaded();
  602. };
  603. function restoreSlidesState(img) {
  604. logger.debug("==Restoring slide state");
  605. // Set the current image as visible
  606. img.style.visibility = "visible";
  607. resizeSlide();
  608. restoreCanvas();
  609. var isPaused = Popcorn("#video").paused();
  610. if (isPaused) {
  611. restoreViewBoxSize();
  612. restoreCursor(img);
  613. }
  614. };
  615. function restoreCanvas() {
  616. logger.debug("==Restoring canvas");
  617. var numCurrent = currentImageId.substr(5);
  618. var currentCanvas;
  619. currentCanvas = getSVGElementById("canvas" + numCurrent);
  620. if (currentCanvas !== null) {
  621. currentCanvas.setAttribute("display", "");
  622. }
  623. };
  624. function restoreViewBoxSize() {
  625. logger.debug("==Restoring view box");
  626. var t = Popcorn("#video").currentTime().toFixed(1);
  627. var vboxVal = getViewboxAtTime(t);
  628. if (vboxVal !== undefined) {
  629. setViewBox(vboxVal);
  630. }
  631. };
  632. function restoreCursor(img) {
  633. logger.debug("==Restoring cursor");
  634. var imageWidth = parseInt(img.getAttribute("width"), 10);
  635. var imageHeight = parseInt(img.getAttribute("height"), 10);
  636. showCursor(true);
  637. drawCursor(parseFloat(currentCursorVal[0]) / (imageWidth/2), parseFloat(currentCursorVal[1]) / (imageHeight/2));
  638. };
  639. // Manually resize some components we can't properly resize just using css.
  640. // Mostly the components in the side-section, that has more than one component that
  641. // need to fill 100% height.
  642. function resizeComponents() {
  643. logger.info("==Resizing components");
  644. let availableHeight = $("body").height();
  645. if (hasVideo) {
  646. availableHeight -= $("#video-area .acorn-controls").outerHeight(true);
  647. } else {
  648. availableHeight -= $("#audio-area .acorn-controls").outerHeight(true);
  649. }
  650. availableHeight -= $("#navbar").outerHeight(true);
  651. if (window.innerHeight > window.innerWidth) {
  652. logger.debug("==Portrait mode");
  653. let mainSectionHeight = availableHeight * 0.6; // 60% for top bar
  654. $("#main-section").outerHeight(mainSectionHeight);
  655. let sideSectionHeight = availableHeight - mainSectionHeight;
  656. $("#side-section").outerHeight(sideSectionHeight);
  657. $("#chat-area").innerHeight(sideSectionHeight);
  658. } else {
  659. logger.debug("==Landscape mode");
  660. $("#main-section").outerHeight(availableHeight);
  661. $("#side-section").outerHeight(availableHeight);
  662. logger.debug("==Data-swap children", $("#side-section").children("[data-swap]"));
  663. let chatHeight = availableHeight - $("#side-section").children("[data-swap]").outerHeight(true);
  664. $("#chat-area").innerHeight(chatHeight);
  665. }
  666. };
  667. document.addEventListener('content-ready', function(event) {
  668. logger.debug("==Content ready", event.detail);
  669. if (contentReady) return;
  670. switch(event.detail) {
  671. case 'dom':
  672. DOMLoaded = true;
  673. break;
  674. case 'metadata':
  675. metadataLoaded = true;
  676. break;
  677. case 'medias-checked':
  678. mediasChecked = true;
  679. break;
  680. default:
  681. logger.warn("==Unhandled content-ready event", event.detail);
  682. }
  683. if (DOMLoaded && metadataLoaded && mediasChecked) {
  684. loadPlayback();
  685. document.dispatchEvent(new CustomEvent('playback-ready', {'detail': 'content'}));
  686. }
  687. }, false);
  688. document.addEventListener('playback-ready', function(event) {
  689. logger.debug("==Playback ready", event.detail);
  690. switch(event.detail) {
  691. case 'data':
  692. dataReady = true;
  693. break;
  694. case 'media':
  695. mediaReady = true;
  696. break;
  697. case 'content':
  698. contentReady = true;
  699. break;
  700. default:
  701. logger.warn("==Unhandled playback-ready event", event.detail);
  702. }
  703. if (dataReady && mediaReady && contentReady) {
  704. runPopcorn();
  705. if (firstLoad) {
  706. initPopcorn();
  707. }
  708. }
  709. }, false);