From 342df5510a4cbd73dff7063b4e94b8d6c364271e Mon Sep 17 00:00:00 2001 From: Reaby Date: Sun, 25 Aug 2019 20:06:55 +0300 Subject: [PATCH] fix backend memory issue, better handling of preload images, please wait screen while preload, add memoryprofiler infos --- app.js | 22 +++--- data/bundles/default/bundle.json | 2 +- modules/admin.js | 117 +++++++++++----------------- modules/adminLobby.js | 126 +++++++++++++++++++++++++++++++ modules/bundle.js | 10 +-- modules/bundleManager.js | 12 ++- modules/display.js | 61 ++++++++------- modules/profiler.js | 25 ++++++ modules/websocket.js | 55 +------------- public/img/wait.png | Bin 0 -> 37286 bytes public/js/display.js | 60 +++++++-------- views/display.twig | 2 +- 12 files changed, 287 insertions(+), 205 deletions(-) create mode 100644 modules/adminLobby.js create mode 100644 modules/profiler.js create mode 100644 public/img/wait.png diff --git a/app.js b/app.js index f65a20d..35a04fb 100644 --- a/app.js +++ b/app.js @@ -5,6 +5,8 @@ let logger = require('morgan'); let lessMiddleware = require('less-middleware'); let path = require('path'); +require('./modules/profiler.js').init("./tmp"); + const EventEmitter = require('events'); let config = require("./config.js"); @@ -25,9 +27,7 @@ let LocalStrategy = require('passport-local').Strategy; let User = require("./modules/db.js"); passport.use("local", new LocalStrategy( - { - - }, + {}, function (username, password, cb) { User.findByUsername(username, function (err, user) { if (err) { @@ -64,13 +64,15 @@ if (config.mediaServer) { nms.run(); } -passport.serializeUser(function(user, cb) { +passport.serializeUser(function (user, cb) { cb(null, user.id); }); -passport.deserializeUser(function(id, cb) { +passport.deserializeUser(function (id, cb) { User.findById(id, function (err, user) { - if (err) { return cb(err); } + if (err) { + return cb(err); + } cb(null, user); }); }); @@ -103,9 +105,9 @@ i18next caches: false, // ['cookie'] // optional expire and domain for set cookie - // cookieExpirationDate: new Date(), - // cookieDomain: 'myDomain', - // cookieSecure: true // if need secure cookie + // cookieExpirationDate: new Date(), + // cookieDomain: 'myDomain', + // cookieSecure: true // if need secure cookie } }); @@ -120,7 +122,7 @@ app.use(express.urlencoded({extended: false})); app.use(cookieParser()); app.use(lessMiddleware(path.join(__dirname, 'public'))); app.use(express.static(path.join(__dirname, 'public'))); -app.use(require('express-session')({ secret: config.sessionKey, resave: false, saveUninitialized: true })); +app.use(require('express-session')({secret: config.sessionKey, resave: false, saveUninitialized: true})); // Initialize Passport and restore authentication state, if any, from the // session. diff --git a/data/bundles/default/bundle.json b/data/bundles/default/bundle.json index 8df663a..778b2b6 100644 --- a/data/bundles/default/bundle.json +++ b/data/bundles/default/bundle.json @@ -1,7 +1,7 @@ { "displayName": "default", "background": "bg.jpg", - "duration": 30, + "duration": 15, "transition": "tiles3d", "useWebFonts": false, "displayTime": true, diff --git a/modules/admin.js b/modules/admin.js index 08ab749..41aa27c 100644 --- a/modules/admin.js +++ b/modules/admin.js @@ -56,11 +56,10 @@ class admin { dispatcher.on("updateBundles", function () { io.emit("callback.dashboard.updateBundles", { - "bundleDirs": self.bundleManager.getBundleInfos() + "bundleDirs": bundleManager.getBundleInfos() }); }); - io.on("connection", function (socket) { cli.success("WS " + socket.conn.remoteAddress + " connect with id:" + socket.id); @@ -143,14 +142,13 @@ class admin { } view.io.emit("callback.load", view.getSlideData()); self.updateDashboard(io); + + view = null; + serverOptions = null; }); - socket.on('controls.blackout', function () { - let view = self.getView(); - view.serverOptions.blackout = view.serverOptions.blackout === false; - - view.io.emit("callback.blackout", {serverOptions: view.serverOptions}); + self.screenView.toggleBlackout(); self.updateDashboard(io); }); @@ -173,23 +171,23 @@ class admin { } catch (err) { cli.error("preview instance not found"); } - + bundle = null; }); socket.on("controls.skipTo", function (data) { - let bundleSettings = self.getView().getBundle(); + let bundleSettings = self.screenView.getBundle(); let slides = bundleSettings.enabledSlides; let idx = slides.indexOf(data.fileName); if (idx >= 0) { self.getServerOptions().loopIndex = idx; } - self.getView().mainLoop(); + self.screenView.mainLoop(); }); socket.on('controls.toggle', function (data) { let fileName = data.fileName; - let bundleSettings = self.getView().getBundle(); + let bundleSettings = self.screenView.getBundle(); let idx = bundleSettings.disabledSlides.indexOf(fileName); if (idx > -1) { @@ -197,8 +195,8 @@ class admin { } else { bundleSettings.setSlideStatus(fileName, false); } - self.updateDashboard(socket); + bundleSettings = null; }); // announce @@ -210,8 +208,7 @@ class admin { } dispatcher.emit("all.override", data); } else { - let view = self.getView(); - view.overrideSlide(data.json, data.png, data.duration, data.transition); + self.screenView.overrideSlide(data.json, data.png, data.duration, data.transition); } } catch (err) { cli.error("error, admin.override", err); @@ -225,16 +222,12 @@ class admin { } dispatcher.emit("all.override", data); } else { - let view = self.getView(); - view.overrideSlide(data.json, data.png, data.duration, data.transition); + self.screenView.overrideSlide(data.json, data.png, data.duration, data.transition); } - }); socket.on('admin.setBundle', function (data) { - let view = self.getView(); - view.changeBundle(data.bundle); - view.displayCurrentSlide(); + self.screenView.changeBundle(data.bundle); self.updateDashboard(io); }); @@ -253,27 +246,9 @@ class admin { socket.on('admin.reorderSlides', function (data) { - let bundle = bundleManager.getBundle(data.bundleName); - - let i = 0; - for (let uuid of data.sortedIDs) { - bundle.setIndex(uuid, i); - i += 1; - } - - // calculate next slide order for all displays which has the bundle selected - let display = screenView; - if (display.serverOptions.currentBundle === data.bundleName) { - for (let slide of bundle.allSlides) { - // calculate new index for next slide; - if (slide.uuid === display.serverOptions.currentFile) { - display.serverOptions.loopIndex = slide.index + 1; - } - } - } - - bundle.save(); - self.updateSlides(io, bundle.name, bundle); + bundleManager.reorderSlides(data.bundleName, data.sortedIDs); + dispatcher.emit("display.recalcBundleData", data.bundleName); + self.updateSlides(); }); /** remove slide **/ @@ -281,7 +256,7 @@ class admin { try { let bundle = bundleManager.getBundle(data.bundleName); bundle.removeUuid(data.uuid); - self.updateSlides(io, bundle.name, bundle); + self.updateSlides(); self.announce(null, "callback.removeSlide", data); } catch (err) { cli.error("error while removing slide", err); @@ -290,8 +265,6 @@ class admin { socket.on("admin.createBundle", function (data) { - cli.info("@ create Bundle"); - try { if (!fs.existsSync("data/bundles" + data.dir)) { fs.mkdirSync("data/bundles/" + data.dir); @@ -372,7 +345,7 @@ class admin { cli.success("save web link"); socket.emit("callback.saveLink", {}); - self.updateSlides(io, data.bundleName, bundle); + self.updateSlides(); } catch (err) { cli.error(err, "save web link"); @@ -402,7 +375,7 @@ class admin { try { let bundle = bundleManager.getBundle(data.bundleName); bundle.setName(data.uuid, data.name); - self.updateSlides(io, data.bundleName, bundle); + self.updateSlides(); } catch (err) { cli.error("error while renaming slide", err); } @@ -482,8 +455,8 @@ class admin { bundleName: data.bundleName, uuid: filename }); - self.updateSlides(io, data.bundleName, bundle); - + self.updateSlides(); + self.dispatcher.emit("lobbyUpdate"); } catch (err) { cli.error(err, "save new slide date"); socket.emit("callback.save", {error: err}); @@ -521,7 +494,6 @@ class admin { socket.emit("callback.edit.updateFileList", {error: err}); } }); - }); // io } @@ -531,19 +503,21 @@ class admin { * @param {object} socket */ updateDashboard(socket) { - let view = this.getView(); - let serverOptions = view.serverOptions; - let bundleSettings = view.getBundle(); + let bundleSettings = this.screenView.getBundle(); socket.emit("callback.dashboard.update", { bundleSettings: bundleSettings, allSlides: bundleSettings.allSlides, - serverOptions: serverOptions, + serverOptions: this.screenView.serverOptions, displayId: this.displayId }); - this.dispatcher.emit("dashboard.update", {displayId: this.displayId, serverOptions: serverOptions}); + this.dispatcher.emit("dashboard.update", { + displayId: this.displayId, + serverOptions: this.screenView.serverOptions + }); + bundleSettings = null; } /** helper function to call announce easier **/ @@ -551,23 +525,21 @@ class admin { this.dispatcher.emit("announce", {screens: screens, event: event, data: data}); } - updateSlides(socket, bundlename, bundle) { - socket.emit("callback.dashboard.updateSlides", { - bundleName: bundlename, - bundleSettings: bundle + updateSlides() { + let bundleName = this.getServerOptions().currentBundle; + let bundle = this.getView().getBundle(); + this.io.emit("callback.dashboard.updateSlides", { + bundleName: bundleName, + bundleSettings: bundle, }); } syncDashboard(socket) { this.previewInstances.push({adminId: socket.id, preview: {}, currentView: {}}); - let serverOptions = this.getView().serverOptions; - let bundleDirs = this.bundleManager.getBundleInfos(); - - socket.emit("callback.dashboard.sync", { displayId: this.displayId, - bundleDirs: bundleDirs, - serverOptions: serverOptions, + bundleDirs: this.bundleManager.getBundleInfos(), + serverOptions: this.screenView.serverOptions, displays: config.displays }); } @@ -585,7 +557,7 @@ class admin { * @return infoscreen3/display.serverOptions */ getServerOptions() { - return this.getView().serverOptions; + return this.screenView.serverOptions; } /** @@ -597,28 +569,29 @@ class admin { controls(action) { + let view = this.getView(); switch (action) { case "previous": - let view = this.getView(); view.serverOptions.loopIndex -= 2; view.mainLoop(); break; case "play": - this.getServerOptions().loop = true; - this.getView().mainLoop(); + view.serverOptions.loop = true; + view.mainLoop(); this.updateDashboard(this.io); break; case "pause": - this.getView().clearTimers(); - this.getServerOptions().loop = false; + view.clearTimers(); + view.serverOptions.loop = false; this.updateDashboard(this.io); break; case "next": - this.getView().mainLoop(); + view.mainLoop(); break; } + view = null; + } - } // // admin } diff --git a/modules/adminLobby.js b/modules/adminLobby.js new file mode 100644 index 0000000..b84bae0 --- /dev/null +++ b/modules/adminLobby.js @@ -0,0 +1,126 @@ +let cli = require(`./cli.js`); + +class adminLobby { + + /** + * @param sharedIO + * @param dispatcher + * @param screenView + * @param adminView + * @param bundleManager + */ + constructor(sharedIO, dispatcher, screenView, adminView, bundleManager) { + let io = sharedIO.of("/admin"); + this.io = io; + this.dispatcher = dispatcher; + this.screenView = screenView; + this.adminView = adminView; + this.bundleManager = bundleManager; + let self = this; + + // create overview admin lobby + io.on("connection", function (socket) { + + let arr = []; + for (let display of screenView) { + arr.push({displayId: display.serverOptions.displayId, serverOptions: display.serverOptions}); + } + socket.emit("callback.serverOptions", arr); + + // handle other events + cli.info("WS/" + socket.conn.remoteAddress + " connect"); + socket.on('error', function (error) { + cli.error(error, "WS/ error"); + }); + socket.on('disconnect', function (reason) { + cli.info("WS/ " + reason + " " + socket.conn.remoteAddress); + }); + + socket.on('admin.reorderSlides', function (data) { + bundleManager.reorderSlides(data.bundleName, data.sortedIDs); + dispatcher.emit("display.recalcBundleData", data.bundleName); + let bundle = bundleManager.getBundle(data.bundleName); + updateSlides(data.bundleName, bundle); + }); + + socket.on('admin.removeSlide', function (data) { + try { + let bundle = bundleManager.getBundle(data.bundleName); + bundle.removeUuid(data.uuid); + updateSlides(data.bundleName, bundle); + self.announce(null, "callback.removeSlide", data); + } catch (err) { + cli.error("error while removing slide", err); + } + }); + + /** rename **/ + socket.on('admin.renameSlide', function (data) { + try { + let bundle = bundleManager.getBundle(data.bundleName); + bundle.setName(data.uuid, data.name); + updateSlides(data.bundleName, bundle); + } catch (err) { + cli.error("error while renaming slide", err); + } + }); + + + socket.on('controls.previous', function (data) { + getAdminView(data.displayId).controls("previous"); + }); + + socket.on('controls.play', function (data) { + getAdminView(data.displayId).controls("play"); + }); + + socket.on('controls.next', function (data) { + getAdminView(data.displayId).controls("next"); + }); + + socket.on('controls.pause', function (data) { + getAdminView(data.displayId).controls("pause"); + }); + + dispatcher.on("dashboard.update", function (data) { + socket.emit("callback.serverOptions", [data]); + }); + + }); + + function updateSlides(bundleName, bundleData) { + for (let aView of adminView) { + aView.updateSlides(); + } + // update admin lobby + io.emit("callback.dashboard.updateSlides", { + bundleName: bundleName, + bundleSettings: bundleData + }); + } + + /** + * @return admin + */ + function getAdminView(index) { + try { + return adminView[index]; + } catch (err) { + cli.log(err); + } + } + + } + + announce(screens, event, data) { + /** + * @event event:announce + */ + this.dispatcher.emit("announce", {screens: screens, event: event, data: data}); + } + +} + +module.exports = adminLobby; + + diff --git a/modules/bundle.js b/modules/bundle.js index 765d92b..24e246f 100644 --- a/modules/bundle.js +++ b/modules/bundle.js @@ -119,21 +119,15 @@ class bundleClass { } } - toJson() { - this.sync(); - return JSON.stringify(this.allSlides, null, "\t"); - } - save() { this.sync(); try { - fs.writeFileSync("./data/bundles/" + this.name + "/slides.json", this.toJson()); + fs.writeFileSync("./data/bundles/" + this.name + "/slides.json", JSON.stringify(this.allSlides, null, "\t")); fs.writeFileSync("./data/bundles/" + this.name + "/bundle.json", JSON.stringify(this.bundleData, null, "\t")); } catch (e) { cli.error("error while saving file:", e); } } - } /** @@ -166,4 +160,4 @@ function sortByProperty(property) { * * @type {bundleClass} */ -module.exports = bundleClass; \ No newline at end of file +module.exports = bundleClass; diff --git a/modules/bundleManager.js b/modules/bundleManager.js index cfb379a..df6158f 100644 --- a/modules/bundleManager.js +++ b/modules/bundleManager.js @@ -51,6 +51,16 @@ class bundleManager { } } + reorderSlides(bundleName, sortedIDs) { + let bundle = this.getBundle(bundleName); + let i = 0; + for (let uuid of sortedIDs) { + bundle.setIndex(uuid, i); + i += 1; + } + bundle.save(); + } + /** * * @return {Array} @@ -90,4 +100,4 @@ function getJson(file) { /** * @type {bundleManager} */ -module.exports = bundleManager; \ No newline at end of file +module.exports = bundleManager; diff --git a/modules/display.js b/modules/display.js index 55c6309..f8a456c 100644 --- a/modules/display.js +++ b/modules/display.js @@ -1,3 +1,4 @@ +'use strict' let fs = require("fs"); let cli = require(`./cli.js`); let bundleManager = require(`./bundleManager.js`); @@ -16,16 +17,6 @@ class display { */ constructor(sharedIO, dispatcher, meta, displayId, bundleManager) { cli.info("Starting display with id " + displayId + " ..."); - - /** @property {Array} timeoutId - holds setTimeout id's for mainLoop */ - this.timeoutId = []; - - /** - * @type {bundleManager|module:infoscreen3/bundleManager} - */ - this.bundleManager = bundleManager; - let self = this; - /** * @property {number} displayId - this display id * @property {number} currentId - index number @see getBundle() @@ -59,11 +50,20 @@ class display { displayTime: true }; + /** @property {Array} timeoutId - holds setTimeout id's for mainLoop */ + this.timeoutId = null; + + /** + * @type {bundleManager|module:infoscreen3/bundleManager} + */ + this.bundleManager = bundleManager; + let io = sharedIO.of("/display-" + displayId.toString()); this.io = io; this.name = meta.name; this.dispatcher = dispatcher; this.id = displayId; + let self = this; /** * helper callback for global announce @@ -76,10 +76,20 @@ class display { self.displayCurrentSlide(); }); + dispatcher.on("display.recalcBundleData", function (bundleName) { + if (self.serverOptions.currentBundle === bundleName) { + for (let slide of self.getBundle().allSlides) { + // calculate new index for next slide; + if (slide.uuid === self.serverOptions.currentFile) { + self.serverOptions.loopIndex = slide.index + 1; + } + } + } + }); + dispatcher.on("announce", function (obj) { // if global announce, ie screens is null if (obj.screens === null) { - console.log("announcing: " + this.serverOptions.displayId); io.emit(obj.event, obj.data); } else { // screens is array @@ -141,6 +151,7 @@ class display { this.serverOptions.transition = bundle.getBundleData().transition; this.serverOptions.loop = true; this.io.emit("callback.load", this.getSlideData()); + bundle = null; this.mainLoop(); } @@ -153,10 +164,7 @@ class display { * clears timers */ clearTimers() { - for (let i in this.timeoutId) { - clearTimeout(this.timeoutId[i]); - delete this.timeoutId[i]; - } + clearTimeout(this.timeoutId); } /** @@ -166,6 +174,10 @@ class display { return this.bundleManager.getBundle(this.serverOptions.currentBundle); } + toggleBlackout() { + this.serverOptions.blackout = this.serverOptions.blackout === false; + this.io.emit("callback.blackout", {serverOptions: this.serverOptions}); + } /** * emits custom event for screens @@ -208,8 +220,6 @@ class display { } if (this.serverOptions.loop) { - let that = this; - // override slide timeout if set by slide let slideTimeout = this.serverOptions.slideDuration * 1000; if (this.serverOptions.currentMeta.duration > 5) { @@ -217,11 +227,7 @@ class display { } if (slideTimeout >= 5000) { - this.timeoutId.push( - setTimeout(function () { - that.mainLoop(); - }, slideTimeout)); - + this.timeoutId = setTimeout(this.mainLoop.bind(this), slideTimeout); } else { this.serverOptions.loop = false; } @@ -231,6 +237,8 @@ class display { this.displayCurrentSlide(); this.serverOptions.loopIndex += 1; } + + bundle = null; } overrideSlide(json, pngData, duration, transition) { @@ -244,20 +252,17 @@ class display { if (json.type === "image") { fs.writeFileSync("./tmp/display_" + this.serverOptions.displayId + ".png", pngData.replace(/^data:image\/png;base64,/, ""), "base64"); } - let that = this; + if (duration && duration >= 5) { this.serverOptions.loop = true; - this.timeoutId.push( - setTimeout(function () { - that.mainLoop(); - }, duration * 1000)); + this.timeoutId = setTimeout(this.mainLoop.bind(this), duration * 1000); } this.displayCurrentSlide(); this.serverOptions.transition = _transition; } displayCurrentSlide() { - this.announce([this.serverOptions.displayId, "admin"], "callback.update", this.getSlideData()); + this.announce([this.id, "admin"], "callback.update", this.getSlideData()); } } diff --git a/modules/profiler.js b/modules/profiler.js new file mode 100644 index 0000000..60de4b2 --- /dev/null +++ b/modules/profiler.js @@ -0,0 +1,25 @@ +var prevValue = 0; +let cli = require('./cli.js'); +module.exports.init = function (datadir) { + setInterval(tickHeapDump, 1000); +}; + +/** + * Schedule a heapdump by the end of next tick + */ +function tickHeapDump() { + setImmediate(function () { + heapDump(); + }); +} + +/** + * Creates a heap dump if the currently memory threshold is exceeded + */ +function heapDump() { + var memMB = (process.memoryUsage().rss / 1048576).toFixed(1); + if (prevValue != memMB) { + cli.info("Memory: " + memMB + 'Mb'); + prevValue = memMB; + } +} diff --git a/modules/websocket.js b/modules/websocket.js index c38e7af..b85f26d 100644 --- a/modules/websocket.js +++ b/modules/websocket.js @@ -1,13 +1,12 @@ let availableDisplays = require(`../config.js`).displays; let display = require(`./display.js`); let admin = require(`./admin.js`); +let adminLobby = require(`./adminLobby.js`); let cli = require(`./cli.js`); let _bundleManager = require(`./bundleManager.js`); let chalk = require('chalk'); let fs = require('fs'); - - /** * @param server * @param app @@ -70,46 +69,7 @@ module.exports = function (server, app, io, dispatcher) { screenId += 1; } - // create overview admin lobby - io.of("/admin").on("connection", function (socket) { - - let arr = []; - for (let display of screenView) { - arr.push({displayId: display.serverOptions.displayId, serverOptions: display.serverOptions}); - } - socket.emit("callback.serverOptions", arr); - - - // handle other events - cli.info("WS/" + socket.conn.remoteAddress + " connect"); - socket.on('error', function (error) { - cli.error(error, "WS/ error"); - }); - socket.on('disconnect', function (reason) { - cli.info("WS/ " + reason + " " + socket.conn.remoteAddress); - }); - - socket.on('controls.previous', function (data) { - getAdminView(data.displayId).controls("previous"); - }); - - socket.on('controls.play', function (data) { - getAdminView(data.displayId).controls("play"); - }); - - socket.on('controls.next', function (data) { - getAdminView(data.displayId).controls("next"); - }); - - socket.on('controls.pause', function (data) { - getAdminView(data.displayId).controls("pause"); - }); - - dispatcher.on("dashboard.update", function (data) { - socket.emit("callback.serverOptions", [data]); - }); - - }); + let adminLobby1 = new adminLobby(io, dispatcher, screenView, adminView, bundleManager); // create lobby io.of("/lobby").on("connection", function (socket) { @@ -136,17 +96,6 @@ module.exports = function (server, app, io, dispatcher) { }); }); - /** - * @return admin - */ - function getAdminView(index) { - try { - return adminView[index]; - } catch (err) { - cli.log(err); - } - } - return {screenView: screenView, adminView: adminView, bundleManager: bundleManager}; }; diff --git a/public/img/wait.png b/public/img/wait.png new file mode 100644 index 0000000000000000000000000000000000000000..54e728867ee3c26cc05e8de45dda86ee9c158618 GIT binary patch literal 37286 zcmeFZc|4SD`v-i@jAbwwWf_D)3ZaobWKdZu$-eJf*_V(dGxjW5lBMh-OLnsFYe*8Z z?;*03?Bczq=lA@cyWjo1pU?Z}`^W7cvs^RRah~UK9>@3m9^Yf0sH!MXk~5J506?jv zcwG$uh{1=@0XPZx?}P7@7Wj|ITK0}C02FtN5r=&RpEJ7M&~a0Dv~u$>b+H6wEga1( z(MtBF)|P6PrWTK!-&sllK#aK3by*G1@oy~f7tJn4Dx z5h7srmwa*8N4N^)_Z!LpWV95<2tLNio{a46lXF7ggg4B|nxC?C6CNRcR5f;KqsXv? zC#YNhT>sDf{NoScR`_SX{1YGkiHHB>i+}RtKltGvyz~z~{09&Gg9rY>1OMQGfAGLR zc;FvA@c#i12(V-U2&gQrCHqDAD2dBy@2fI8yPyg!(-hb%9n;=Zlad3SP|~~LQ9HV~ zPk@Ch`evQJnI`L!1J2M-rE6psAuUH1}? z7%$J#e%-+O6f65!n%su~v%R?dT34^ZqF-(cersRFV=o@=9ueQ#tdkbQpo zXvW1+LsPF@1lNU93u}NRyT(h{Q_kuY{B(E#q}fJkKIQ^|=4nnq(&IJ}1_4zS&Uf<9 z$ZY{myexV~1g55+0TWFy82|z_zAT?Pd&*&^IsC-o*fG=Gs%WFpqx235FgwKwczf8u zz=?*G#HT!vq@KVy(kXG}ud4V)U!IwzzyQsYUsW|bo1!?(ESJ2;^&miE4LG`xn*a`j z0E&eyPHF03Lng8rCz`fdW@bd~FMJZ~N-s3AJamDITVZ4(LeTON|5b6t0HFr!nG3Qz?ARxcEtW+`NoXK@EftD zM-6pN93FYAnDX_AP(FTZG62|Gr@^sERzQ$pcpz-mbLy4)!{;hLF0vm$VbFn1t~l>!`@WAADzqIh2;RuYgS@$)WddY>(ST&gsow%1wvhg$ zvoZx$jf1d(e&cafzK;LAuFUGb6O%3k@Dw95S$_HiWS-FQOmfOkYG=5|L;PMTHo3s}KXh@=Q~c)#~{IGhR`;nuq|xCTj4 z2s$tW<3nM2s;g!hfQVk*c*r1&7&#@S=b-o3482B9a9yD;X!^>N{F9PTHZPl`$-j5^ zUE~;lQRi4ke(FH+gnS~ABw=zOY513TX)r!CDN^zzKmSfmJorKDV&L0UDJ6hLz*;iu zPqh%ZK~*#)#%A-T(?KLL7Qm(M_&7_pi05rRZx2g*t9q3W3T%lIo6Jzf0Ku|ofa)#% zyHme-zk%6#HO)#}Y!=ev<5Ur7`xoah9q*xU>N|zJ3I`uwu?cP8mc4eAFkDCiRC^Me zY*O6;aCj6Ta3o~WI=-R7O>5McU#p(P&fyG&0+=eJ-2A%DE*T}SgR19#;P?~(_YD-W z2`aF)MC{!o7u;lLr5MtSggshw%Y_Rs_c}I1{h&!{;Nw^J-_G$(a+8DORa}{W^TEr_ z4^}H9-U))8Ysq!D3NTm3hvXCoA<(C~TU-ifI5~Hc1`W-54b@ zL58-LT&iiOH1TGJJDfS!DShSHcj7Z@iE?tar!X3#a9cL&rzB`F#`sSki3%`V`!!zT z4i`*~th`L?$+OR!UGKoG^{Kzlzb<5C{IYBQPJ$~HPqyUo+4tJEDJS<%p1Efmo|9g;xTBwjw`%*nk5T^kTX3ec5#b#ssS&yg$iF8DIHsDnmCc!i{$$(p8_jS z^wta(#K=$XPq(3XYu*g49gS99J6HqVv)`L6{UaSN%Y-(<{~m_?W(zlp*uwwx^wb!b zcSC<72~7)VfBfr^*r>sMd+AQ*dkODdKF=dQyDe%W4Gj$p$tf1*($Ud@I+UHKqM|bK zHm*;%i$J)Iu-R~Nj?c~rqen>Rd(l)jC1)lc;apr?x+iayY&BQCpkdU|(#m`(2|+c+ zVkd>K?u-5F-v2TLdkui6e}pvu+akiNQ1TYgtSBfC8@iEnKHzOrXGaH3_TFquz-&w1 z>$_>0p%bH{Cb`6v(d?A3SV-ruL_fqp2Hf>EH8r<*We1v>d4NT$0W6CRF>7cfks1D^ zH+Z_5qAAFHZKM!orFiQWr>jRRO5y3bmV8!5+*^7NkF0ZrI;0h#Ge3;{-?;%TQKbT) zHg15^UuG-|M?MQ0w~W&ej__L@;_(kKO1NxnwY9gW!0L2FW}fc2?E7vcQ{}}^ftGqwaQ&Pv?M{Z2O{OVQ(qRCR`*zRx3u?4 zIG%)@_3E#wiP;_Uy3g`4PHkLdRz+D!4&K1>klI6UC3VO3(;>x#e0=(z(VI%Mi2F6Z z1HlJy`{e$K>QwK~wYigsWM^`dsvM&j-L~PvSvg!6X(Ja@!Hm2~rNuZo$?F4Yd@8|2ZOZEX;Tof*sg38t9R(DF5D^o|OfAv_&ngw8v; zxLo1p<;~1(a$je<;=xt>2waC5(pLyZMn-v@8wIi;pj|e=J6-k~@&Xjcmq+v>XzhV% zwL-}OS;Y%v5{2+G#ej5rVNX29!_(8#WCb?$AcE+{P14gp&7c4$)^$%5@W-Np$GTF9 zC!1SY?o7fymvWT0NJcKYoNxF4;d_R>)mVNfDBz3<81eARJk}V9p>kf*GuK_N(t?VG zwG|3!UqR~{nO{Zy2X(^y6sc`TcW2qII>tOyzj&-J(hx2v8o=Z}UNm+zC)d~WT-0%r z1N@>=2gsyR!#pr%`s&zvZZgJE#beINl$=H>@RX02rG(W-{5=Lmre zXg>Z`8YD2YDRp3QqJ6^G5EKIGWM}|Ik=+OMC zHA}PmV)Wjvv|x|+6>BSJ@+~c8-Kk%PR~5s(t@ZuBZR%Bg^!E4C#thcbN(P(=@>(HY z7>4a`|ZgOBaaUyQEDscHM#k{HEOGq zo>xIjkEXzFw08jr#Grybx>>a?^CsBCEv25M`@D{K89vg;p&=@WW;;U|)ZY8+BUgKi z`CdASB+CC#+5W*Hw^_j3y!U!fqpUPxv*u`I(BO)?e4(gMr)(7ywbd@!0pSjNA|UAV zJ_pF65N%~8?qA%eJ1N|@P;Xd2#G~_KXsY&faa+%t_bKB5BmkI5GqS{du;BS3g7`&~ z{62rzYk!H|#3?6kSLU+WGTTT+7@NfM9nIHk4+@<nJCls6qqt3jX4Lr6W}66SmhA9cwy zdOg(Z)ZGaWeZIGsxmjhUmC$K?qgnS@^7G`B%$Q$ha$Bse=W~_TG2!dh4wu=~C$?t~ z!J&P7_;0_shq5{^>ICrW>yeWZ2Usc=TZ4k~kdAZLnmqp)v=9V27Pa<4FR{g=Cd8eT*X~{U^Na~FhtiEFgjEZCpvOoJ#QBEl3)F4an^_kC$`F)-t%p&`qGloVM7 zolnWID?-1@q2mFm&BV7N!@2=dNH~7UfXIxRd}@=jT^rv=n~1X6ay&Uc&>0TU;!#Pu zLaYlHWzvDSyP$)Ulh4U3D#BGHWnC1-h?vz^L;jkv{O{q&|4XV+#>K>Z^oIxnBu0WE z!X-h_urO8nT7KMg1qF2LUK23|J?8juhoa!|K+Pjks&giaii%kJ5!n1Xw!9x!dg+OS z2eH`z_Y6JBEBkk789u{WTAvd((GsioOk4CO+@3_^Cltb@75SyC7sTzyg_Gw1X!->H z)8Jrqke7n`CosSNJ6+s_>;PCm5XyW%E8~As|G(bCMh4SyE<7SxS(O=(@U!kuZjsm` zW*RLoD>D-Z^Z)G?+7d%6Dv@Q;Xy|1cg9;lOc{@8kBku%${m$sr)c&n6y$o)7QHW?r zM}X=s%Xx``_4@RC(gRoIWJrd36`NrZXD{4F+lX~jE9yUY#XnB;KSz~5>|3B+d;#U2 z&i^4lKi`Cj{5Yon%(?&AN&qIb01jYc0seS3{bp8JOzAnrhq%%7!lsLNQ?ECDgTV~y z+#_2C29S@Q8zW2X-?G}!+el2_js{lzBh0&=(p0bsJB@t&*n;srbhCru&*S_0ZU^t~ zy1x-aal!`ZE!o)EPz-M7WRl2$U8zm~pteY6G1RN<>?dUmizhwG`=tS&ai$s4FUtPN z65ON!MEEKo{s)q|A3$FAWe13mvF<-tpJ0TBRmc_}HNQNQM ze;tbK-)`Jl`z&P8=#yk4gX=#}RJaRE*$Z*I#;yWfJ^Ikz9yr$;M1jdrPM?s(ZIDE2 z`LlqS2i{njr~@!n$0Z~v7!j`^gk5KB$eI5-bLj|xXkZ1H{bTL3DS2P<7pF7Z%U1n7 zL(%AWN;Q3W`17lJqUzNWUr`DhD~4!8@-uJ*!a}FYf#ux>H06EO{ZALRu(MrJY>XR| zvfqG_;bG#mHPTu|A~$ux@%QaSE%P1WjPvvJCI$ns@{DK_e#>K$Gr*MA@$rG{*1_X$ zVDYKHKeVLD+uK`Ei*)4kXCYw`k(O!-oN0{si`G9U69!6b>g2%?i9xifH3YS^)8$$e z-FodsWVJ-1b&!a$F3~}3K11OttxaYg?~@S@eU0aGB0e&Ez3g<WO;-!b0wUDNP+o!^3NU2%AWR2z;w zbTHqRsKWkq8G2}aas`Z^jw-+7CzyoE+f-{Iu+iE;VzRTC0Agm$6K2fwtn?3P!Os59 z`O|#c8r6oyjcSk}0Jh${&@mnP2K$X~E;op=2fEhp*z<{X+9a=uiQlwc*RY+sJ;B4x zlRYCPzU)JNN~=>;xIy+sUq@H@>s(qcL#><7>+j~o+3%fxnk|Kc!}BQzBkNQqfM_#8 zZM0aCev;5ZKL~lU`u=7ao1B=o;P$ng3{`E-LoO*o81Ea~;0U&k(5S5O9-;B3i#aCK zzCkNy*4W`y=q1Q2bdEq4 z5I_%Yu;CY8(fWJI9h9SOJ^U)HG_E=iOt<|g{RL6-59ip4vEUpf6eT*${IIsV#ba4p z?4L%COX4URC)<0~Z8sQhL{nPT*_B?mC-n0qvqoJ*cVzde{_2FUsi<@To6nHm_JCkx zNI!2MEoHx<7897~#a*OHy)oD8V@FS zVZ`p^L#392X}^Y>gLtH5i5?^zZbFd385pUbqN$&T*S?&QD?&apk&me6b~qKQq*bO! zVa;F%hycLTanU4VVqQIZ_!OWGGO?r5lkgYpaGIPSPKS9#KtR zrXW|mWtlcOXgRGPDU+{7Q@z~>u3)1Ck;&$UJwSxVAnF#M(+bMoP;F-<%tKiR za=#p%51jG$U!{OR2`jjlgHO1!Ia`^=f3u2sr=eLNQ=On@r;M^4tH^*P?L9`FKe@BV z#bNEMyEZ*<@yH=ExlU=>K#X3lqzaZ0H<;BcNSMhb>|JyM? zPB;g}DC@1mEc-1w8W}uXp#98_#AXXwvAVv{6@_89H(_39f_Pts@<=0@h0{G}n<2>o z4_^6y{q$PM!qSqRq7pWxe#62O*9xsK{@mP*n_GPM3{}A4jK6H$h_SJ;!ASX%HyMnw z*=*)~)DZn6D>8!8%xRFRhQscm`rX8y*mqVl5_zv-FOW?F0&{>Dl07t6w)ZrkIfer! z_2Y~KA)xsPvB)d)BATge1ix$rq{(0yzgN8Q~h`_?I+NxD=SDz`?nz&&(ej zpZ0uePQ1|BSfv0Zc;*_uC$TWj=)?4p!183h7K4#;1J5t5J70X>if%~8yX=fTSS^Xg zjf`v0WY8}mTYcw?^_)T&PR#`@C1%u>4I{ogN9qNqxOUCPg#Ba#-SnLhQE_G_Lhl*Y*}yY8@(?CIpMB?N@2H z$vpA+EPnW=hzV!4Yp~9L&-Gq-6VI_^&iDDvm8N*{rK7Q_V)sSnN;VN(Y->~G7Bx5w z8A2=-B*&pq!E-93$7vbyHd`vJ8B?X|)gB}};ek$}C7pYCOb8&sV9& z%H0&#fW4FNjKuanq|ualHpy*C@JeR!!1xw2~7|B+VY57(0hkbgTO48hX*%e76< z4P?`&uNSWX!n47m;@W#4m5>*wgBWU}R2+kSCq=SVw3d{W#in12J{r)Pc?5lbRk=tK z#mLx(8!|Tj%%w)Yk%#OH@{|S4aB>W=z4`f4NY5v9^d0|nZew-Az zexmE7=@gfvn~l+rW%D|~(OF^hnL~+9%;eNyy`;3X zG}G2VGQ{0fDbs4_1(G&G?7DvA#=V~ehVI)`T>?7>gPf{1NPl$e=Ch$cK;Rbujk`$- zC=k%~zmEmE05~KH{E!h^6!*j#8S69tUtgM2aWg~R4zpQbiaR~3E4EHo5 zGoeIrn+`)q$IC_yXc6$RV2Z(Du=FaB`EabzdN(}y9VR$Fp1v*nRs!nB_O_tbuMF75 zix-0*u8jyv`Rqrt^Yc^c@-nSF-TFB6=SqP=;_oCNgp<>pKic2;S8rPzjCCJY=o20p z*`b>D>=}&Jv%I3>I=v{#JU_NPLb2pCvEmf=?aRo>$c&1LxVhtlZ3M_b61@i~p{Y`j z&Pb_-5G~^O(F4aCXC|Swq1P$eNeZQi3RgKhA6o!wGOkOvxAL*)2d@1WWZJ$D`o>4q z|4bUV!ssf&j_wu6tG8|YMMXu3aC&~T=;&y)^6Ag7pBTf!!&|I(Wm|Ymj{1_}-i3^4 z>vchn32E?zMnXqNS9T1ojo=gzpz?VFKa)#R`#KW7=LX4BLo!#i(g?$q1OUTKj?0&$ zd<35$1}N_;s89T5!EQnx0buFG!w!mt%bV(0lN)>?Nje`O-c~pZXsLKyzg}&tQt_*) z=K{Qiw_fpgWb2#!zT@}pL1Ay342U8+pLw^@na2eUN=D43&45Lt!pilzH1*-sbI0e; zZY%EnQreClyeID~t+zM~ol64q$v1BhSUj}{NKDaToo(6KmZ{%bFh+f9ZUP*yp)ql% znyP5-ToseEgt%>YMJU@{*W)f^8kI%zs8<7aSJuQ?Wmgj&Z|FX3Bi~}Yc^cRANzA}Q zM$g;w@#oe-PZFB65BH3&FU3s|Fw-W8nd&4UC=v*UY15Dc24{IkY1kTbE*);ak|F$a zdB5QqGBIu`+&Z5gr2Zhp$E^T_wvv)B!Cc=J+Meb|^yQ>pL8sD1;&qac*;nY}a7 z#>v-{K06xcN1`A~N}V7Slr9YdZfnq=gGOawjYaOmzri zZ`c7TqI*+@pXF_hq!k|$4oR6cJTT6>PJ8B!{~!VFi4S>ymY5XP)?_f3y4v-^P-0o6 z!u$Jf(PuqFo-d#4-z0r)FmC$sHfgOqFK9SpY;rdU`P2v+7J7@PFeCgWI8R&eLEPmn zfkzQRF;e`xPfQpgwxi~Da&Vp&Gd`0j3AmQYIoWQWx?8*ZehkK*sPDqv|MG>T&)?cn z9p0zvl=XTAI<{QPQm>V5?dv8!eV%F1UDom2pkzPhN{ov|ncqCKWp?0FM}I}?CNewJ z)uBD7iNY`QGFs$*?y^Vep3Yfq@YF*nyMUa6=KtjslmftWI-4S0^S+**9)h2re^wvw z3>gTM9l)91xkGt4GUsG+WJedXn+j74#ykdTmA1?q;V5wl38h_FxAgPp&sQ$d$Tm~2 zq`p5N7{n6&0@j}2J)hotyjGKhq-0jwI_Wk2%UoPRvTACv?3XT4_-+-4j<(3; zHr&KMponM!sTLA_ef<)aSHQpnndoa3Elu87JNcejwxK&AI1O^7pFBOOVJrtbJ3iy2 zKSgqu&fg}$FET5@?+<&1z$5P8_yXcBFtQ-fbddHf8tH)>C<=N$>^;(2kT>{2Z9hw0 zar&9m=B5ne?jbbqChGLtSUF;8X$e6}&3ay0W+nOBK)msQvebDDTj&nnij8YndSJp| z_OJ%)GZ5M8y3}VX?lcRG=*MPdW}@EOjrsd5V02J6RLeH`O#d7I!MRGO`Y)k(6`zJh zuzi72K6vEqoyCn25|aO1{u8uz99#d~;1=GC8K(h_pw@f1^7ZRGr{jI+`a`U~qwFO* z*<~aL-Xi^x681Rg_Ob=6^h_v(X-m>)A0rQD7bD7a%vJYqD42|l%x!puuehtQ#GmyR z(*MF<&;gvBoHmjhS5d}#dQ4Ke*#!mRnF3N$$r+iMP+e}O+^RseBha=cq+$*9yFQZ5 ztsG;M%`V_H*Ge3Gc<5!3F@cEYZ`$UD!)~C7n<6bhf^U>WsuZd!jvwO0Pk+?>lSCYX z{Fk+2!LWof$|At{{U*Q91rvy5Zy33ZQ1@ zx>F%%aSk)XpFWxI{~&%mnR*c<)&dYmvH|WBcpXGLI1+1d*CoVZ2a`@q45tEWa&od+ zKR9ES4z?&4`{{fwq+;^>nNUimW1sykc3o34mynVYOqq8z&f(zNi=N**^xxZ~8)VWz z@`v-cC}NJPjfC%>F-?diL;Q-yREYGrQ-`X2_^-R{I0}zk1E>Ze$?!S#>!#V>I_W@V z$6aX{8Fvb?nENh-@Rlm(I`y)gx&7lq?t_<(&%>!Fmg_%M`bEednH=xGD*vG5(m&sj z5Enb7XY48mH(&eN$q$}kbzI2^rCR_FmTo_vb)ybJj<*Rtszi!WUw|J3wm&%Y`PA{N z5)-8gTO$V{cN~{!n*GiTK^c;oO){YB|-1E|Q%_Sh>@{M&2=I3VQ$MIZ5 zQWsgxy-n9Qt@?E(mNnqA&($=P^M2*&tX?+nv+bt;eRBfMGl7LeO;EwA#%+iQtmD&h z`^1hCQm!ZJs-=-w7X~V3>4`lhg#WRy2GO?jZw}-lPj>zd>P=PUoxmC0qlVAkm)}%M zKlHe^HK<3iJULo1spD7BahO*{J9WRPcf2}o(9?BGGp(WMd!u;JK_klb5G9Aqj!S!+ zTplWzdia*;OKFb^XpQI6-{JBiEKbvLg~1P})i5?be&y%Rj$pF1cYH=(-V=5~L7FzC zw|AVN46_-Zm4yW#k(w^%+RC?=`i?F16Qej%h-^TtYBRL` z2d2 zp=>9+Dj!c?jh{ueu$;@9Ju@uVk;(q5;bi89F9)xu7k4aSq4rbO=$3O-HXFJn{5s4c z)W2;!)8_f275?j!!-r69wy2857nQrdM-D@=v7IOyxkNFIy%@DKdTW9{LlSHZy=o1a zLqodCj4wYt+ka3$p9ms?<7F;TS`-CEBLYnKPh6z~qVo#O?`m*+0taEwE5b61Hia1B zDamq=xqqKwJ@_W-#_O!Dt|gBvM3nY=3;n2Nm|vo|^)rUg#l4g4(vgPjef9X~P7I)7eAr(x2W2nTNT8U6siPhJyh;&OuBtTC45He>$v>?x#*foFBo0^(#KRfPPc3jnW z=^@DgWzCfx&X)0En=ZV_o(pkI(eBMcdF%dzMpk1(VTTB3&(BX4i+!xXL7t9*`WFz; z1>(68FE4D&QK>BQF>=^TO`f1)=%+gTCNinbQzxi?HEPzBL}>a&PPy{ z&)DbQRy2|xzK`NXe=rNHuDssbWL@OJX-F2^N;twDNGvfUJv*8s;Qbu3}(Ceaiw z*3EXWo6Rm(MWaj3naA#kWZ}iC#!hc3dh%q`Y5H-a35EgthSFCn9~-*$zyFeY=u6?gU1ax)E8zqsN{`RgZN;`fej@w~4xylEZi5HMQ2Ra@D`UB72hXxO=3 z-u|HN(}=^o$YvI&%CWRfVP2dZJ5i6sW+kn8vbOp)@6F4<=M6Z01cn}#2~cEr4;36V zsA?ICf$En)_*o2Jyoru~(HA6f3%#8A%4z)6maoLl^xg0$79_!~ldDAgKY6c^Ehj$_ zY?N6-woJ4rjtnR86HAibBK0a;S85Mb;#hgCFcF- z;)48munne)$GRAjLoGA2=CN|?kEB!#5jkalE&f4!AcQQAB9%kJ({r`wcqj3}fsa3! zRZTg_u2jEg?QD-P5xO2xrk^-^^4UhUwO!|qNiY!_WQnA`KQ^EiW*Fq)>dIf2SBeL{ zJVIW0+$!90^?dX2^k?IuRI-}^sqW+Ie$sM?ANZ=lj!85EOn`g0lb&5F_>oq&H|<=^0u!e z0>hT|^t>sr2FfqFy?V-SgOyjkJil}5aAPo_OCk2R?(5h*&)D`(Cn_z>w(!K}i7s4r z*~fk}&7Jy?!B1Uz(0i|P{Gckel4)hEzr`EKHH$d0If6z>(mSW%vg5kMi`_+Pi&~ZX zXE$?)NpCN$$@d@ZJX{nz+?+)1To3@Ir$Dnz-S5B-$}WRUuO>WI=XRJy(Q7-CCpg9R zT%rs#p#8b;_gW7=$|^q?{d?!8SLU2dsM~6Od9G&E#jA^&u3u9sj*GqGTW7z@B5Pdw zdbKuM+`#Zln%!H~9n<6r+oKFhPZw)7u36I=iRF`#^pC1EceW_MZ-)*?8M-&>tu08~ zke3BViTBzRgTbwt+TrggU4lD65L@uW$)5NosjjZ>ir94PF4P)YdM=dx;>9jiLqp2X zFeVEk%5MDIKDid>^hOm_NKA}i6vXR@iV0 z0J^T$ISv>zGjs6bqJ>3<(Ron=!4|g@_fbB&@v@&VXZhX1|jn&t_Jx>?yQJvZCXwYn+7r z3mhvJ?wIzzs!-wsg+C*T+!2EP>H#)O$eZE~WdVn+mXMq2ODP-uFT4Sb%u zzo_!GZTe_ftoxqlZlR~6bGf7uJ;TH%U;jeJ_4jdunMSRzav;nK1(FqyC+n8143x)& zi9QNvAru;qz#D==df|7{XhS2eo1&tcX<0itt4W?bW#xfcWW4%*{!J{$9x^?F`z!b9 z-K%&6UFKEkwEF~G4NUIN*^^zfPbjSPQr--BVW!-WWa-G!RZ zj`ZnFFP2zm=HAVKj_6ca1v+9WWe^%kGJ7j|(H`4+CgMA9mkEU=Jz9&enM8O9(M;c+ z6uRs^u6ttaLhL`TV|e0e^Lsw)t%Wx|(>v6EL>4EgJ+)k~3|)^oUom2|u|2I`xTEs} zHexhm^i0Y+A_M7vtT4k_DDoxworHoiPfbT8_UV$Z*ZNdl+@LYJQ>x{M&19)by@dcy zVw8Ek@978H1>!W2oCcbYL_uNDZw*OA51mv%!Yu96)Q@K|LgGTDSF63?ukPG_z+!N2 z<`z6Udh2Y?lC2CBN0@h+k9rd%S^j4qzSM41DwMR2U=LTpkIs<=Rdr>EmTNl#JU^kt}s8y;_l6UJ)^PSFV zXw(G$Ya0)UG@EHP){s{j$u5)leY5gr#^+D>R|=gJUFbwTC}dxrJY5ueSFNqL=EReI z@oc0e5s$Q_+l_Vs?Xw>~kl*Q8SPTw0SI-J&2*2ci)hz=s$Qx}sxE?fROL{!L%T`Bjhw~AI9tl!Af$*PlB-V12BJQbK7msKe$ zdbsiGnD@en-a>4YEQ_}(;*si}DN+*nMK+lrl_t3iDwl!d1p-M$C?B?9Ci9|)+lah` zK0Gt`thmiJs9a$?HEsQal(^+<8ygf$NMQ%u)puFG_ShEZ{1BI5Nb`$R%W;-ajKKBx zJ=I$qxzYZvaYXNgr&_;FO7s0DrP19)#zlMDm=DLdsy)l>!Q1Ut695kQNEU!!C7HmG zEtI~_hz5e5mmaQ_Wc|9%MR3CUgbusT^6x@UEP6owleHSFT}v8G!;R_wr6d2+tk9t> zA&ZV=Ss^5W*E$&`)Tb^Ir2JS(C3=@Lq}?Ys!sO(6q!sUOPSMhy?8t|L#Gy&kaPV`p zZ-qbgTq`VJ?e2cip^8mv3cg>+(qK&SR3S!NNP00jB$0vU`wsZ)8!uA`>mUvUfMWB@ zv(kx)Ua_gs1uqg5<@2(>{`5QhV1DsBR*_(8vWMmDMsYwqynw;rjd_QF+nTPU)_y=-#kgQTH;5=^U{5w@(??E@rt(-^{=*!}N`J;!Y z$&F6xD)6atcQce2)X_?@!gsHKv#%l7O*(ksyH4&uVJ_n%0w=fYTR5VyZ|pmFuJYnu z;d_4SOFZcG?yk*N<8B4bDfVS*$C&dU-^{*RSn3&8o*Hu>ILSEFAzoEJE)TEPW8CBhRh0rsrW%NnWi zg0K%$RVc33Yr{@PIZLbu(-RpgJSuxTyTU`h9P@k0hZOXjGyX<6&_mwGgEkA2H)G(~ zk6yZagQXX96~4|9IS!1X1LGZZ$xrb_UDyjxlp$a!F@e%ZjF)OUSf$Avum-jGew}dP zd}fy&8EQN=HMN;r(V_b!zi|ouv@F{*kvj3#JuRcN;1)MX_`4Pdz+Aa<74n@CU5>aw zGJXiAv2gXfCMM_rumBK2!E_9xWStHnn{4v)Qw(a!Q-2*@diz@~;0nkAWr-n;p;z{r3=vSBBX;GhqMZCNm}lj+pp@wG(|Y_y_+$Q1Xn{{1#o3 zw{__tnyES|?ZyJ8V^ycX@+4KL64Ao{#z zL**={vwe5LH0SBafJ*^;AVAufJKuctRaw-fvAQVcYW$_@0Ly$hV{2}-20rkW(s#Dd zisF|&#pVQfKE>w!TQPS=Unqa7zoB%_okuwk~3y`}Mh}a0+ z@o(9Y8~r6bZ2zp*cL77kEk`~8fD@y`pM3Z+%5l@!DlDJX_w*MfibhRyg0sSJcEiky z)9x;dcMKh9O^rT?@>NLzWFU0*_JxzvOJjW(vXhcqJQKRl?}}nUYG&(-aHW=aW7ewP z!TY|~)@;Y>ko(t@#Ym4*EnoM82J!!Du$+dw#TrPLx73we>rK7pDAq6Im*%O+ z=yPf>2paqv!$XhN(_XL>=jv{x#3uDdSuM@PW1O~)o+cCv;BKY?-$qNxnz3-=0cM_U z_{RM&8dOfNlo;UW4W|qE0;$<9NrSmBHOV%y#;~VPQ?sCMW{Jg*@8H{I4g+SOIpi(y zZL*XM00HX9=VdY$A#Y+KL=x=uVJ7CBry_C{WpUe{1V^bB`s7)d67XoX)OFAK0!QbY z#@Zs=dkQ92k00tNgoUKb<-1w(!UO zmd@2wl5d4Z@ZU%3#h*cslcpZixI9r@*Y~BFn-j3n7+#>A4@(-p$~G_lF3_c^7^pki zTGS^vH66i+&f!dv$AcZ?!Zg?gtiz<`oHcylA6Z><#z+)k{Pe25}3NsqmHj(aG@>(`HGY`ox5 z3-V7^iu!#&zrUUBGn8d~J$Ye97}S^4j}dz1M8S?XTH^BO9zV_`WyD%Gk04|Bp*cS@JfOWEp{ru_le2+}#L$^Ajs@n7eIz z=Rskc(bGe#a*Td>@zTt~H^0$<&tO@wWSRbVml{B11|%B8yHvjgnkw_LKoF|}_ouZ* zHo=PB)|J$Jum0OFL>njGbw4)D2S@8cWYYEYJ^+AiR3ygSEs(*1=0NIpRf_16DX#aM zDKGZG<@aQ~m;Ez2Uc=s}^4asgZzCC0U{F)|yWn9w-ONI`lf8qay_TqA8z{XB5s|~b z5hwDWha#VO33gp3S^2`NI4gpbfzH2fe!Cj}k@oMBng7?{BCGq$Zz`XZLFq*2`~{ zzx^@-cIWCrLup1aD9WJ!CFO8G`$cWGHaU>tsy0Im=zgM+u3IZ<=1zw0iLddNB+1x9?#&N9OIC`f1hxu!{;IS=noEMAe=`1h!yRD`Jm?h z?<^dBr7a_0FA&4w@N_2y_1{V$99Du%?;zyLenNC~^X%s42P6`S%U~6TSDUtx1V1@@ zk%O8f7eAo%!T?It_TSs<| z?hGeqy`Fa(HYfJo2A|LCgOU zZkc?Y9JB6VxHkOs6r|AhDr4f6sS}+G>Gm# z#>&D=CUtKG>)%G9tEeS0v)9Upz1)z0=_i!>KO-eZM7thiSyqqsHUp}9GEzQNp+@J{ z87Z@W`!)dG$Lc?Rd~GZ^{VUgME7t(83|fW6OjUQEk=yMB#Ki)7?N?IPz5iqr1nd8i zz(LLcl*b~%!nhB8S1{O`%db$Q-y<0*Gye7U5KnxdmS~I>Jl`mp1kx*MjdmbOc|yL} zNHQ;hMexEN4GjDDqrYu#lHN^6LtcL5{?DEvq5iG&1j|!_szAd%oya^p#`pISook_$ zf}79d;@F^IpKmjoV*`m1($-uD>ru&{?qG2;o+KV$$P?;q;4XCNX{$wsUNTv0{-BF zhYb-L34=sKd6v{EH3#ICpQJ~nKVJHB2~o#ueK$1}JfVw52#$C@Ale@liRv@(d*%=x znw4+mQ+uR!2{>-M1dhvw@Vf|rlO}y`KhtgfTmHFuVd33PbJehb>o{UGV7J1oLt~0; zrqSFf+VXiM!`;)jeqwa;Yc(Vf+AN&|FiFGy*ZWE!>2!h=34Z%bhZ(ro5~pZc1@E@g*wV)SaQ7w00f(d}<+LkzS|T)U^wiD@iqY#U~Ul z$$X_UpmhUA*+(+f{R`J)#mX+~h#rH4iJ5XUCp54P3?9+A{9sr8MVsF69h*ZIO3gGh zQSHpR9}2azhWhvO6Q?kYj*f=b|DYyXg1*-aj->)rbEbgGKXOpdoI61qzgSFie4{b;Gi zx>6w-wG6K8s;aWf$$mtlqKv$kTIqv#1Bbp0(9E_xe(DUZ_O>*&+@mDGya55Y0CL~W5pCa zu0+e2gj3!&O&`SauzJ`+@(MJrY>*EW)+$=5o14%i2jiD|)o$gP%+qd~0jIiOwfkA;&a1Qfa~G=iw*7)>Al$Upk>egIl0 zhlaVZ@CJpSi8)Ql$BVP^Sp$}>;N^i^I4r>t$xIBD+%WbWm%z;*D?2w_$`Tcu$fUG;A5J$_r zEmd`+s~HYxzmaLHwmti7s|IY}yr0T(Ng|rscp{2ouA`K%_eH{j&U~tR`=tNpxp0H; zclG)`J2%dyq}zRompb^tl?-$fphx%MU~<}K_JEqRcf z;5k-kiEXDSq7_SPh>t;`i635{qp8O;gtkLtq_E^9iY;(fJPo&6j?*zq=u?XG-SkSX z7aK_4E&HAJE7G?Yd;6o|{#%Z6`%i{2ou08>(UT!xA5{w)#1{bZ397}Av2~1UxdObUnNS&!3_2|}kS{(MSlF`c} zR#{&Q>D9*X)0&!eA5`)gd{R~0S|x~fME z3|#&rVnCnNB(!MYPA#BDY8r}Y&S=zaJP&0v%e!P zC&#b{M*HoLJxm~vGtMa53z|n6N|OxVg=g5^3O{j#tg_p#gN5}Uy-iZYnj?kzlnE=V z`R>v=8j{Ol{Asrf<4;TrZ~iPimb9*8z4X1ctI5_@et5{NQi0fd-#Q{Dq%Vx$HS#{B zG}u@*{V8wJxL$l{of^*Yhjf~;Vq4L5^{~qKSeA?8BfpfM9@rFhTk%_c>^d=2<&!_~ zjg|m=-0JtZ_7v<#4g%}1tEBtw9QSFyQ-*{6{bdxF39aO`V6`{Qi(5fq^XshaPb{k( z>v=8`nm3H+PF_9;8Oljwef1om1sn1ohrVmFhugn_pKrh05#C94B`i@X@y*-EIq%tj z@lKEkwcmVOqGIyadtu{8P=(zO>Qc3o0tRajnwV~mlOO|a#X;0rMmkjCjk?$=(u%^} z=zf!)%Imk>9jWG*kw#_fdG9Nl+DI$zYr_{W)*F3FrLXH%BUm{v1k&v(bp*Pdn3=>B zE@}qT$}mA_U8gpsO(If-vt`r?&|P?9N00^sLL7664_Lz@zM|r24mVuAkAon=*Ct`sz33^POfkjz5bb_R6tV zWqN`M(UOtRoZs5C9wmf73x7EOGDIh;Ppq{4fok1!;Zufl001asK#Gg;70A8G{=8yX z^zdc>J%ttWsSiv8xnn^&_)UEJrRS$p2%-O{ovZwcs{glVn4n<@X>e$emhK@W3y>8M68U1Vca+B}5;*+i2QJUF?nKY%!z%XoYbrw8WX+M5 zDduJ)^gNXbija|=T|GH&nBuoGnUMm#{5|#rj3&jtvIXE$U37f>619-Nd^?0E!lP!ITS7eGA<^^T0V)fR$2Y9}hhi*B z!~{QgFHV%w9VoA>$_McQvLk=Jg*l6 z%fGlfNL__sU=v^YFLA>I@vnN*7FwOJSHBIY7M^__Vby%F#doYhYn(W0LYt7L2T*WF z?5HL}Bq|37BSf>mX~!@s{SHAqeSlzG9)4=eXPB*6>~OoWd09@tlgdf8@~NmHHw zv`JT;D8KApEQ=V*>mgxff>l#1JxfnWh!Mj#aQ`_*=Qiv8Y#4Q~p4Sz&UsKguR@&W} zlD<5m{BGfrI2gEr=K_7D%HfPrV7P|COvzZMmrW`%s-o96`l2tb zq#KLGQ!;RNpw;fEo`Zp>^9bISFbt{kOL)6C->{8@K`H58Ij+4!ws5zbc5fPAk40(L z;cb6B(A299DT;2=jq`}X92)@HR~2@Vti^|}9Jp4#%UM4gw!UTrV*Cc>vO1D6)BD2F z0+<(M)qNpB>8RV&`;j|%z)f`rPA8Lp{Y9I<<(7bb|E|@ z8Mw+X@dWIW3Ow=b*SgD{%qHwheFuhWIhX%CiQC+ypW6U=edy)YTzX=RzV@Iv#HpzA z{$Yh{+vBPj{o=}EhsgJtX>`?X)Z~ki!iu_T32x?sYLn%0A0qEIm5YoA6}0dGNw;$~ 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 z zen0mI!AD}e#yJeV9nxf-%AEKUX2a>N2!%pR4?$vORRy|ASZ3K_kg@$=-?TKD8MJdV z-AO%lsS?fNL=j~oYHD1|h{Qt@5)z@U&>5)r7F$&)GSMuN%b1idQE8e`+%*#FFI=mX z7>K?X9!c3LZeb0LB}Gz?M^SjmGXZRjg``nEvh;)cG>OmR`qohL;BdcnDq4iIc~G{D zeSMNxi&$tdRGOdaKwpeXuER0CA$-PG8J34AQjqvG6-d&u^rpksIKxnyMt0x z&05CEN673JR7v?fL%j+)85xi&SXxFk)`#*EFv!4jRXO59B>Ar!P&;`2qoE+V5Bbl) zL4|XCI_*MkP(?cuB4nvdtFxZa!{;4SQO+PYs>ur|{chk0{zcnGtw74Fzb+BEXALF$ zN%$OtmR~`XSvs)@+$30+#rIa&z&V%k*%-M(cC(1*5!k8L*>pseXyERS~Ep35$7ohi4 z`Jb2@BJ{*D|1X>fCqXOc)C?_rl_psBOLk5LMw)JiEl1GY#v$IL%?%q3yOarHxr9*O zYl*u%2sk?2irC!E=wzBgg1%Q3wYN=BWj{#WmJ^(E8l$d zrmH|?z?qu3&OGD2E`En!v}}kk#FkL{MmLC!`zX_W6!pxKG%EA0uHC54Ec<14mw%_W z005DxCHZG!{k1R+%&`&?aqAp90!-hivS_Z><6I{s$hXqbE2*S5#4I#is2zcZ1{1tq zqr>fJvt^dc;GQdf2xA&UdE7EHx_pi1(KK1bJoU5NkOD5_CQ^bTGd_+1p65Z=#2|;- zR6{Su5+!T2X1#eyd~(%5vAzG`08~iFcNTvCr4QRY+-By?o$(g|;PRcVpdRKDE@roi z;Waf=55J&({|NOy4kf+Tw8_sOz;&sigGu5napR1T19UGp7Mv2;{NnLJmrU{{Q0zA zIIX8n>&Mf2`Lv&K$_GyQz$qU%4NHCFwid1v= count) { + setBackground(bundleData.background); + if (checkStream(serverOptions) === false) { + nextSlide(data); + } + } }); } } catch (err) { @@ -484,9 +482,9 @@ function getDataUri(url, imageId, callback) { var canvas = document.createElement('canvas'); canvas.width = this.naturalWidth; // or 'width' if you want a special/scaled size canvas.height = this.naturalHeight; // or 'height' if you want a special/scaled size - canvas.getContext('2d').drawImage(this, 0, 0); callback(imageId, canvas.toDataURL('image/png'), {src: url}); + canvas = null; }; image.src = url; } diff --git a/views/display.twig b/views/display.twig index 11ac63e..2cd23d2 100644 --- a/views/display.twig +++ b/views/display.twig @@ -28,7 +28,7 @@
- +