From 813f16e4793f375feb8f00ef335af59a783612b3 Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 24 Dec 2015 13:14:12 -0500 Subject: [PATCH 01/12] fixes #1304 - Wrong profile picture --- dashboard-ui/scripts/librarymenu.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dashboard-ui/scripts/librarymenu.js b/dashboard-ui/scripts/librarymenu.js index d9411773e8..f6102ccc36 100644 --- a/dashboard-ui/scripts/librarymenu.js +++ b/dashboard-ui/scripts/librarymenu.js @@ -70,6 +70,9 @@ var header = document.querySelector('.viewMenuBar'); + var headerUserButton = header.querySelector('.headerUserButton'); + var hasImage; + if (user.name) { if (user.imageUrl && AppInfo.enableUserImage) { @@ -81,15 +84,21 @@ url += "&height=" + (userButtonHeight * Math.max(devicePixelRatio || 1, 2)); } - var headerUserButton = header.querySelector('.headerUserButton'); if (headerUserButton) { headerUserButton.icon = null; headerUserButton.src = url; headerUserButton.classList.add('headerUserButtonRound'); + hasImage = true; } } } + if (hasUserButton && !hasImage) { + headerUserButton.icon = 'person'; + headerUserButton.src = null; + headerUserButton.classList.remove('headerUserButtonRound'); + } + updateLocalUser(user.localUser); requiresUserRefresh = false; } From 7a80749c6f8b0767092cc41a99f7fba0a4d9cf28 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 25 Dec 2015 21:28:35 -0500 Subject: [PATCH 02/12] fixes #1304 - Wrong profile picture --- dashboard-ui/scripts/librarymenu.js | 31 ++++++++++++++++------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/dashboard-ui/scripts/librarymenu.js b/dashboard-ui/scripts/librarymenu.js index f6102ccc36..40ac1ef3e0 100644 --- a/dashboard-ui/scripts/librarymenu.js +++ b/dashboard-ui/scripts/librarymenu.js @@ -66,14 +66,14 @@ } } - function addUserToHeader(user) { + function updateUserInHeader(user) { var header = document.querySelector('.viewMenuBar'); var headerUserButton = header.querySelector('.headerUserButton'); var hasImage; - - if (user.name) { + + if (user && user.name) { if (user.imageUrl && AppInfo.enableUserImage) { var userButtonHeight = 26; @@ -93,13 +93,21 @@ } } - if (hasUserButton && !hasImage) { - headerUserButton.icon = 'person'; + if (headerUserButton && !hasImage) { + headerUserButton.icon = 'person'; headerUserButton.src = null; headerUserButton.classList.remove('headerUserButtonRound'); + + // Looks like a bug in paper-icon-button that this doesn't get removed + var headerUserButtonImg = headerUserButton.querySelector('img'); + if (headerUserButtonImg) { + headerUserButtonImg.parentNode.removeChild(headerUserButtonImg); + } + } + if (user) { + updateLocalUser(user.localUser); } - updateLocalUser(user.localUser); requiresUserRefresh = false; } @@ -149,11 +157,6 @@ } } - function removeUserFromHeader() { - - updateLocalUser(null); - } - function bindMenuEvents() { var mainDrawerButton = document.querySelector('.mainDrawerButton'); @@ -788,7 +791,7 @@ } if (requiresUserRefresh) { - ConnectionManager.user(window.ApiClient).then(addUserToHeader); + ConnectionManager.user(window.ApiClient).then(updateUserInHeader); } } @@ -947,13 +950,13 @@ Events.on(ConnectionManager, 'localusersignedin', function (e, user) { requiresLibraryMenuRefresh = true; requiresDrawerRefresh = true; - ConnectionManager.user(ConnectionManager.getApiClient(user.ServerId)).then(addUserToHeader); + ConnectionManager.user(ConnectionManager.getApiClient(user.ServerId)).then(updateUserInHeader); }); Events.on(ConnectionManager, 'localusersignedout', function () { requiresLibraryMenuRefresh = true; requiresDrawerRefresh = true; - removeUserFromHeader(); + updateUserInHeader(); }); Events.on(MediaController, 'playerchange', function () { From 6361f4bf1b7bb935bc07e16032ff43d1c194c75e Mon Sep 17 00:00:00 2001 From: Luke Date: Thu, 24 Dec 2015 13:43:45 -0500 Subject: [PATCH 03/12] fixes #1290 - upgrade audio formats --- dashboard-ui/scripts/mediaplayer.js | 75 ++++++++++++----------------- 1 file changed, 30 insertions(+), 45 deletions(-) diff --git a/dashboard-ui/scripts/mediaplayer.js b/dashboard-ui/scripts/mediaplayer.js index 0829a25641..d434ae13cf 100644 --- a/dashboard-ui/scripts/mediaplayer.js +++ b/dashboard-ui/scripts/mediaplayer.js @@ -129,6 +129,7 @@ var canPlayAc3 = supportedFormats.indexOf('ac3') != -1; var canPlayAac = supportedFormats.indexOf('aac') != -1; var canPlayMp3 = supportedFormats.indexOf('mp3') != -1; + var canPlayOpus = supportedFormats.indexOf('opus') != -1; var canPlayMkv = supportedFormats.indexOf('mkv') != -1; var profile = {}; @@ -166,19 +167,15 @@ }); } - if (canPlayMp3) { - profile.DirectPlayProfiles.push({ - Container: 'mp3', - Type: 'Audio' - }); - } + ['opus', 'mp3', 'aac'].forEach(function(audioFormat){ - if (canPlayAac) { - profile.DirectPlayProfiles.push({ - Container: 'aac', - Type: 'Audio' - }); - } + if (supportedFormats.indexOf(audioFormat) != -1) { + profile.DirectPlayProfiles.push({ + Container: audioFormat, + Type: 'Audio' + }); + } + }); var directPlayAudioContainers = AppInfo.directPlayAudioContainers; @@ -264,40 +261,25 @@ Protocol: 'http' }); - if (canPlayAac && browserInfo.safari) { + ['opus', 'mp3', 'aac'].forEach(function(audioFormat){ - profile.TranscodingProfiles.push({ - Container: 'aac', - Type: 'Audio', - AudioCodec: 'aac', - Context: 'Streaming', - Protocol: 'http' - }); - - profile.TranscodingProfiles.push({ - Container: 'aac', - Type: 'Audio', - AudioCodec: 'aac', - Context: 'Static', - Protocol: 'http' - }); - - } else { - profile.TranscodingProfiles.push({ - Container: 'mp3', - Type: 'Audio', - AudioCodec: 'mp3', - Context: 'Streaming', - Protocol: 'http' - }); - profile.TranscodingProfiles.push({ - Container: 'mp3', - Type: 'Audio', - AudioCodec: 'mp3', - Context: 'Static', - Protocol: 'http' - }); - } + if (supportedFormats.indexOf(audioFormat) != -1) { + profile.TranscodingProfiles.push({ + Container: audioFormat, + Type: 'Audio', + AudioCodec: audioFormat, + Context: 'Streaming', + Protocol: 'http' + }); + profile.TranscodingProfiles.push({ + Container: audioFormat, + Type: 'Audio', + AudioCodec: audioFormat, + Context: 'Static', + Protocol: 'http' + }); + } + }); profile.ContainerProfiles = []; @@ -1918,6 +1900,9 @@ if (document.createElement('audio').canPlayType('audio/mp3').replace(/no/, '')) { list.push('mp3'); } + if (document.createElement('audio').canPlayType('audio/opus').replace(/no/, '')) { + list.push('opus'); + } if (browserInfo.chrome) { list.push('mkv'); From 61f0d833aeb5a86a69a85fafb2d83d99baa0ffd0 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 25 Dec 2015 21:30:18 -0500 Subject: [PATCH 04/12] fixes #1290 - upgrade audio formats --- dashboard-ui/scripts/mediaplayer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dashboard-ui/scripts/mediaplayer.js b/dashboard-ui/scripts/mediaplayer.js index d434ae13cf..64022fe71b 100644 --- a/dashboard-ui/scripts/mediaplayer.js +++ b/dashboard-ui/scripts/mediaplayer.js @@ -129,7 +129,6 @@ var canPlayAc3 = supportedFormats.indexOf('ac3') != -1; var canPlayAac = supportedFormats.indexOf('aac') != -1; var canPlayMp3 = supportedFormats.indexOf('mp3') != -1; - var canPlayOpus = supportedFormats.indexOf('opus') != -1; var canPlayMkv = supportedFormats.indexOf('mkv') != -1; var profile = {}; @@ -1900,7 +1899,8 @@ if (document.createElement('audio').canPlayType('audio/mp3').replace(/no/, '')) { list.push('mp3'); } - if (document.createElement('audio').canPlayType('audio/opus').replace(/no/, '')) { + + if (document.createElement('audio').canPlayType('audio/ogg; codecs="opus"').replace(/no/, '')) { list.push('opus'); } From 267292963acdf3df7bd3257c40fe9f4c035c5249 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 25 Dec 2015 23:08:25 -0500 Subject: [PATCH 05/12] fixes #898 - Google Cast Ready --- .../components/remotecontrolautoplay.js | 51 ++++++++++++++++ dashboard-ui/scripts/chromecast.js | 59 ++++++++++++++----- dashboard-ui/scripts/mediacontroller.js | 12 ++-- dashboard-ui/scripts/site.js | 2 + 4 files changed, 106 insertions(+), 18 deletions(-) create mode 100644 dashboard-ui/components/remotecontrolautoplay.js diff --git a/dashboard-ui/components/remotecontrolautoplay.js b/dashboard-ui/components/remotecontrolautoplay.js new file mode 100644 index 0000000000..6689ea3005 --- /dev/null +++ b/dashboard-ui/components/remotecontrolautoplay.js @@ -0,0 +1,51 @@ +(function () { + + function transferPlayback(oldPlayer) { + + oldPlayer.getPlayerState().then(function (state) { + + var item = state.NowPlayingItem; + + if (!item) { + return; + } + + var playState = state.PlayState || {}; + + oldPlayer.stop(); + + var itemId = item.Id; + var resumePositionTicks = playState.PositionTicks || 0; + + MediaController.play({ + ids: [itemId], + startPositionTicks: resumePositionTicks + }); + + }); + } + + Events.on(MediaController, 'playerchange', function (e, newPlayer, newTarget, oldPlayer) { + + if (!oldPlayer) { + console.log('Skipping remote control autoplay because oldPlayer is null'); + return; + } + + if (!oldPlayer.isLocalPlayer) { + console.log('Skipping remote control autoplay because oldPlayer is not a local player'); + return; + } + + if (newPlayer.isLocalPlayer) { + console.log('Skipping remote control autoplay because newPlayer is a local player'); + return; + } + + // If playback is playing locally and a new player is activated, transfer the media to that player + if (oldPlayer.isPlaying()) { + transferPlayback(oldPlayer); + } + }); + +})(); \ No newline at end of file diff --git a/dashboard-ui/scripts/chromecast.js b/dashboard-ui/scripts/chromecast.js index d9f8aa6e99..a281ec02d7 100644 --- a/dashboard-ui/scripts/chromecast.js +++ b/dashboard-ui/scripts/chromecast.js @@ -1,6 +1,31 @@ (function (window, chrome, console) { // Based on https://github.com/googlecast/CastVideos-chrome/blob/master/CastVideos.js + var currentResolve; + var currentReject; + + var PlayerName = 'Chromecast'; + + function sendConnectionResult(isOk) { + + var resolve = currentResolve; + var reject = currentReject; + + currentResolve = null; + currentReject = null; + + if (isOk) { + if (resolve) { + resolve(); + } + } else { + if (reject) { + reject(); + } else { + MediaController.removeActivePlayer(PlayerName); + } + } + } /** * Constants of states for Chromecast device @@ -26,8 +51,6 @@ 'ERROR': 'ERROR' }; - var PlayerName = 'Chromecast'; - var applicationID = "2D4B1DA3"; var messageNamespace = 'urn:x-cast:com.connectsdk'; @@ -191,7 +214,7 @@ console.log('sessionUpdateListener: setting currentMediaSession to null'); this.currentMediaSession = null; - MediaController.removeActivePlayer(PlayerName); + sendConnectionResult(false); } }; @@ -257,7 +280,7 @@ //}); - MediaController.removeActivePlayer(PlayerName); + sendConnectionResult(false); }; /** @@ -485,7 +508,11 @@ Events.on(castPlayer, "connect", function (e) { - MediaController.setActivePlayer(PlayerName, self.getCurrentTargetInfo()); + if (currentResolve) { + sendConnectionResult(true); + } else { + MediaController.setActivePlayer(PlayerName, self.getCurrentTargetInfo()); + } console.log('cc: connect'); // Reset this so the next query doesn't make it appear like content is playing. @@ -832,7 +859,19 @@ self.tryPair = function (target) { return new Promise(function (resolve, reject) { - resolve(); + if (castPlayer.deviceState != DEVICE_STATE.ACTIVE && castPlayer.isInitialized) { + + currentResolve = resolve; + currentReject = reject; + + castPlayer.launchApp(); + } else { + + currentResolve = null; + currentReject = null; + + reject(); + } }); }; } @@ -842,14 +881,6 @@ castPlayer = new CastPlayer(); MediaController.registerPlayer(new chromecastPlayer()); - - Events.on(MediaController, 'playerchange', function (e, newPlayer, newTarget) { - if (newPlayer.name == PlayerName) { - if (castPlayer.deviceState != DEVICE_STATE.ACTIVE && castPlayer.isInitialized) { - castPlayer.launchApp(); - } - } - }); } requirejs(["https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"], initializeChromecast); diff --git a/dashboard-ui/scripts/mediacontroller.js b/dashboard-ui/scripts/mediacontroller.js index 18b80eab6b..709537c2df 100644 --- a/dashboard-ui/scripts/mediacontroller.js +++ b/dashboard-ui/scripts/mediacontroller.js @@ -286,9 +286,9 @@ }; }; - function triggerPlayerChange(newPlayer, newTarget) { + function triggerPlayerChange(newPlayer, newTarget, previousPlayer) { - Events.trigger(self, 'playerchange', [newPlayer, newTarget]); + Events.trigger(self, 'playerchange', [newPlayer, newTarget, previousPlayer]); } self.setActivePlayer = function (player, targetInfo) { @@ -303,13 +303,15 @@ throw new Error('null player'); } + var previousPlayer = currentPlayer; + currentPairingId = null; currentPlayer = player; currentTargetInfo = targetInfo; console.log('Active player: ' + JSON.stringify(currentTargetInfo)); - triggerPlayerChange(player, targetInfo); + triggerPlayerChange(player, targetInfo, previousPlayer); }; var currentPairingId = null; @@ -333,12 +335,14 @@ player.tryPair(targetInfo).then(function () { + var previousPlayer = currentPlayer; + currentPlayer = player; currentTargetInfo = targetInfo; console.log('Active player: ' + JSON.stringify(currentTargetInfo)); - triggerPlayerChange(player, targetInfo); + triggerPlayerChange(player, targetInfo, previousPlayer); }); }; diff --git a/dashboard-ui/scripts/site.js b/dashboard-ui/scripts/site.js index 29ef578e55..c5115dd4d3 100644 --- a/dashboard-ui/scripts/site.js +++ b/dashboard-ui/scripts/site.js @@ -2267,6 +2267,8 @@ var AppInfo = {}; postInitDependencies.push('cordova/ios/tabbar'); } + postInitDependencies.push('components/remotecontrolautoplay'); + require(postInitDependencies); }); } From 1cf6664993e833a6bd372ceb3fdae34d33582412 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 26 Dec 2015 09:29:25 -0500 Subject: [PATCH 06/12] fixes #1243 - Cannot set local address. If detection is wrong, chromecast fails --- dashboard-ui/components/chromecasthelpers.js | 305 +++++++++++++++++++ dashboard-ui/scripts/chromecast.js | 31 +- dashboard-ui/scripts/site.js | 1 + 3 files changed, 310 insertions(+), 27 deletions(-) create mode 100644 dashboard-ui/components/chromecasthelpers.js diff --git a/dashboard-ui/components/chromecasthelpers.js b/dashboard-ui/components/chromecasthelpers.js new file mode 100644 index 0000000000..1e23b6aa6f --- /dev/null +++ b/dashboard-ui/components/chromecasthelpers.js @@ -0,0 +1,305 @@ +define([], function () { + + // LinkParser + // + // https://github.com/ravisorg/LinkParser + // + // Locate and extract almost any URL within a string. Handles protocol-less domains, IPv4 and + // IPv6, unrecognised TLDs, and more. + // + // This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. + // http://creativecommons.org/licenses/by-sa/4.0/ + (function () { + + // Original URL regex from the Android android.text.util.Linkify function, found here: + // http://stackoverflow.com/a/19696443 + // + // However there were problems with it, most probably related to the fact it was + // written in 2007, and it's been highly modified. + // + // 1) I didn't like the fact that it was tied to specific TLDs, since new ones + // are being added all the time it wouldn't be reasonable to expect developer to + // be continually updating their regular expressions. + // + // 2) It didn't allow unicode characters in the domains which are now allowed in + // many languages, (including some IDN TLDs). Again these are constantly being + // added to and it doesn't seem reasonable to hard-code them. Note this ended up + // not being possible in standard JS due to the way it handles multibyte strings. + // It is possible using XRegExp, however a big performance hit results. Disabled + // for now. + // + // 3) It didn't allow for IPv6 hostnames + // IPv6 regex from http://stackoverflow.com/a/17871737 + // + // 4) It was very poorly commented + // + // 5) It wasn't as smart as it could have been about what should be part of a + // URL and what should be part of human language. + + var protocols = "(?:(?:http|https|rtsp|ftp):\\/\\/)"; + var credentials = "(?:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,64}" // username (1-64 normal or url escaped characters) + + "(?:\\:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,25})?" // followed by optional password (: + 1-25 normal or url escaped characters) + + "\\@)"; + + var hostnames = "(?:[a-z0-9][a-z0-9\\-]{0,64}\\.)+"; // named host + var unknownTLDs = "(?:[a-z]{2,})"; + + // unicode regexp isn't really supported in JS. It can be emulated with XRegExp + // but that creates a pretty big performance hit that + // is very noticable on mobile devices. We'll add an option in the future to + // support them. + var XRegExp_hostnames = "(?:[\\p{L}0-9][\\p{L}0-9\\-]{0,64}\\.)+"; // named host + var XRegExp_unknownTLDs = "(?:[\\p{L}]{2,})"; // top level domain + + // strings that start with one of these are more likely to be URLs + var knownSubdomains = "(?:(?:www|ftp)\\.)"; + + // update from here when needed: https://data.iana.org/TLD/tlds-alpha-by-domain.txt + var knownTLDs = "(?:" + + "A[CDEFGILMNOQRSTUWXZ]|ACADEMY|ACCOUNTANTS|ACTOR|AERO|AGENCY|AIRFORCE|ARCHI|ARPA|ASIA|ASSOCIATES|AUDIO|AUTOS|AXA" + + "|B[ABDEFGHIJMNORSTVWYZ]|BAR|BARGAINS|BAYERN|BEER|BERLIN|BEST|BID|BIKE|BIZ|BLACK|BLACKFRIDAY|BLUE|BOUTIQUE|BUILD|BUILDERS|BUZZ" + + "|C[ACDFGHIKLMNORUVWXYZ]|CAB|CAMERA|CAMP|CAPITAL|CARDS|CARE|CAREER|CAREERS|CASH|CAT|CATERING|CENTER|CEO|CHEAP|CHRISTMAS|CHURCH|CITIC|CLAIMS|CLEANING|CLINIC|CLOTHING|CLUB|CODES|COFFEE|COLLEGE|COLOGNE|COM|COMMUNITY|COMPANYCOMPUTER|CONDOS|CONSTRUCTION|CONSULTING|CONTRACTORS|COOKING|COOL|COOP|COUNTRY|CREDIT|CREDITCARD|CRUISES" + + "|D[EJKMOZ]|DANCE|DATING|DEMOCRAT|DENTAL|DESI|DIAMONDS|DIGITAL|DIRECTORY|DISCOUNT|DNP|DOMAINS" + + "|E[CEGRSTU]|EDU|EDUCATION|EMAIL|ENGINEERING|ENTERPRISES|EQUIPMENT|ESTATE|EUS|EVENTS|EXCHANGE|EXPERT|EXPOSED" + + "|F[IJKMOR]|FAIL|FARM|FEEDBACK|FINANCE|FINANCIAL|FISH|FISHING|FITNESS|FLIGHTS|FLORIST|FOO|FOUNDATION|FROGANS|FUND|FURNITURE|FUTBOL" + + "|G[ABDEFGHILMNPQRSTUWY]|GAL|GALLERY|GIFT|GLASS|GLOBO|GMO|GOP|GOV|GRAPHICS|GRATIS|GRIPE|GUIDE|GUITARS|GURU" + + "|H[KMNRTU]|HAUS|HIPHOP|HOLDINGS|HOLIDAY|HOMES|HORSE|HOUSE" + + "|I[DELMNOQRST]|IMMOBILIEN|INDUSTRIES|INFO|INK|INSTITUTE|INSURE|INT|INTERNATIONAL|INVESTMENTS" + + "|J[EMOP]|JETZT|JOBS|JUEGOS" + + "|K[EGHIMNPRWYZ]|KAUFEN|KIM|KITCHEN|KIWI|KOELN|KRED" + + "|L[ABCIKRSTUVY]|LAND|LEASE|LIFE|LIGHTING|LIMITED|LIMO|LINK|LOANS|LONDON|LUXE|LUXURY" + + "|M[ACDEGHKLMNOPQRSTUVWXYZ]|MAISON|MANAGEMENT|MANGO|MARKETING|MEDIA|MEET|MENU|MIAMI|MIL|MOBI|MODA|MOE|MONASH|MOSCOW|MOTORCYCLES|MUSEUM" + + "|N[ACEFGILOPRUZ]|NAGOYA|NAME|NET|NEUSTAR|NINJA|NYC" + + "|O[M]|OKINAWA|ONL|ORG" + + "|P[AEFGHKLMNRSTWY]|PARIS|PARTNERS|PARTS|PHOTO|PHOTOGRAPHY|PHOTOS|PICS|PICTURES|PINK|PLUMBING|POST|PRO|PRODUCTIONS|PROPERTIES|PUB" + + "|Q[A]|QPON|QUEBEC" + + "|R[EOSUW]|RECIPES|RED|REISE|REISEN|REN|RENTALS|REPAIR|REPORT|REST|REVIEWS|RICH|RIO|ROCKS|RODEO|RUHR|RYUKYU" + + "|S[ABCDEGHIJKLMNORTUVXYZ]|SAARLAND|SCHULE|SERVICES|SEXY|SHIKSHA|SHOES|SINGLES|SOCIAL|SOHU|SOLAR|SOLUTIONS|SOY|SUPPLIES|SUPPLY|SUPPORT|SURGERY|SYSTEMS" + + "|T[CDFGHJKLMNOPRTVWZ]|TATTOO|TAX|TECHNOLOGY|TEL|TIENDA|TIPS|TODAY|TOKYO|TOOLS|TOWN|TOYS|TRADE|TRAINING|TRAVEL" + + "|U[AGKSYZ]|UNIVERSITY|UNO" + + "|V[ACEGINU]|VACATIONS|VEGAS|VENTURES|VERSICHERUNG|VIAJES|VILLAS|VISION|VODKA|VOTE|VOTING|VOTO|VOYAGE" + + "|W[FS]|WANG|WATCH|WEBCAM|WED|WIEN|WIKI|WORKS|WTC|WTF" + + "|XXX|XYZ" + + "|Y[ET]|YACHTS|YOKOHAMA" + + "|Z[AMW]|ZONE" + // IDN TLDs in both punycode and unicode format + + "|XN--(?:3BST00M|3DS443G|3E0B707E|45BRJ9C|55QW42G|55QX5D|6FRZ82G|6QQ986B3XL|80ADXHKS|80AO21A|80ASEHDB|80ASWG|90A3AC|C1AVG|CG4BKI|CLCHC0EA0B2G2A9GCD|CZR694B|CZRU2D|D1ACJ3B|FIQ228C5HS|FIQ64B|FIQS8S|FIQZ9S|FPCRJ9C3D|FZC2C9E2C|GECRJ9C|H2BRJ9C|I1B6B1A6A2E|IO0A7I|J1AMH|J6W193G|KPRW13D|KPRY57D|L1ACC|LGBBAT1AD8J|MGB9AWBF|MGBA3A4F16A|MGBAAM7A8H|MGBAB2BD|MGBAYH7GPA|MGBBH1A71E|MGBC0A9AZCG|MGBERP4A5D4AR|MGBX4CD0AB|NGBC5AZD|NQV7F|NQV7FS00EMA|O3CW4H|OGBPF8FL|P1AI|PGBS0DH|Q9JYB4C|RHQV96G|S9BRJ9C|SES554G|UNUP4Y|WGBH1C|WGBL6A|XKC2AL3HYE2A|XKC2DL3A5EE0H|YFRO4I67O|YGBI2AMMX|ZFR164B)" + + "|\u0627\u0644\u0627\u0631\u062F\u0646|\u4E2D\u570B|\u4E2D\u56FD|\u0627\u0645\u0627\u0631\u0627\u062A|\u9999\u6E2F|\u0627\u06CC\u0631\u0627\u0646|\u0DBD\u0D82\u0D9A\u0DCF|\u0B87\u0BB2\u0B99\u0BCD\u0B95\u0BC8|\u0645\u0635\u0631|\u0642\u0637\u0631|\u0420\u0424|\u0633\u0648\u0631\u064A\u0629|\u53F0\u7063|\u53F0\u6E7E" + + ")"; + + // IPv6 Regex http://forums.intermapper.com/viewtopic.php?t=452 + // by Dartware, LLC is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License + // http://intermapper.com/ + var ipv6 = "(" + + "(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))" + + "|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))" + + "|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))" + + "|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))" + + "|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))" + + "|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))" + + "|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))" + + "|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))" + + ")(%.+)?"; + + var ipv4 = "(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\." + + "(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\." + + "(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\." + + "(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])"; + + // This would have been a lot cleaner if JS RegExp supported conditionals... + var linkRegExpString = + + // begin match for protocol / username / password / host + "(?:" + + // ============================ + // If we have a recognized protocol at the beginning of the URL, we're + // more relaxed about what we accept, because we assume the user wants + // this to be a URL, and we're not accidentally matching human language + + protocols + + // optional username:password@ + + credentials + "?" + + // loose definition of a host name + // any valid unicode sequence is ok for domain + // this is our failsafe. If we don't recognize a TLD (because new TLDs are + // constantly being added) and the host name doesn't start with a common + // prefix like www, then this should catch anything that begins with a + // valid protocol (above) + + hostnames + unknownTLDs + + // ============================ + // OR no protocol and a stricter form of a host name, with something that + // we recognize as a likely URL. This will hopefully keep us from matching + // typos like.this as a URL. + + "|(?!" + protocols + ")" + + // optional username:password@ + + credentials + "?" + + + "(?:" + + // ends with known TLD + + hostnames + knownTLDs + + // OR begins with a known common subdomain + + "|" + knownSubdomains + hostnames + unknownTLDs + + // OR contains a slash after the TLD (very likely a URL, unlikely to exist in common language) + + "|" + hostnames + unknownTLDs + "(?=\\/)" // use a lookahead so we don't pre-match the path below + + + ")" + + // ============================ + // OR we'll also accept IPv6 and IPv4 addresses, with or without a protocol, since it's highly + // unlikely that these would appear in normal language without being intended as a URL + + "|" + protocols + "?" + + // optional username:password@ + + credentials + "?" + + // IP address (both v4 and v6) + + "(?:" + + // IPv6 + + ipv6 + + // IPv4 + + "|" + ipv4 + + + ")" + + // end match for protocol / username / password / host + + ")" + + // optional port number + + "(?:\\:\\d{1,5})?" + + // plus optional path and query params (no unicode allowed here?) + + "(?:" + + "\\/(?:" + // some characters we'll accept because it's unlikely human language + // would use them after a URL unless they were part of the url + + "(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])" + + "|(?:\\%[a-f0-9]{2})" + // some characters are much more likely to be used AFTER a url and + // were not intended to be included in the url itself. Mostly end + // of sentence type things. It's also likely that the URL would + // still work if any of these characters were missing from the end + // because we parsed it incorrectly. For these characters to be accepted + // they must be followed by another character that we're reasonably + // sure is part of the url + + "|(?:[\\;\\?\\:\\.\\!\\'\\(\\)\\,\\=]+(?=(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])|(?:\\%[a-f0-9]{2})))" + + ")*" + + "|\\b|\$" + + ")"; + + // regex = XRegExp(regex,'gi'); + var linkRegExp = RegExp(linkRegExpString, 'gi'); + + var protocolRegExp = RegExp('^' + protocols, 'i'); + + // if url doesn't begin with a known protocol, add http by default + function ensureProtocol(url) { + if (!url.match(protocolRegExp)) { + url = "http://" + url; + } + return url; + } + + // look for links in the text + var LinkParser = { + + parse: function (text) { + var links = []; + + while (match = linkRegExp.exec(text)) { + // console.log(matches); + var txt = match[0]; + var pos = match['index']; + var len = txt.length; + var url = ensureProtocol(text); + links.push({ 'pos': pos, 'text': txt, 'len': len, 'url': url }); + } + + return links; + } + + } + + window.LinkParser = LinkParser; + })(); + + var cache = {}; + + function getEndpointInfo(apiClient) { + + return apiClient.getJSON(apiClient.getUrl('System/Endpoint')); + } + + function isValidIpAddress(address) { + + var links = LinkParser.parse(address); + + return links.length == 1; + } + + function isLocalIpAddress(address) { + + address = address.toLowerCase(); + + if (address.indexOf('127.0.0.1') != -1) { + return true; + } + if (address.indexOf('localhost') != -1) { + return true; + } + + return false; + } + + function getServerAddress(apiClient) { + + var serverAddress = apiClient.serverAddress(); + + if (isValidIpAddress(serverAddress) && !isLocalIpAddress(serverAddress)) { + return new Promise(function (resolve, reject) { + + resolve(serverAddress); + }); + } + + var cachedValue = cache[serverAddress]; + if (cachedValue) { + return new Promise(function (resolve, reject) { + + resolve(cachedValue); + }); + } + + return apiClient.getJSON(apiClient.getUrl('System/Endpoint')).then(function (endpoint) { + if (endpoint.IsInNetwork) { + return apiClient.getPublicSystemInfo().then(function (info) { + cache[serverAddress] = info.LocalAddress; + return info.LocalAddress; + }); + } else { + cache[serverAddress] = serverAddress; + return serverAddress; + } + }); + } + + function clearCache() { + cache = {}; + } + + Events.on(ConnectionManager, 'localusersignedin', clearCache); + Events.on(ConnectionManager, 'localusersignedout', clearCache); + setInterval(clearCache, 180000); + + return { + getServerAddress: getServerAddress + }; +}); \ No newline at end of file diff --git a/dashboard-ui/scripts/chromecast.js b/dashboard-ui/scripts/chromecast.js index a281ec02d7..375888bfe8 100644 --- a/dashboard-ui/scripts/chromecast.js +++ b/dashboard-ui/scripts/chromecast.js @@ -336,24 +336,6 @@ }); }; - var endpointInfo; - function getEndpointInfo() { - - if (endpointInfo) { - - return new Promise(function (resolve, reject) { - - resolve(endpointInfo); - }); - } - - return ApiClient.getJSON(ApiClient.getUrl('System/Endpoint')).then(function (info) { - - endpointInfo = info; - return info; - }); - } - CastPlayer.prototype.sendMessage = function (message) { var player = this; @@ -376,17 +358,12 @@ supportsAc3: AppSettings.enableChromecastAc3() }); - getEndpointInfo().then(function (endpoint) { + require(['chromecasthelpers'], function (chromecasthelpers) { - if (endpoint.IsInNetwork) { - ApiClient.getPublicSystemInfo().then(function (info) { - - message.serverAddress = info.LocalAddress; - player.sendMessageInternal(message); - }); - } else { + chromecasthelpers.getServerAddress(ApiClient).then(function (serverAddress) { + message.serverAddress = serverAddress; player.sendMessageInternal(message); - } + }); }); }; diff --git a/dashboard-ui/scripts/site.js b/dashboard-ui/scripts/site.js index c5115dd4d3..d549eec598 100644 --- a/dashboard-ui/scripts/site.js +++ b/dashboard-ui/scripts/site.js @@ -1794,6 +1794,7 @@ var AppInfo = {}; headroom: bowerPath + '/headroom.js/dist/headroom.min', masonry: bowerPath + '/masonry/dist/masonry.pkgd.min', humanedate: 'components/humanedate', + chromecasthelpers: 'components/chromecasthelpers', jQuery: bowerPath + '/jquery/dist/jquery.min', fastclick: bowerPath + '/fastclick/lib/fastclick', events: apiClientBowerPath + '/events', From 4313e01d2259cac746206a8e0386d87ab6c732a5 Mon Sep 17 00:00:00 2001 From: Luke Date: Fri, 25 Dec 2015 16:20:23 -0500 Subject: [PATCH 07/12] update action sheet for safari --- dashboard-ui/scripts/actionsheet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashboard-ui/scripts/actionsheet.js b/dashboard-ui/scripts/actionsheet.js index 3588e050ce..c2a9aa960b 100644 --- a/dashboard-ui/scripts/actionsheet.js +++ b/dashboard-ui/scripts/actionsheet.js @@ -144,7 +144,7 @@ }); // Seeing an issue in some non-chrome browsers where this is requiring a double click - var eventName = browserInfo.chrome || browserInfo.safari ? 'click' : 'mousedown'; + var eventName = browserInfo.chrome ? 'click' : 'mousedown'; $('.actionSheetMenuItem', dlg).on(eventName, function () { From 8aa8dcf42c091b860977bed5a209270a6e446bdc Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 26 Dec 2015 03:07:24 -0500 Subject: [PATCH 08/12] update subtitles --- dashboard-ui/mypreferenceslanguages.html | 1 + dashboard-ui/strings/html/server.json | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dashboard-ui/mypreferenceslanguages.html b/dashboard-ui/mypreferenceslanguages.html index 99160380b8..f2491977fa 100644 --- a/dashboard-ui/mypreferenceslanguages.html +++ b/dashboard-ui/mypreferenceslanguages.html @@ -34,6 +34,7 @@ + From 5e64aeba542984a3760a0bf9ce9b9131eaef9df4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 25 Dec 2015 21:44:32 -0500 Subject: [PATCH 12/12] use supported formats for webma detection --- dashboard-ui/scripts/mediaplayer.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dashboard-ui/scripts/mediaplayer.js b/dashboard-ui/scripts/mediaplayer.js index 64022fe71b..a554a616c7 100644 --- a/dashboard-ui/scripts/mediaplayer.js +++ b/dashboard-ui/scripts/mediaplayer.js @@ -166,11 +166,11 @@ }); } - ['opus', 'mp3', 'aac'].forEach(function(audioFormat){ + ['opus', 'mp3', 'aac', 'webma'].forEach(function(audioFormat){ if (supportedFormats.indexOf(audioFormat) != -1) { profile.DirectPlayProfiles.push({ - Container: audioFormat, + Container: audioFormat == 'webma' ? 'webma,webm' : audioFormat, Type: 'Audio' }); } @@ -190,10 +190,6 @@ Container: 'webm', Type: 'Video' }); - profile.DirectPlayProfiles.push({ - Container: 'webm,webma', - Type: 'Audio' - }); } profile.TranscodingProfiles = []; @@ -1904,6 +1900,10 @@ list.push('opus'); } + if (document.createElement('audio').canPlayType('audio/webm').replace(/no/, '')) { + list.push('webma'); + } + if (browserInfo.chrome) { list.push('mkv'); }