chef 3745 Posted October 12, 2019 Share Posted October 12, 2019 (edited) Lets create a neat Netflix Style movie rating popup in the Web App like in the video above. 1. Add this mutation.js file to: *AppData*\Roaming\Emby-Server\system\dashboard-ui\scripts (function(win) { 'use strict'; var listeners = [], doc = win.document, MutationObserver = win.MutationObserver || win.WebKitMutationObserver, observer; function ready(selector, fn) { // Store the selector and callback to be monitored listeners.push({ selector: selector, fn: fn }); if (!observer) { // Watch for changes in the document observer = new MutationObserver(function(mutations) { check() }); observer.observe(doc.documentElement, { childList: true, subtree: true, attributes: true, attributeOldValue: true, }); } // Check if the element is currently in the DOM check(); } function check() { // Check the DOM for elements matching a stored selector for (var i = 0, len = listeners.length, listener, elements; i < len; i++) { listener = listeners[i]; // Query for elements matching the specified selector elements = doc.querySelectorAll(listener.selector); for (var j = 0, jLen = elements.length, element; j < jLen; j++) { element = elements[j]; // Make sure the callback isn't invoked with the // same element more than once if (!element.ready) { element.ready = true; // Invoke the callback with the element listener.fn.call(element, element); } } } } // Expose `ready` win.ready = ready; })(this); 2. Open embys web app index .html ("*AppData*\Roaming\Emby-Server\system\dashboard-ui\index.html") and add the script to the bottom of the body with all the other scripts/ <body> <div class="backdropContainer"></div> <div class="backgroundContainer"></div> <div class="mainDrawer hide"></div> <div class="skinHeader"></div> <div class="mainAnimatedPages skinBody"></div> <div class="mainDrawerHandle"></div> <script src="scripts/apploader.js" defer></script> <script src="scripts/mutation.js"></script> </body> 3. Finally add the following code in script tags to the body (should now look like this): <!DOCTYPE html> <html class="preload"> <head> <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"> <link rel="manifest" href="manifest.json"> <meta name="format-detection" content="telephone=no"> <meta name="msapplication-tap-highlight" content="no"> <meta http-equiv="X-UA-Compatibility" content="IE=Edge"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes"> <meta name="application-name" content="Emby"> <meta name="robots" content="noindex, nofollow, noarchive"> <meta property="og:title" content="Emby"> <meta property="og:site_name" content="Emby"> <meta property="og:url" content="https://emby.media"> <meta property="og:description" content="Energize your media."> <meta property="og:type" content="article"> <meta property="fb:app_id" content="1618309211750238"> <link rel="apple-touch-icon" href="touchicon.png"> <link rel="apple-touch-icon" sizes="72x72" href="touchicon72.png"> <link rel="apple-touch-icon" sizes="114x114" href="touchicon114.png"> <link rel="apple-touch-startup-image" href="css/images/iossplash.png"> <link rel="shortcut icon" href="favicon.ico"> <meta name="msapplication-TileImage" content="touchicon144.png"> <meta name="msapplication-TileColor" content="#333333"> <meta name="theme-color" content="#43A047"> <title>Emby</title> <style> .mouseIdle, .mouseIdle button, .mouseIdle select, .mouseIdle input, .mouseIdle textarea, .mouseIdle a, .mouseIdle label { cursor: none !important; } .preload { background-color: #000; } .hide, .mouseIdle .hide-mouse-idle, .mouseIdle-tv .hide-mouse-idle-tv { display: none !important; } .mainDrawerHandle { position: fixed; top: 0; left: 0; bottom: 0; z-index: 1; width: .7em; } </style> </head> <body> <div class="backdropContainer"></div> <div class="backgroundContainer"></div> <div class="mainDrawer hide"></div> <div class="skinHeader"></div> <div class="mainAnimatedPages skinBody"></div> <div class="mainDrawerHandle"></div> <script src="scripts/apploader.js" defer></script> <script src="scripts/mutation.js"></script> <script> mutations(); function mutations() { return new Promise(() => { ready('#videoOsdPage > div.videoOsdBottom', (element) => { var ratingContainer = document.createElement('div'); ratingContainer.style.width = '25em'; ratingContainer.style.position = 'absolute'; ratingContainer.style.top = '-75vh'; ratingContainer.style.left = '2%'; ratingContainer.style.height = '100px'; ratingContainer.style.display = 'inline-flex'; var ratingBar = document.createElement('div'); ratingBar.style.height = '0'; ratingBar.style.background = 'rgba(255,0,0,1)'; ratingBar.style.width = '4px'; ratingBar.animate([{ height: '0' }, { height: '3em' }], { // timing options duration: 300, iterations: 1, fill: 'forwards', delay: 20 }) var ratingTextContainer = document.createElement('div'); ratingTextContainer.style.paddingLeft = '15px'; //Get the rating from Embys details page (just faster then creating a Client to communicate here) var ratingDiv = document.querySelector('#itemDetailPage > div.detailPageContent.padded-bottom-page.detailPageContent-nodetailimg > div.detailPagePrimaryContainer.padded-left.padded-right > div.detailPagePrimaryContent > div > div.itemMiscInfo.itemMiscInfo-primary.details-secondary > div.mediaInfoItem.undefined'); var rating = ratingDiv.textContent; var ratingText = document.createElement('span'); ratingText.style.fontSize = "2em"; ratingText.innerText = 'Rated: ' + rating; ratingText.style.opacity = '0'; ratingText.animate([ // keyframes { transform: 'translateX(-30px)', opacity: 0 }, { transform: 'translateX(30px)', opacity: 1 } ], { // timing options duration: 600, iterations: 1, fill: 'forwards', delay: 550 }); var ratingTextDescription = document.createElement('span'); ratingTextDescription.innerText = MPAA_descriptions(rating); ratingTextDescription.style.opacity = '0'; ratingTextDescription.style.display = 'inherit'; ratingTextDescription.animate([ // keyframes { opacity: 0 }, { opacity: 1 } ], { // timing options duration: 600, iterations: 1, fill: 'forwards', delay: 750 }); ratingContainer.appendChild(ratingBar); ratingTextContainer.appendChild(ratingText); ratingTextContainer.appendChild(ratingTextDescription); ratingContainer.appendChild(ratingTextContainer); element.appendChild(ratingContainer); }) function MPAA_descriptions(rating) { switch (rating) { case "G": return "All ages admitted. Nothing that would offend parents for viewing by children"; case "PG": return "Some material may not be suitable for children. Parents urged to give \"parental guidance\". May contain some material parents might not like for their young children."; case "PG-13": return "Some material may be inappropriate for children under 13. Parents are urged to be cautious."; case "R": return "Under 17 requires accompanying parent or adult guardian. Contains some adult material. Parents are urged to learn more about the film before taking their young children with them."; case "NC-17": return "No One 17 and Under Admitted. Clearly adult. Children are not admitted."; case "TV-Y": return "This program is designed to be appropriate for all children."; case "TV-Y7": return "This program is designed for children age 7 and above."; case "TV-G": return "This program is suitable for all ages."; case "TV-PG": return "This program contains material that parents may find unsuitable for younger children."; case "TV-14": return "This program contains some material that many parents would find unsuitable for children under 14 years of age."; case "TV-MA": return "This program is specifically designed to be viewed by adults and therefore may be unsuitable for children under 17."; } } }); } </script> </body> </html> Save it! Done! All we did was create a couple 'divs' and a 'span' to create the Netflix Effect on embys On Screen Display. I use a ultra wide screen so I'm hoping that my calculations are correct for placement of the items. Edited October 12, 2019 by chef Link to comment Share on other sites More sharing options...
chef 3745 Posted October 12, 2019 Author Share Posted October 12, 2019 (edited) I've updated the code above to now return MPAA descriptions of the media item. Edited October 12, 2019 by chef Link to comment Share on other sites More sharing options...
daedalus 430 Posted October 13, 2019 Share Posted October 13, 2019 it's very sad that things like this will only work in the local web client and must be reapplied on every update 1 Link to comment Share on other sites More sharing options...
rechigo 293 Posted October 16, 2019 Share Posted October 16, 2019 Thanks again for your contribution man Link to comment Share on other sites More sharing options...
rechigo 293 Posted October 17, 2019 Share Posted October 17, 2019 I just got around to adding it, and I can't get it to work. I'm on the latest beta 4.3.0.15. Could this have something to do with it? Link to comment Share on other sites More sharing options...
chef 3745 Posted October 17, 2019 Author Share Posted October 17, 2019 (edited) I just got around to adding it, and I can't get it to work. I'm on the latest beta 4.3.0.15. Could this have something to do with it? I'm not sure. Here is my mutation.js (function(win) { 'use strict'; var listeners = [], doc = win.document, MutationObserver = win.MutationObserver || win.WebKitMutationObserver, observer; function ready(selector, fn) { // Store the selector and callback to be monitored listeners.push({ selector: selector, fn: fn }); if (!observer) { // Watch for changes in the document observer = new MutationObserver(function(mutations) { check() }); observer.observe(doc.documentElement, { childList: true, subtree: true, attributes: true, attributeOldValue: true, }); } // Check if the element is currently in the DOM check(); } function check() { // Check the DOM for elements matching a stored selector for (var i = 0, len = listeners.length, listener, elements; i < len; i++) { listener = listeners[i]; // Query for elements matching the specified selector elements = doc.querySelectorAll(listener.selector); for (var j = 0, jLen = elements.length, element; j < jLen; j++) { element = elements[j]; // Make sure the callback isn't invoked with the // same element more than once if (!element.ready) { element.ready = true; // Invoke the callback with the element listener.fn.call(element, element); } } } } // Expose `ready` win.ready = ready; })(this); save that into: *AppData*\Roaming\Emby-Server\system\dashboard-ui\scripts\mutation.js Here is what your index.html file should look like: <!DOCTYPE html> <html class="preload"> <head> <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"> <link rel="manifest" href="manifest.json"> <meta name="format-detection" content="telephone=no"> <meta name="msapplication-tap-highlight" content="no"> <meta http-equiv="X-UA-Compatibility" content="IE=Edge"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes"> <meta name="application-name" content="Emby"> <meta name="robots" content="noindex, nofollow, noarchive"> <meta property="og:title" content="Emby"> <meta property="og:site_name" content="Emby"> <meta property="og:url" content="https://emby.media"> <meta property="og:description" content="Energize your media."> <meta property="og:type" content="article"> <meta property="fb:app_id" content="1618309211750238"> <link rel="apple-touch-icon" href="touchicon.png"> <link rel="apple-touch-icon" sizes="72x72" href="touchicon72.png"> <link rel="apple-touch-icon" sizes="114x114" href="touchicon114.png"> <link rel="apple-touch-startup-image" href="css/images/iossplash.png"> <link rel="shortcut icon" href="favicon.ico"> <meta name="msapplication-TileImage" content="touchicon144.png"> <meta name="msapplication-TileColor" content="#333333"> <meta name="theme-color" content="#43A047"> <title>Emby</title> <style> .mouseIdle, .mouseIdle button, .mouseIdle select, .mouseIdle input, .mouseIdle textarea, .mouseIdle a, .mouseIdle label { cursor: none !important; } .preload { background-color: #000; } .hide, .mouseIdle .hide-mouse-idle, .mouseIdle-tv .hide-mouse-idle-tv { display: none !important; } .mainDrawerHandle { position: fixed; top: 0; left: 0; bottom: 0; z-index: 1; width: .7em; } </style> </head> <body> <div class="backdropContainer"></div> <div class="backgroundContainer"></div> <div class="mainDrawer hide"></div> <div class="skinHeader"></div> <div class="mainAnimatedPages skinBody"></div> <div class="mainDrawerHandle"></div> <script src="scripts/apploader.js" defer></script> <script src="scripts/mutation.js"></script> <script> mutations(); function mutations() { return new Promise(() => { ready('#videoOsdPage > div.videoOsdBottom', (element) => { var ratingContainer = document.createElement('div'); ratingContainer.style.width = '25em'; ratingContainer.style.position = 'absolute'; ratingContainer.style.top = '-75vh'; ratingContainer.style.left = '2%'; ratingContainer.style.height = '100px'; ratingContainer.style.display = 'inline-flex'; ratingContainer.animate([{ opacity: 1 }, { opacity: 0 }], { // timing options duration: 500, iterations: 1, fill: 'forwards', delay: 7500 }) var ratingBar = document.createElement('div'); ratingBar.style.height = '0'; ratingBar.style.background = 'rgba(255,0,0,1)'; ratingBar.style.width = '3px'; ratingBar.animate([{ height: '0' }, { height: '4em' }], { // timing options duration: 900, iterations: 1, fill: 'forwards', delay: 2500 }) var ratingTextContainer = document.createElement('div'); ratingTextContainer.style.paddingLeft = '15px'; //Get the rating from Embys details page (just faster then creating a Client to communicate here) var ratingDiv = document.querySelector('#itemDetailPage > div.detailPageContent.padded-bottom-page.detailPageContent-nodetailimg > div.detailPagePrimaryContainer.padded-left.padded-right > div.detailPagePrimaryContent > div > div.itemMiscInfo.itemMiscInfo-primary.details-secondary > div.mediaInfoItem.undefined'); var rating = ratingDiv.textContent; var ratingText = document.createElement('span'); ratingText.style.fontSize = "3.5em"; ratingText.innerText = 'Rated ' + rating; ratingText.style.opacity = '0'; ratingText.style.position = 'absolute'; ratingText.style.top = '-6px'; ratingText.style.transitionTimingFunction = "ease"; ratingText.animate([ // keyframes { transform: 'translateX(-30px)', opacity: 0 }, { transform: 'translateX(0px)', opacity: 1 } ], { // timing options duration: 1000, iterations: 1, fill: 'forwards', delay: 4050 }); ratingContainer.appendChild(ratingBar); ratingTextContainer.appendChild(ratingText); ratingContainer.appendChild(ratingTextContainer); element.appendChild(ratingContainer); }) function MPAA_descriptions(rating) { switch (rating) { case "G": return "All ages admitted. Nothing that would offend parents for viewing by children"; case "PG": return "Some material may not be suitable for children. Parents urged to give \"parental guidance\". May contain some material parents might not like for their young children."; case "PG-13": return "Some material may be inappropriate for children under 13. Parents are urged to be cautious."; case "R": return "Under 17 requires accompanying parent or adult guardian. Contains some adult material. Parents are urged to learn more about the film before taking their young children with them."; case "NC-17": return "No One 17 and Under Admitted. Clearly adult. Children are not admitted."; case "TV-Y": return "This program is designed to be appropriate for all children."; case "TV-Y7": return "This program is designed for children age 7 and above."; case "TV-G": return "This program is suitable for all ages."; case "TV-PG": return "This program contains material that parents may find unsuitable for younger children."; case "TV-14": return "This program contains some material that many parents would find unsuitable for children under 14 years of age."; case "TV-MA": return "This program is specifically designed to be viewed by adults and therefore may be unsuitable for children under 17."; } } }) } </script> </body> </html> Try that and see how it goes. I also changed timeing of the OSD to allow for the animation to complete: 1. open 'videoosd.js' 2. search for 'stopOsdHideTimer' 3. change 'setTimeout' to 8000 stopOsdHideTimer(), osdHideTimeout = setTimeout(hideOsd, 8000) Edited October 18, 2019 by chef Link to comment Share on other sites More sharing options...
rechigo 293 Posted October 17, 2019 Share Posted October 17, 2019 Yeah still can't get it to work, I have no clue why Link to comment Share on other sites More sharing options...
chef 3745 Posted October 17, 2019 Author Share Posted October 17, 2019 (edited) Yeah still can't get it to work, I have no clue why clear the browser cache? and restart server to add the mutation.js to the service? Edited October 17, 2019 by chef Link to comment Share on other sites More sharing options...
rechigo 293 Posted October 18, 2019 Share Posted October 18, 2019 Cleared the cache and I already had the mutationjs file from a previous mod, unless this mutationjs is different from the one in the mo.js/vanilla-tilt 1 Link to comment Share on other sites More sharing options...
chef 3745 Posted October 18, 2019 Author Share Posted October 18, 2019 Cleared the cache and I already had the mutationjs file from a previous mod, unless this mutationjs is different from the one in the mo.js/vanilla-tilt the mutation.js is a little different, but it does the same thing with mutationObserver. that shouldn't matter. are there any errors in the console when inspecting element? Link to comment Share on other sites More sharing options...
chef 3745 Posted October 18, 2019 Author Share Posted October 18, 2019 (edited) would you mind copying your index.html here on the thread? I screwed up the code above. I forgot the last two curly braces in the <script> tags.... sorry... This is the index.html... {faceplam..} <!DOCTYPE html> <html class="preload"> <head> <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"> <link rel="manifest" href="manifest.json"> <meta name="format-detection" content="telephone=no"> <meta name="msapplication-tap-highlight" content="no"> <meta http-equiv="X-UA-Compatibility" content="IE=Edge"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes"> <meta name="application-name" content="Emby"> <meta name="robots" content="noindex, nofollow, noarchive"> <meta property="og:title" content="Emby"> <meta property="og:site_name" content="Emby"> <meta property="og:url" content="https://emby.media"> <meta property="og:description" content="Energize your media."> <meta property="og:type" content="article"> <meta property="fb:app_id" content="1618309211750238"> <link rel="apple-touch-icon" href="touchicon.png"> <link rel="apple-touch-icon" sizes="72x72" href="touchicon72.png"> <link rel="apple-touch-icon" sizes="114x114" href="touchicon114.png"> <link rel="apple-touch-startup-image" href="css/images/iossplash.png"> <link rel="shortcut icon" href="favicon.ico"> <meta name="msapplication-TileImage" content="touchicon144.png"> <meta name="msapplication-TileColor" content="#333333"> <meta name="theme-color" content="#43A047"> <title>Emby</title> <style> .mouseIdle, .mouseIdle button, .mouseIdle select, .mouseIdle input, .mouseIdle textarea, .mouseIdle a, .mouseIdle label { cursor: none !important; } .preload { background-color: #000; } .hide, .mouseIdle .hide-mouse-idle, .mouseIdle-tv .hide-mouse-idle-tv { display: none !important; } .mainDrawerHandle { position: fixed; top: 0; left: 0; bottom: 0; z-index: 1; width: .7em; } </style> </head> <body> <div class="backdropContainer"></div> <div class="backgroundContainer"></div> <div class="mainDrawer hide"></div> <div class="skinHeader"></div> <div class="mainAnimatedPages skinBody"></div> <div class="mainDrawerHandle"></div> <script src="scripts/apploader.js" defer></script> <script src="scripts/mutation.js"></script> <script> mutations(); function mutations() { return new Promise(() => { ready('#videoOsdPage > div.videoOsdBottom', (element) => { var ratingContainer = document.createElement('div'); ratingContainer.style.width = '25em'; ratingContainer.style.position = 'absolute'; ratingContainer.style.top = '-75vh'; ratingContainer.style.left = '2%'; ratingContainer.style.height = '100px'; ratingContainer.style.display = 'inline-flex'; ratingContainer.animate([{ opacity: 1 }, { opacity: 0 }], { // timing options duration: 500, iterations: 1, fill: 'forwards', delay: 7500 }) var ratingBar = document.createElement('div'); ratingBar.style.height = '0'; ratingBar.style.background = 'rgba(255,0,0,1)'; ratingBar.style.width = '3px'; ratingBar.animate([{ height: '0' }, { height: '4em' }], { // timing options duration: 900, iterations: 1, fill: 'forwards', delay: 2500 }) var ratingTextContainer = document.createElement('div'); ratingTextContainer.style.paddingLeft = '15px'; //Get the rating from Embys details page (just faster then creating a Client to communicate here) var ratingDiv = document.querySelector('#itemDetailPage > div.detailPageContent.padded-bottom-page.detailPageContent-nodetailimg > div.detailPagePrimaryContainer.padded-left.padded-right > div.detailPagePrimaryContent > div > div.itemMiscInfo.itemMiscInfo-primary.details-secondary > div.mediaInfoItem.undefined'); var rating = ratingDiv.textContent; var ratingText = document.createElement('span'); ratingText.style.fontSize = "3.5em"; ratingText.innerText = 'Rated ' + rating; ratingText.style.opacity = '0'; ratingText.style.position = 'absolute'; ratingText.style.top = '-6px'; ratingText.style.transitionTimingFunction = "ease"; ratingText.animate([ // keyframes { transform: 'translateX(-30px)', opacity: 0 }, { transform: 'translateX(0px)', opacity: 1 } ], { // timing options duration: 1000, iterations: 1, fill: 'forwards', delay: 4050 }); ratingContainer.appendChild(ratingBar); ratingTextContainer.appendChild(ratingText); ratingContainer.appendChild(ratingTextContainer); element.appendChild(ratingContainer); }) function MPAA_descriptions(rating) { switch (rating) { case "G": return "All ages admitted. Nothing that would offend parents for viewing by children"; case "PG": return "Some material may not be suitable for children. Parents urged to give \"parental guidance\". May contain some material parents might not like for their young children."; case "PG-13": return "Some material may be inappropriate for children under 13. Parents are urged to be cautious."; case "R": return "Under 17 requires accompanying parent or adult guardian. Contains some adult material. Parents are urged to learn more about the film before taking their young children with them."; case "NC-17": return "No One 17 and Under Admitted. Clearly adult. Children are not admitted."; case "TV-Y": return "This program is designed to be appropriate for all children."; case "TV-Y7": return "This program is designed for children age 7 and above."; case "TV-G": return "This program is suitable for all ages."; case "TV-PG": return "This program contains material that parents may find unsuitable for younger children."; case "TV-14": return "This program contains some material that many parents would find unsuitable for children under 14 years of age."; case "TV-MA": return "This program is specifically designed to be viewed by adults and therefore may be unsuitable for children under 17."; } } }) } </script> </body> </html> I also fixed the prior post. Edited October 18, 2019 by chef Link to comment Share on other sites More sharing options...
rechigo 293 Posted January 4, 2020 Share Posted January 4, 2020 (edited) So I was bored today and decided to mess around with this today. Doing so, I noticed two things: 1) Chef's code never actually displays the MPAA_description, so only the rating "ex. Rated: PG 13" is shown. 2) Some of the IDs/classnames aren't present in newer Emby versions, so I had to find the correct ones in order to get it to display properly. One extra thing I did was add a default case under the MPAA_description function to display if the show/movie didn't have a rating. EDIT: I forgot to mention that I also tweaked the positioning a bit since for some reason it was set way too high and 90% of it was out of the viewport I'll probably update this soon to simply make an API request to get the rating as this method is kinda hacky and is dependent on a couple of classes staying the same Code: https://paste.memester.cf/oporaxavay.js Edited January 4, 2020 by rechigo Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now