diff --git a/dashboard-ui/bower_components/emby-apiclient/.bower.json b/dashboard-ui/bower_components/emby-apiclient/.bower.json index 1a3df41565..da5da08783 100644 --- a/dashboard-ui/bower_components/emby-apiclient/.bower.json +++ b/dashboard-ui/bower_components/emby-apiclient/.bower.json @@ -16,12 +16,12 @@ }, "devDependencies": {}, "ignore": [], - "version": "1.1.107", - "_release": "1.1.107", + "version": "1.1.125", + "_release": "1.1.125", "_resolution": { "type": "version", - "tag": "1.1.107", - "commit": "82a9be9ffc4359043cbbae83031491dc4d2182cc" + "tag": "1.1.125", + "commit": "de914c8db94b994ea45746626ba73bfb72a594a0" }, "_source": "https://github.com/MediaBrowser/Emby.ApiClient.Javascript.git", "_target": "^1.1.51", diff --git a/dashboard-ui/bower_components/emby-apiclient/apiclient.js b/dashboard-ui/bower_components/emby-apiclient/apiclient.js index 95980106ca..761129730e 100644 --- a/dashboard-ui/bower_components/emby-apiclient/apiclient.js +++ b/dashboard-ui/bower_components/emby-apiclient/apiclient.js @@ -589,7 +589,8 @@ var url = self.getUrl("socket"); url = replaceAll(url, 'emby/socket', 'embywebsocket'); - url = replaceAll(url, 'http', 'ws'); + url = replaceAll(url, 'https:', 'wss:'); + url = replaceAll(url, 'http:', 'ws:'); url += "?api_key=" + accessToken; url += "&deviceId=" + deviceId; @@ -1407,6 +1408,26 @@ return self.getJSON(url); }; + self.getPlaybackInfo = function(itemId, options, deviceProfile) { + + var postData = { + DeviceProfile: deviceProfile + }; + + return self.ajax({ + url: self.getUrl('Items/' + itemId + '/PlaybackInfo', options), + type: 'POST', + data: JSON.stringify(postData), + contentType: "application/json", + dataType: "json" + }); + }; + + self.getIntros = function(itemId) { + + return self.getJSON(self.getUrl('Users/' + self.getCurrentUserId() + '/Items/' + itemId + '/Intros')); + }; + /** * Gets the directory contents of a path on the server */ @@ -2122,6 +2143,19 @@ return self.getJSON(url); }; + self.getItemDownloadUrl = function (itemId) { + + if (!itemId) { + throw new Error("itemId cannot be empty"); + } + + var url = "Items/" + itemId + "/Download"; + + return self.getUrl(url, { + api_key: self.accessToken() + }); + }; + self.getSessions = function (options) { var url = self.getUrl("Sessions", options); @@ -2617,9 +2651,10 @@ var url = self.getUrl("Users/authenticatebyname"); - require(["cryptojs-sha1"], function () { + require(["cryptojs-sha1", "cryptojs-md5"], function () { var postData = { - password: CryptoJS.SHA1(password || "").toString(), + Password: CryptoJS.SHA1(password || "").toString(), + PasswordMd5: CryptoJS.MD5(password || "").toString(), Username: name }; diff --git a/dashboard-ui/bower_components/emby-apiclient/apiclientex.js b/dashboard-ui/bower_components/emby-apiclient/apiclientex.js new file mode 100644 index 0000000000..026d32f89f --- /dev/null +++ b/dashboard-ui/bower_components/emby-apiclient/apiclientex.js @@ -0,0 +1,373 @@ +define(['apiclientcore', 'localassetmanager', 'events'], function (apiclientcorefactory, localassetmanager, events) { + 'use strict'; + + var localPrefix = 'local:'; + var localViewPrefix = 'localview:'; + + /** + * Creates a new api client instance + * @param {String} serverAddress + * @param {String} clientName s + * @param {String} applicationVersion + */ + return function (serverAddress, clientName, applicationVersion, deviceName, deviceId, devicePixelRatio) { + + var apiclientcore = new apiclientcorefactory(serverAddress, clientName, applicationVersion, deviceName, deviceId, devicePixelRatio); + + var self = Object.assign(this, apiclientcore); + + events.on(apiclientcore, 'websocketmessage', onWebSocketMessage); + + Object.defineProperty(self, 'onAuthenticated', { + get: function () { return apiclientcore.onAuthenticated; }, + set: function (newValue) { apiclientcore.onAuthenticated = newValue; } + }); + + Object.defineProperty(self, 'enableAutomaticBitrateDetection', { + get: function () { return apiclientcore.enableAutomaticBitrateDetection; }, + set: function (newValue) { apiclientcore.enableAutomaticBitrateDetection = newValue; } + }); + + function getUserViews(userId) { + + return apiclientcore.getUserViews(userId).then(function (result) { + + var serverInfo = apiclientcore.serverInfo(); + + if (serverInfo) { + + return getLocalView(serverInfo.Id, userId).then(function (localView) { + + if (localView) { + + result.Items.push(localView); + result.TotalRecordCount++; + } + + return Promise.resolve(result); + }); + } + + return Promis.resolve(result); + }); + } + + function getLocalView(serverId, userId) { + + return localassetmanager.getViews(serverId, userId).then(function (views) { + + var localView = null; + + if (views.length > 0) { + + localView = { + Name: 'Offline Items', + ServerId: serverId, + Id: 'localview', + Type: 'localview' + }; + } + + return Promise.resolve(localView); + }); + } + + function getItems(userId, options) { + + var serverInfo = apiclientcore.serverInfo(); + var i; + + if (serverInfo && options.ParentId === 'localview') { + + return localassetmanager.getViews(serverInfo.Id, userId).then(function (items) { + var result = { + Items: items, + TotalRecordCount: items.length + }; + + return Promise.resolve(result); + }); + + } else if (serverInfo && options && (startsWith(options.ParentId, localViewPrefix) || startsWith(options.ParentId, localPrefix))) { + + return localassetmanager.getViewItems(serverInfo.Id, userId, options.ParentId).then(function (items) { + + items.forEach(function (item) { + item.Id = localPrefix + item.Id; + }); + + items.sort(function (a, b) { return a.SortName.toLowerCase().localeCompare(b.SortName.toLowerCase()); }); + + var result = { + Items: items, + TotalRecordCount: items.length + }; + + return Promise.resolve(result); + }); + } else if (options && options.ExcludeItemIds && options.ExcludeItemIds.length) { + + var exItems = options.ExcludeItemIds.split(','); + + for (i = 0; i < exItems.length; i++) { + if (startsWith(exItems[i], localPrefix)) { + return Promise.resolve(createEmptyList()); + } + } + } else if (options && options.Ids && options.Ids.length) { + + var ids = options.Ids.split(','); + var hasLocal = false; + + for (i = 0; i < ids.length; i++) { + if (startsWith(ids[i], localPrefix)) { + hasLocal = true; + } + } + + if (hasLocal) { + return localassetmanager.getItemsFromIds(serverInfo.Id, ids).then(function (items) { + + items.forEach(function (item) { + item.Id = localPrefix + item.Id; + }); + + var result = { + Items: items, + TotalRecordCount: items.length + }; + + return Promise.resolve(result); + }); + } + } + + return apiclientcore.getItems(userId, options); + } + + function getItem(userId, itemId) { + + if (itemId) { + itemId = itemId.toString(); + } + + var serverInfo; + + if (startsWith(itemId, localViewPrefix)) { + + serverInfo = apiclientcore.serverInfo(); + + if (serverInfo) { + return localassetmanager.getViews(serverInfo.Id, userId).then(function (items) { + + var views = items.filter(function (item) { + return item.Id === itemId; + }); + + if (views.length > 0) { + return Promise.resolve(views[0]); + } + + // TODO: Test consequence of this + return Promise.reject(); + }); + } + } + + if (startsWith(itemId, localPrefix)) { + + serverInfo = apiclientcore.serverInfo(); + + if (serverInfo) { + return localassetmanager.getLocalItem(serverInfo.Id, stripStart(itemId, localPrefix)).then(function (item) { + + item.Item.Id = localPrefix + item.Item.Id; + + return Promise.resolve(item.Item); + }); + } + } + + return apiclientcore.getItem(userId, itemId); + } + + function getNextUpEpisodes(options) { + + if (options.SeriesId) { + if (startsWith(options.SeriesId, localPrefix)) { + return Promise.resolve(createEmptyList()); + } + } + + return apiclientcore.getNextUpEpisodes(options); + } + + function getSeasons(itemId, options) { + + if (startsWith(itemId, localPrefix)) { + options.ParentId = itemId; + return getItems(apiclientcore.getCurrentUserId(), options); + } + + return apiclientcore.getSeasons(itemId, options); + } + + function getEpisodes(itemId, options) { + + if (startsWith(options.SeasonId, localPrefix)) { + options.ParentId = options.SeasonId; + return getItems(apiclientcore.getCurrentUserId(), options); + } + + return apiclientcore.getEpisodes(itemId, options); + } + + function getThemeMedia(userId, itemId, inherit) { + + if (startsWith(itemId, localViewPrefix) || startsWith(itemId, localPrefix)) { + return Promise.reject(); + } + + return apiclientcore.getThemeMedia(userId, itemId, inherit); + } + + function getSimilarItems(itemId, options) { + + if (startsWith(itemId, localPrefix)) { + return Promise.resolve(createEmptyList()); + } + + return apiclientcore.getSimilarItems(itemId, options); + } + + function updateFavoriteStatus(userId, itemId, isFavorite) { + + if (startsWith(itemId, localPrefix)) { + return Promise.resolve(); + } + + return apiclientcore.updateFavoriteStatus(userId, itemId, isFavorite); + } + + function getScaledImageUrl(itemId, options) { + + if (startsWith(itemId, localPrefix)) { + + var serverInfo = apiclientcore.serverInfo(); + var id = stripStart(itemId, localPrefix); + + return localassetmanager.getImageUrl(serverInfo.Id, id, options.type, 0); + } + + + return apiclientcore.getScaledImageUrl(itemId, options); + } + + function onWebSocketMessage(e, msg) { + + events.trigger(self, 'websocketmessage', [msg]); + } + + // **************** Helper functions + + function startsWith(str, find) { + + if (str && find && str.length > find.length) { + if (str.indexOf(find) === 0) { + return true; + } + } + + return false; + } + + function stripStart(str, find) { + if (startsWith(str, find)) { + return str.substr(find.length); + } + + return str; + } + + function createEmptyList() { + var result = { + Items: [], + TotalRecordCount: 0 + }; + + return result; + } + + function getPlaybackInfo(itemId, options, deviceProfile) { + + return localassetmanager.getLocalItem(apiclientcore.serverId(), stripStart(itemId, localPrefix)).then(function (item) { + + // TODO: This was already done during the sync process, right? If so, remove it + var mediaSources = item.Item.MediaSources.map(function (m) { + m.SupportsDirectPlay = true; + m.SupportsDirectStream = false; + m.SupportsTranscoding = false; + return m; + }); + + return { + MediaSources: mediaSources + }; + }); + } + + // "Override" methods + + self.detectBitrate = function () { + return Promise.reject(); + }; + + self.reportPlaybackStart = function (options) { + + if (!options) { + throw new Error("null options"); + } + + return Promise.resolve(); + }; + + self.reportPlaybackProgress = function (options) { + + if (!options) { + throw new Error("null options"); + } + + return Promise.resolve(); + }; + + self.reportPlaybackStopped = function (options) { + + if (!options) { + throw new Error("null options"); + } + + return Promise.resolve(); + }; + + self.getIntros = function (itemId) { + + return Promise.resolve({ + Items: [], + TotalRecordCount: 0 + }); + }; + + self.getUserViews = getUserViews; + self.getItems = getItems; + self.getItem = getItem; + self.getSeasons = getSeasons; + self.getEpisodes = getEpisodes; + self.getThemeMedia = getThemeMedia; + self.getNextUpEpisodes = getNextUpEpisodes; + self.getSimilarItems = getSimilarItems; + self.updateFavoriteStatus = updateFavoriteStatus; + self.getScaledImageUrl = getScaledImageUrl; + self.getPlaybackInfo = getPlaybackInfo; + }; + +}); \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-apiclient/connectionmanager.js b/dashboard-ui/bower_components/emby-apiclient/connectionmanager.js index 66581b7e0a..6f4c1e7ba5 100644 --- a/dashboard-ui/bower_components/emby-apiclient/connectionmanager.js +++ b/dashboard-ui/bower_components/emby-apiclient/connectionmanager.js @@ -216,7 +216,7 @@ return connectUser; }; - var minServerVersion = '3.0.7200'; + var minServerVersion = '3.0.8500'; self.minServerVersion = function (val) { if (val) { @@ -251,6 +251,10 @@ return credentialProvider.credentials().ConnectAccessToken; }; + self.getApiClients = function () { + return apiClients; + }; + self.getServerInfo = function (id) { var servers = credentialProvider.credentials().Servers; @@ -353,6 +357,8 @@ function onConnectUserSignIn(user) { + appStorage.removeItem('lastLocalServerId'); + connectUser = user; events.trigger(self, 'connectusersignedin', [user]); } @@ -458,6 +464,12 @@ function onLocalUserSignIn(server, connectionMode, user) { + if (self.connectUserId()) { + appStorage.removeItem('lastLocalServerId'); + } else { + appStorage.setItem('lastLocalServerId', server.Id); + } + // Ensure this is created so that listeners of the event can get the apiClient instance getOrAddApiClient(server, connectionMode); @@ -471,29 +483,26 @@ function ensureConnectUser(credentials) { - return new Promise(function (resolve, reject) { + if (connectUser && connectUser.Id === credentials.ConnectUserId) { + return Promise.resolve(); + } - if (connectUser && connectUser.Id === credentials.ConnectUserId) { - resolve(); - } + else if (credentials.ConnectUserId && credentials.ConnectAccessToken) { - else if (credentials.ConnectUserId && credentials.ConnectAccessToken) { + connectUser = null; - connectUser = null; + return getConnectUser(credentials.ConnectUserId, credentials.ConnectAccessToken).then(function (user) { - getConnectUser(credentials.ConnectUserId, credentials.ConnectAccessToken).then(function (user) { + onConnectUserSignIn(user); + return Promise.resolve(); - onConnectUserSignIn(user); - resolve(); + }, function () { + return Promise.resolve(); + }); - }, function () { - resolve(); - }); - - } else { - resolve(); - } - }); + } else { + return Promise.resolve(); + } } function getConnectUrl(handler) { @@ -728,6 +737,10 @@ } } + if (credentials.ConnectAccessToken) { + appStorage.removeItem('lastLocalServerId'); + } + credentials.Servers = servers; credentials.ConnectAccessToken = null; credentials.ConnectUserId = null; @@ -926,9 +939,18 @@ console.log('Begin connectToServers, with ' + servers.length + ' servers'); - if (servers.length === 1) { + var defaultServer = servers.length === 1 ? servers[0] : null; - return self.connectToServer(servers[0], options).then(function (result) { + if (!defaultServer) { + var lastLocalServerId = appStorage.getItem('lastLocalServerId'); + defaultServer = servers.filter(function (s) { + return s.Id === lastLocalServerId; + })[0]; + } + + if (defaultServer) { + + return self.connectToServer(defaultServer, options).then(function (result) { if (result.State === ConnectionState.Unavailable) { diff --git a/dashboard-ui/bower_components/emby-apiclient/localassetmanager.js b/dashboard-ui/bower_components/emby-apiclient/localassetmanager.js index 60c1a9cf5f..c949636265 100644 --- a/dashboard-ui/bower_components/emby-apiclient/localassetmanager.js +++ b/dashboard-ui/bower_components/emby-apiclient/localassetmanager.js @@ -66,6 +66,26 @@ }); } + function getItemsFromIds(serverId, ids) { + + var actions = ids.map(function (id) { + var strippedId = stripStart(id, 'local:'); + + return getLocalItem(serverId, strippedId); + }); + + return Promise.all(actions).then(function (items) { + + var libItems = items.map(function (locItem) { + + return locItem.Item; + }); + + + return Promise.resolve(libItems); + }); + } + function getViews(serverId, userId) { return itemrepository.getServerItemTypes(serverId, userId).then(function (types) { @@ -80,7 +100,7 @@ ServerId: serverId, Id: 'localview:MusicView', Type: 'MusicView', - CollectionType: 'Music', + CollectionType: 'music', IsFolder: true }; @@ -94,7 +114,7 @@ ServerId: serverId, Id: 'localview:PhotosView', Type: 'PhotosView', - CollectionType: 'Photos', + CollectionType: 'photos', IsFolder: true }; @@ -108,23 +128,49 @@ ServerId: serverId, Id: 'localview:TVView', Type: 'TVView', - CollectionType: 'TvShows', + CollectionType: 'tvshows', IsFolder: true }; list.push(item); } - if (types.indexOf('video') > -1 || - types.indexOf('movie') > -1 || - types.indexOf('musicvideo') > -1) { + if (types.indexOf('movie') > -1) { + + item = { + Name: 'Movies', + ServerId: serverId, + Id: 'localview:MoviesView', + Type: 'MoviesView', + CollectionType: 'movies', + IsFolder: true + }; + + list.push(item); + } + + if (types.indexOf('video') > -1) { item = { Name: 'Videos', ServerId: serverId, Id: 'localview:VideosView', Type: 'VideosView', - CollectionType: 'HomeVideos', + CollectionType: 'videos', + IsFolder: true + }; + + list.push(item); + } + + if (types.indexOf('musicvideo') > -1) { + + item = { + Name: 'Music Videos', + ServerId: serverId, + Id: 'localview:MusicVideosView', + Type: 'MusicVideosView', + CollectionType: 'videos', IsFolder: true }; @@ -135,28 +181,80 @@ }); } + function getTypeFilterForTopLevelView(parentId) { + + var typeFilter = null; + + switch (parentId) { + case 'localview:MusicView': + typeFilter = 'audio'; + break; + case 'localview:PhotosView': + typeFilter = 'photo'; + break; + case 'localview:TVView': + typeFilter = 'episode'; + break; + case 'localview:VideosView': + typeFilter = 'video'; + break; + case 'localview:MoviesView': + typeFilter = 'movie'; + break; + case 'localview:MusicVideosView': + typeFilter = 'musicvideo'; + break; + } + + return typeFilter; + } + function getViewItems(serverId, userId, parentId) { + var typeFilter = getTypeFilterForTopLevelView(parentId); + + parentId = stripStart(parentId, 'localview:'); + parentId = stripStart(parentId, 'local:'); + return getServerItems(serverId).then(function (items) { - var resultItems = items.filter(function (item) { + var resultItemIds = items.filter(function (item) { - var type = (item.Item.Type || '').toLowerCase(); - - switch (parentId) { - case 'localview:MusicView': - return type === 'audio'; - case 'localview:PhotosView': - return type === 'photo'; - case 'localview:TVView': - return type === 'episode'; - case 'localview:VideosView': - return type === 'movie' || type === 'video' || type === 'musicvideo'; - default: - return false; + if (item.SyncStatus && item.SyncStatus !== 'synced') { + return false; } + + if (typeFilter) { + var type = (item.Item.Type || '').toLowerCase(); + return typeFilter === type; + } + + return item.Item.ParentId === parentId; + }).map(function (item2) { - return item2.Item; + + switch (typeFilter) { + case 'audio': + case 'photo': + return item2.Item.AlbumId; + case 'episode': + return item2.Item.SeriesId; + } + + return item2.Item.Id; + + }).filter(filterDistinct); + + var resultItems = []; + + items.forEach(function (item) { + var found = false; + + resultItemIds.forEach(function (id) { + if (item.Item.Id === id) { + resultItems.push(item.Item); + } + }); }); return Promise.resolve(resultItems); @@ -203,7 +301,14 @@ } function addOrUpdateLocalItem(localItem) { - return itemrepository.set(localItem.Id, localItem); + console.log('addOrUpdateLocalItem Start'); + return itemrepository.set(localItem.Id, localItem).then(function (res) { + console.log('addOrUpdateLocalItem Success'); + return Promise.resolve(true); + }, function (error) { + console.log('addOrUpdateLocalItem Error'); + return Promise.resolve(false); + }); } function createLocalItem(libraryItem, serverInfo, jobItem) { @@ -211,14 +316,19 @@ var path = getDirectoryPath(libraryItem, serverInfo); var localFolder = filerepository.getFullLocalPath(path); - path.push(getLocalFileName(libraryItem, jobItem.OriginalFileName)); + var localPath; - var localPath = filerepository.getFullLocalPath(path); + if (jobItem) { + path.push(getLocalFileName(libraryItem, jobItem.OriginalFileName)); + localPath = filerepository.getFullLocalPath(path); + } - for (var i = 0; i < libraryItem.MediaSources.length; i++) { - var mediaSource = libraryItem.MediaSources[i]; - mediaSource.Path = localPath; - mediaSource.Protocol = 'File'; + if (libraryItem.MediaSources) { + for (var i = 0; i < libraryItem.MediaSources.length; i++) { + var mediaSource = libraryItem.MediaSources[i]; + mediaSource.Path = localPath; + mediaSource.Protocol = 'File'; + } } var item = { @@ -228,11 +338,14 @@ ServerId: serverInfo.Id, LocalPath: localPath, LocalFolder: localFolder, - AdditionalFiles: jobItem.AdditionalFiles.slice(0), - Id: getLocalId(serverInfo.Id, libraryItem.Id), - SyncJobItemId: jobItem.SyncJobItemId + Id: getLocalId(serverInfo.Id, libraryItem.Id) }; + if (jobItem) { + item.AdditionalFiles = jobItem.AdditionalFiles.slice(0); + item.SyncJobItemId = jobItem.SyncJobItemId; + } + return Promise.resolve(item); } @@ -276,12 +389,14 @@ function downloadFile(url, localItem) { - return transfermanager.downloadFile(url, localItem); + var folder = filerepository.getLocalPath(); + return transfermanager.downloadFile(url, folder, localItem); } function downloadSubtitles(url, fileName) { - return transfermanager.downloadSubtitles(url, fileName); + var folder = filerepository.getLocalPath(); + return transfermanager.downloadSubtitles(url, folder, fileName); } function getImageUrl(serverId, itemId, imageType, index) { @@ -296,7 +411,7 @@ function hasImage(serverId, itemId, imageType, index) { var pathArray = getImagePath(serverId, itemId, imageType, index); - var localFilePath = filerepository.getFullLocalPath(pathArray); + var localFilePath = filerepository.getFullMetadataPath(pathArray); return filerepository.fileExists(localFilePath).then(function (exists) { // TODO: Maybe check for broken download when file size is 0 and item is not queued @@ -316,7 +431,7 @@ function downloadImage(localItem, url, serverId, itemId, imageType, index) { var pathArray = getImagePath(serverId, itemId, imageType, index); - var localFilePath = filerepository.getFullLocalPath(pathArray); + var localFilePath = filerepository.getFullMetadataPath(pathArray); if (!localItem.AdditionalFiles) { localItem.AdditionalFiles = []; @@ -331,7 +446,8 @@ localItem.AdditionalFiles.push(fileInfo); - return transfermanager.downloadImage(url, localFilePath); + var folder = filerepository.getMetadataPath(); + return transfermanager.downloadImage(url, folder, localFilePath); } function isDownloadFileInQueue(path) { @@ -339,6 +455,11 @@ return transfermanager.isDownloadFileInQueue(path); } + function getDownloadItemCount() { + + return transfermanager.getDownloadItemCount(); + } + function translateFilePath(path) { return Promise.resolve(path); } @@ -409,7 +530,9 @@ parts.push('Metadata'); parts.push(serverId); parts.push('images'); - parts.push(itemId + '_' + imageType + '_' + index.toString() + '.png'); + // Store without extension. This allows mixed image types since the browser will + // detect the type from the content + parts.push(itemId + '_' + imageType + '_' + index.toString()); // + '.jpg'); var finalParts = []; for (var i = 0; i < parts.length; i++) { @@ -444,6 +567,29 @@ return uuid; } + function startsWith(str, find) { + + if (str && find && str.length > find.length) { + if (str.indexOf(find) === 0) { + return true; + } + } + + return false; + } + + function stripStart(str, find) { + if (startsWith(str, find)) { + return str.substr(find.length); + } + + return str; + } + + function filterDistinct(value, index, self) { + return self.indexOf(value) === index; + } + return { getLocalItem: getLocalItem, @@ -468,8 +614,10 @@ getServerItems: getServerItems, getItemFileSize: getItemFileSize, isDownloadFileInQueue: isDownloadFileInQueue, + getDownloadItemCount: getDownloadItemCount, getViews: getViews, getViewItems: getViewItems, - resyncTransfers: resyncTransfers + resyncTransfers: resyncTransfers, + getItemsFromIds: getItemsFromIds }; }); \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-apiclient/sync/itemrepository.js b/dashboard-ui/bower_components/emby-apiclient/sync/itemrepository.js index c9a97831fe..bd076e35d7 100644 --- a/dashboard-ui/bower_components/emby-apiclient/sync/itemrepository.js +++ b/dashboard-ui/bower_components/emby-apiclient/sync/itemrepository.js @@ -90,7 +90,7 @@ function clear() { return dbPromise.then(function (db) { var tx = db.transaction(dbName, 'readwrite'); - tx.objectStore(dbName).clear(key); + tx.objectStore(dbName).clear(); return tx.complete; }); } diff --git a/dashboard-ui/bower_components/emby-apiclient/sync/mediasync.js b/dashboard-ui/bower_components/emby-apiclient/sync/mediasync.js index df681c3a73..a36b369136 100644 --- a/dashboard-ui/bower_components/emby-apiclient/sync/mediasync.js +++ b/dashboard-ui/bower_components/emby-apiclient/sync/mediasync.js @@ -178,7 +178,7 @@ }); } - function getNewMedia(apiClient, serverInfo, options) { + function getNewMedia(apiClient, serverInfo, options, downloadCount) { console.log('[mediasync] Begin getNewMedia'); @@ -186,10 +186,15 @@ var p = Promise.resolve(); + var maxDownloads = 10; + var currentCount = downloadCount; + jobItems.forEach(function (jobItem) { - p = p.then(function () { - return getNewItem(jobItem, apiClient, serverInfo, options); - }); + if (currentCount++ <= maxDownloads) { + p = p.then(function () { + return getNewItem(jobItem, apiClient, serverInfo, options); + }); + } }); return p.then(function () { @@ -216,7 +221,9 @@ libraryItem.CanDelete = false; libraryItem.CanDownload = false; + libraryItem.SupportsSync = false; libraryItem.People = []; + libraryItem.UserData = []; libraryItem.SpecialFeatureCount = null; return localassetmanager.createLocalItem(libraryItem, serverInfo, jobItem).then(function (localItem) { @@ -225,18 +232,94 @@ localItem.SyncStatus = 'queued'; - return downloadMedia(apiClient, jobItem, localItem, options).then(function () { + return downloadParentItems(apiClient, jobItem, localItem, serverInfo, options).then(function () { - return getImages(apiClient, jobItem, localItem).then(function () { + return downloadMedia(apiClient, jobItem, localItem, options).then(function () { - return getSubtitles(apiClient, jobItem, localItem); + return getImages(apiClient, jobItem, localItem).then(function () { + return getSubtitles(apiClient, jobItem, localItem); + + }); }); }); }); }); } + function downloadParentItems(apiClient, jobItem, localItem, serverInfo, options) { + + var p = Promise.resolve(); + + var libraryItem = localItem.Item; + + var itemType = (libraryItem.Type || '').toLowerCase(); + var logoImageTag = (libraryItem.ImageTags || {}).Logo; + + switch (itemType) { + case 'episode': + if (libraryItem.SeriesId && libraryItem.SeriesId) { + p = p.then(function () { + return downloadItem(apiClient, libraryItem, libraryItem.SeriesId, serverInfo).then(function (seriesItem) { + libraryItem.SeriesLogoImageTag = (seriesItem.Item.ImageTags || {}).Logo; + return Promise.resolve(); + }); + }); + } + if (libraryItem.SeasonId && libraryItem.SeasonId) { + p = p.then(function () { + return downloadItem(apiClient, libraryItem, libraryItem.SeasonId, serverInfo).then(function (seasonItem) { + libraryItem.SeasonPrimaryImageTag = (seasonItem.Item.ImageTags || {}).Primary; + return Promise.resolve(); + }); + }); + } + break; + + case 'audio': + case 'photo': + if (libraryItem.AlbumId && libraryItem.AlbumId) { + p = p.then(function () { + return downloadItem(apiClient, libraryItem, libraryItem.AlbumId, serverInfo); + }); + } + break; + + case 'video': + case 'movie': + case 'musicvideo': + // no parent item download for now + break; + } + + return p; + } + + function downloadItem(apiClient, libraryItem, itemId, serverInfo) { + + return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (downloadedItem) { + + downloadedItem.CanDelete = false; + downloadedItem.CanDownload = false; + downloadedItem.SupportsSync = false; + downloadedItem.People = []; + downloadedItem.UserData = {}; + downloadedItem.SpecialFeatureCount = null; + downloadedItem.BackdropImageTags = null; + + return localassetmanager.createLocalItem(downloadedItem, serverInfo, null).then(function (localItem) { + + return localassetmanager.addOrUpdateLocalItem(localItem).then(function () { + return Promise.resolve(localItem); + }); + }); + }, function (err) { + + console.error('[mediasync] downloadItem failed: ' + err.toString()); + return Promise.resolve(null); + }); + } + function downloadMedia(apiClient, jobItem, localItem, options) { var url = apiClient.getUrl('Sync/JobItems/' + jobItem.SyncJobItemId + '/File', { @@ -327,8 +410,23 @@ p = p.then(function () { return downloadImage(localItem, apiClient, serverId, libraryItem.SeriesId, libraryItem.SeriesPrimaryImageTag, 'Primary'); }); + } + + if (libraryItem.SeriesId && libraryItem.SeriesThumbImageTag) { p = p.then(function () { - return downloadImage(localItem, apiClient, serverId, libraryItem.SeriesId, libraryItem.SeriesPrimaryImageTag, 'Thumb'); + return downloadImage(localItem, apiClient, serverId, libraryItem.SeriesId, libraryItem.SeriesThumbImageTag, 'Thumb'); + }); + } + + if (libraryItem.SeriesId && libraryItem.SeriesLogoImageTag) { + p = p.then(function () { + return downloadImage(localItem, apiClient, serverId, libraryItem.SeriesId, libraryItem.SeriesLogoImageTag, 'Logo'); + }); + } + + if (libraryItem.SeasonId && libraryItem.SeasonPrimaryImageTag) { + p = p.then(function () { + return downloadImage(localItem, apiClient, serverId, libraryItem.SeasonId, libraryItem.SeasonPrimaryImageTag, 'Primary'); }); } @@ -344,6 +442,7 @@ return localassetmanager.addOrUpdateLocalItem(localItem); }, function (err) { console.log('[mediasync] Error getImages: ' + err.toString()); + return Promise.resolve(); }); } @@ -359,18 +458,30 @@ return Promise.resolve(); } - var imageUrl = apiClient.getImageUrl(itemId, { + var maxWidth = 400; + + if (imageType === 'backdrop') { + maxWidth = null; + } + + var imageUrl = apiClient.getScaledImageUrl(itemId, { tag: imageTag, type: imageType, - format: 'png', + maxWidth: maxWidth, api_key: apiClient.accessToken() }); console.log('[mediasync] downloadImage ' + itemId + ' ' + imageType + '_' + index.toString()); - return localassetmanager.downloadImage(localItem, imageUrl, serverId, itemId, imageType, index); + return localassetmanager.downloadImage(localItem, imageUrl, serverId, itemId, imageType, index).then(function (result) { + return Promise.resolve(); + }, function (err) { + console.log('[mediasync] Error downloadImage: ' + err.toString()); + return Promise.resolve(); + }); }, function (err) { console.log('[mediasync] Error downloadImage: ' + err.toString()); + return Promise.resolve(); }); } @@ -450,26 +561,28 @@ return processDownloadStatus(apiClient, serverInfo, options).then(function () { - if (options.syncCheckProgressOnly === true) { - return Promise.resolve(); - } + return localassetmanager.getDownloadItemCount().then(function (downloadCount) { - return reportOfflineActions(apiClient, serverInfo).then(function () { + if (options.syncCheckProgressOnly === true && downloadCount > 2) { + return Promise.resolve(); + } - //// Do the first data sync - //return syncData(apiClient, serverInfo, false).then(function () { + return reportOfflineActions(apiClient, serverInfo).then(function () { - // Download new content - return getNewMedia(apiClient, serverInfo, options).then(function () { + // Download new content + return getNewMedia(apiClient, serverInfo, options, downloadCount).then(function () { - // Do the second data sync - return syncData(apiClient, serverInfo, false).then(function () { - console.log('[mediasync]************************************* Exit sync'); - return Promise.resolve(); + // Do the second data sync + return syncData(apiClient, serverInfo, false).then(function () { + console.log('[mediasync]************************************* Exit sync'); + return Promise.resolve(); + }); }); + //}); }); - //}); }); + }, function (err) { + console.error(err.toString()); }); }; }; diff --git a/dashboard-ui/bower_components/emby-apiclient/sync/transfermanager.js b/dashboard-ui/bower_components/emby-apiclient/sync/transfermanager.js index f8bea7307b..309823e1f4 100644 --- a/dashboard-ui/bower_components/emby-apiclient/sync/transfermanager.js +++ b/dashboard-ui/bower_components/emby-apiclient/sync/transfermanager.js @@ -1,17 +1,17 @@ define(['filerepository'], function (filerepository) { 'use strict'; - function downloadFile(url, localPath) { + function downloadFile(url, folderName, localPath) { return Promise.resolve(); } - function downloadSubtitles(url, localItem, subtitleStreamh) { + function downloadSubtitles(url, folderName, localItem) { return Promise.resolve(''); } - function downloadImage(url, serverId, itemId, imageTag) { + function downloadImage(url, folderName, serverId, itemId, imageTag) { return Promise.resolve(false); } diff --git a/dashboard-ui/bower_components/emby-webcomponents/.bower.json b/dashboard-ui/bower_components/emby-webcomponents/.bower.json index 43c84e47cf..b5d83fb2bb 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/.bower.json +++ b/dashboard-ui/bower_components/emby-webcomponents/.bower.json @@ -14,12 +14,12 @@ }, "devDependencies": {}, "ignore": [], - "version": "1.4.406", - "_release": "1.4.406", + "version": "1.4.490", + "_release": "1.4.490", "_resolution": { "type": "version", - "tag": "1.4.406", - "commit": "5ef7b315244a1804f2892269a42db94a52a86ea8" + "tag": "1.4.490", + "commit": "d0ee6da0b7661ff97d3501ea7633d113d3cb5a99" }, "_source": "https://github.com/MediaBrowser/emby-webcomponents.git", "_target": "^1.2.1", diff --git a/dashboard-ui/bower_components/emby-webcomponents/LICENSE.md b/dashboard-ui/bower_components/emby-webcomponents/LICENSE.md index 8f5a547ed2..4522ba0659 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/LICENSE.md +++ b/dashboard-ui/bower_components/emby-webcomponents/LICENSE.md @@ -1,22 +1,339 @@ -The MIT License +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 -Copyright (c) Emby https://emby.media + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + Preamble -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {{description}} + Copyright (C) {{year}} {{fullname}} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/dashboard-ui/bower_components/emby-webcomponents/actionsheet/actionsheet.css b/dashboard-ui/bower_components/emby-webcomponents/actionsheet/actionsheet.css index 5d16f7945d..97f36906f8 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/actionsheet/actionsheet.css +++ b/dashboard-ui/bower_components/emby-webcomponents/actionsheet/actionsheet.css @@ -97,7 +97,7 @@ } .actionSheetTitle { - margin: .5em 0 1em !important; + margin: .5em 0 !important; padding: 0 1em; flex-grow: 0; } diff --git a/dashboard-ui/bower_components/emby-webcomponents/appsettings.js b/dashboard-ui/bower_components/emby-webcomponents/appsettings.js index 06b4a79ccd..37d0d4ed04 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/appsettings.js +++ b/dashboard-ui/bower_components/emby-webcomponents/appsettings.js @@ -47,7 +47,8 @@ define(['appStorage', 'events'], function (appStorage, events) { self.set('maxStaticMusicBitrate', val); } - return parseInt(self.get('maxStaticMusicBitrate') || '0') || null; + var defaultValue = 384000; + return parseInt(self.get('maxStaticMusicBitrate') || defaultValue.toString()) || defaultValue; }; self.maxChromecastBitrate = function (val) { diff --git a/dashboard-ui/bower_components/emby-webcomponents/backdrop/backdrop.js b/dashboard-ui/bower_components/emby-webcomponents/backdrop/backdrop.js index 3c5121a408..fd4cb438f8 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/backdrop/backdrop.js +++ b/dashboard-ui/bower_components/emby-webcomponents/backdrop/backdrop.js @@ -201,7 +201,7 @@ return item.BackdropImageTags.map(function (imgTag, index) { - return apiClient.getScaledImageUrl(item.Id, Object.assign(imageOptions, { + return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, { type: "Backdrop", tag: imgTag, maxWidth: getBackdropMaxWidth(), @@ -294,14 +294,14 @@ currentRotationIndex = -1; if (images.length > 1 && enableImageRotation !== false && enableRotation()) { - rotationInterval = setInterval(onRotationInterval, 20000); + rotationInterval = setInterval(onRotationInterval, 24000); } onRotationInterval(); } function onRotationInterval() { - if (playbackManager.isPlayingVideo()) { + if (playbackManager.isPlayingLocally(['Video'])) { return; } diff --git a/dashboard-ui/bower_components/emby-webcomponents/browser.js b/dashboard-ui/bower_components/emby-webcomponents/browser.js index cacaa3a57e..b403bad12e 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/browser.js +++ b/dashboard-ui/bower_components/emby-webcomponents/browser.js @@ -248,16 +248,25 @@ browser.tv = true; } + if (userAgent.toLowerCase().indexOf("embytheaterpi") !== -1) { + browser.slow = true; + browser.noAnimation = true; + } + if (isMobile(userAgent)) { browser.mobile = true; } browser.xboxOne = userAgent.toLowerCase().indexOf('xbox') !== -1; browser.animate = typeof document !== 'undefined' && document.documentElement.animate != null; - browser.tizen = userAgent.toLowerCase().indexOf('tizen') !== -1 || userAgent.toLowerCase().indexOf('smarthub') !== -1; + browser.tizen = userAgent.toLowerCase().indexOf('tizen') !== -1 || self.tizen != null; browser.web0s = userAgent.toLowerCase().indexOf('Web0S'.toLowerCase()) !== -1; browser.edgeUwp = browser.edge && userAgent.toLowerCase().indexOf('msapphost') !== -1; + if (!browser.tizen) { + browser.orsay = userAgent.toLowerCase().indexOf('smarthub') !== -1; + } + if (browser.edgeUwp) { browser.edge = true; } diff --git a/dashboard-ui/bower_components/emby-webcomponents/browserdeviceprofile.js b/dashboard-ui/bower_components/emby-webcomponents/browserdeviceprofile.js index c73c9071d4..7180f88f4c 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/browserdeviceprofile.js +++ b/dashboard-ui/bower_components/emby-webcomponents/browserdeviceprofile.js @@ -370,6 +370,16 @@ define(['browser'], function (browser) { profile.TranscodingProfiles = []; + if (canPlayNativeHls() && options.enableHlsAudio) { + profile.TranscodingProfiles.push({ + Container: 'ts', + Type: 'Audio', + AudioCodec: 'aac', + Context: 'Streaming', + Protocol: 'hls' + }); + } + ['opus', 'mp3', 'aac', 'wav'].filter(canPlayAudioFormat).forEach(function (audioFormat) { profile.TranscodingProfiles.push({ @@ -390,13 +400,8 @@ define(['browser'], function (browser) { }); }); - var copyTimestamps = false; - if (browser.chrome) { - copyTimestamps = true; - } - // Can't use mkv on mobile because we have to use the native player controls and they won't be able to seek it - if (canPlayMkv && options.supportsCustomSeeking && !browser.tizen && options.enableMkvProgressive !== false) { + if (canPlayMkv && !browser.tizen && options.enableMkvProgressive !== false) { profile.TranscodingProfiles.push({ Container: 'mkv', Type: 'Video', @@ -404,7 +409,7 @@ define(['browser'], function (browser) { VideoCodec: 'h264', Context: 'Streaming', MaxAudioChannels: physicalAudioChannels.toString(), - CopyTimestamps: copyTimestamps + CopyTimestamps: true }); } @@ -435,7 +440,6 @@ define(['browser'], function (browser) { } if (canPlayWebm) { - profile.TranscodingProfiles.push({ Container: 'webm', Type: 'Video', @@ -557,6 +561,16 @@ define(['browser'], function (browser) { } var isTizenFhd = false; + if (browser.tizen) { + try { + var isTizenUhd = webapis.productinfo.isUdPanelSupported(); + isTizenFhd = !isTizenUhd; + console.log("isTizenFhd = " + isTizenFhd); + } catch (error) { + console.log("isUdPanelSupported() error code = " + error.code); + } + } + var globalMaxVideoBitrate = browser.ps4 ? '8000000' : (browser.xboxOne ? '10000000' : (browser.edgeUwp ? '40000000' : diff --git a/dashboard-ui/bower_components/emby-webcomponents/cardbuilder/cardbuilder.js b/dashboard-ui/bower_components/emby-webcomponents/cardbuilder/cardbuilder.js index b92d051bb1..b4aee60549 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/cardbuilder/cardbuilder.js +++ b/dashboard-ui/bower_components/emby-webcomponents/cardbuilder/cardbuilder.js @@ -6,15 +6,13 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana function getCardsHtml(items, options) { - var apiClient = connectionManager.currentApiClient(); - if (arguments.length === 1) { options = arguments[0]; items = options.items; } - var html = buildCardsHtmlInternal(items, apiClient, options); + var html = buildCardsHtmlInternal(items, options); return html; } @@ -249,12 +247,15 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana else if (options.shape === 'square') { options.width = options.width || 243; } + else if (options.shape === 'banner') { + options.width = options.width || 800; + } } options.width = options.width || getImageWidth(options.shape); } - function buildCardsHtmlInternal(items, apiClient, options) { + function buildCardsHtmlInternal(items, options) { var isVertical; @@ -269,7 +270,7 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana setCardData(items, options); if (options.indexBy === 'Genres') { - return buildCardsByGenreHtmlInternal(items, apiClient, options); + return buildCardsByGenreHtmlInternal(items, options); } var className = 'card'; @@ -290,10 +291,18 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana var hasOpenSection; var sectionTitleTagName = options.sectionTitleTagName || 'div'; + var apiClient; + var lastServerId; for (var i = 0, length = items.length; i < length; i++) { var item = items[i]; + var serverId = item.ServerId || options.serverId; + + if (serverId !== lastServerId) { + lastServerId = serverId; + apiClient = connectionManager.getApiClient(lastServerId); + } if (options.indexBy) { var newIndexValue = ''; @@ -404,7 +413,7 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana }); } - function buildCardsByGenreHtmlInternal(items, apiClient, options) { + function buildCardsByGenreHtmlInternal(items, options) { var className = 'card'; @@ -435,7 +444,7 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana } var cardClass = className; - currentItemHtml += buildCard(i, renderItem, apiClient, options, cardClass); + currentItemHtml += buildCard(i, renderItem, connectionManager.getApiClient(renderItem.ServerId || options.serverId), options, cardClass); itemsInRow++; @@ -498,6 +507,9 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana if (shape.indexOf('square') !== -1) { return 1; } + if (shape.indexOf('banner') !== -1) { + return (1000 / 185); + } } return null; } @@ -583,7 +595,7 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; - imgUrl = apiClient.getScaledImageUrl(item.Id || item.ItemId, { + imgUrl = apiClient.getScaledImageUrl(item.PrimaryImageItemId || item.Id || item.ItemId, { type: "Primary", maxHeight: height, maxWidth: width, @@ -1412,9 +1424,7 @@ define(['datetime', 'imageLoader', 'connectionManager', 'itemHelper', 'focusMana } } - var apiClient = connectionManager.currentApiClient(); - - var html = buildCardsHtmlInternal(items, apiClient, options); + var html = buildCardsHtmlInternal(items, options); if (html) { diff --git a/dashboard-ui/scripts/chromecast.js b/dashboard-ui/bower_components/emby-webcomponents/chromecastplayer.js similarity index 64% rename from dashboard-ui/scripts/chromecast.js rename to dashboard-ui/bower_components/emby-webcomponents/chromecastplayer.js index 3ac9a14e80..6ccc16ed51 100644 --- a/dashboard-ui/scripts/chromecast.js +++ b/dashboard-ui/bower_components/emby-webcomponents/chromecastplayer.js @@ -1,891 +1,1084 @@ -define(['appSettings'], function (appSettings) { - 'use strict'; - - // 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 - **/ - var DEVICE_STATE = { - 'IDLE': 0, - 'ACTIVE': 1, - 'WARNING': 2, - 'ERROR': 3 - }; - - /** - * Constants of states for CastPlayer - **/ - var PLAYER_STATE = { - 'IDLE': 'IDLE', - 'LOADING': 'LOADING', - 'LOADED': 'LOADED', - 'PLAYING': 'PLAYING', - 'PAUSED': 'PAUSED', - 'STOPPED': 'STOPPED', - 'SEEKING': 'SEEKING', - 'ERROR': 'ERROR' - }; - - var applicationID = "2D4B1DA3"; - - // This is the beta version used for testing new changes - - //applicationID = '27C4EB5B'; - - var messageNamespace = 'urn:x-cast:com.connectsdk'; - - var CastPlayer = function () { - - /* device variables */ - // @type {DEVICE_STATE} A state for device - this.deviceState = DEVICE_STATE.IDLE; - - /* Cast player variables */ - // @type {Object} a chrome.cast.media.Media object - this.currentMediaSession = null; - - // @type {string} a chrome.cast.Session object - this.session = null; - // @type {PLAYER_STATE} A state for Cast media player - this.castPlayerState = PLAYER_STATE.IDLE; - - this.hasReceivers = false; - - // bind once - commit 2ebffc2271da0bc5e8b13821586aee2a2e3c7753 - this.errorHandler = this.onError.bind(this); - this.mediaStatusUpdateHandler = this.onMediaStatusUpdate.bind(this); - - this.initializeCastPlayer(); - }; - - /** - * Initialize Cast media player - * Initializes the API. Note that either successCallback and errorCallback will be - * invoked once the API has finished initialization. The sessionListener and - * receiverListener may be invoked at any time afterwards, and possibly more than once. - */ - CastPlayer.prototype.initializeCastPlayer = function () { - - var chrome = window.chrome; - - if (!chrome) { - return; - } - - if (!chrome.cast || !chrome.cast.isAvailable) { - - setTimeout(this.initializeCastPlayer.bind(this), 1000); - return; - } - - // request session - var sessionRequest = new chrome.cast.SessionRequest(applicationID); - var apiConfig = new chrome.cast.ApiConfig(sessionRequest, - this.sessionListener.bind(this), - this.receiverListener.bind(this), - "origin_scoped"); - - console.log('chromecast.initialize'); - - chrome.cast.initialize(apiConfig, this.onInitSuccess.bind(this), this.errorHandler); - - }; - - /** - * Callback function for init success - */ - CastPlayer.prototype.onInitSuccess = function () { - this.isInitialized = true; - console.log("chromecast init success"); - }; - - /** - * Generic error callback function - */ - CastPlayer.prototype.onError = function () { - console.log("chromecast error"); - }; - - /** - * @param {!Object} e A new session - * This handles auto-join when a page is reloaded - * When active session is detected, playback will automatically - * join existing session and occur in Cast mode and media - * status gets synced up with current media of the session - */ - CastPlayer.prototype.sessionListener = function (e) { - - this.session = e; - if (this.session) { - - console.log('sessionListener ' + JSON.stringify(e)); - - if (this.session.media[0]) { - this.onMediaDiscovered('activeSession', this.session.media[0]); - } - - this.onSessionConnected(e); - } - }; - - CastPlayer.prototype.messageListener = function (namespace, message) { - - if (typeof (message) === 'string') { - message = JSON.parse(message); - } - - if (message.type == 'playbackerror') { - - var errorCode = message.data; - - setTimeout(function () { - Dashboard.alert({ - message: Globalize.translate('MessagePlaybackError' + errorCode), - title: Globalize.translate('HeaderPlaybackError') - }); - }, 300); - - } - else if (message.type == 'connectionerror') { - - setTimeout(function () { - Dashboard.alert({ - message: Globalize.translate('MessageChromecastConnectionError'), - title: Globalize.translate('HeaderError') - }); - }, 300); - - } - else if (message.type) { - Events.trigger(this, message.type, [message.data]); - } - }; - - /** - * @param {string} e Receiver availability - * This indicates availability of receivers but - * does not provide a list of device IDs - */ - CastPlayer.prototype.receiverListener = function (e) { - - if (e === 'available') { - console.log("chromecast receiver found"); - this.hasReceivers = true; - } - else { - console.log("chromecast receiver list empty"); - this.hasReceivers = false; - } - }; - - /** - * session update listener - */ - CastPlayer.prototype.sessionUpdateListener = function (isAlive) { - - console.log('sessionUpdateListener alive: ' + isAlive); - - if (isAlive) { - } - else { - this.session = null; - this.deviceState = DEVICE_STATE.IDLE; - this.castPlayerState = PLAYER_STATE.IDLE; - - console.log('sessionUpdateListener: setting currentMediaSession to null'); - this.currentMediaSession = null; - - sendConnectionResult(false); - } - }; - - /** - * Requests that a receiver application session be created or joined. By default, the SessionRequest - * passed to the API at initialization time is used; this may be overridden by passing a different - * session request in opt_sessionRequest. - */ - CastPlayer.prototype.launchApp = function () { - console.log("chromecast launching app..."); - chrome.cast.requestSession(this.onRequestSessionSuccess.bind(this), this.onLaunchError.bind(this)); - }; - - /** - * Callback function for request session success - * @param {Object} e A chrome.cast.Session object - */ - CastPlayer.prototype.onRequestSessionSuccess = function (e) { - - console.log("chromecast session success: " + e.sessionId); - this.onSessionConnected(e); - }; - - CastPlayer.prototype.onSessionConnected = function (session) { - - this.session = session; - - this.deviceState = DEVICE_STATE.ACTIVE; - - this.session.addMessageListener(messageNamespace, this.messageListener.bind(this)); - this.session.addMediaListener(this.sessionMediaListener.bind(this)); - this.session.addUpdateListener(this.sessionUpdateListener.bind(this)); - - Events.trigger(this, 'connect'); - - this.sendMessage({ - options: {}, - command: 'Identify' - }); - }; - - /** - * session update listener - */ - CastPlayer.prototype.sessionMediaListener = function (e) { - - console.log('sessionMediaListener'); - this.currentMediaSession = e; - this.currentMediaSession.addUpdateListener(this.mediaStatusUpdateHandler); - }; - - /** - * Callback function for launch error - */ - CastPlayer.prototype.onLaunchError = function () { - console.log("chromecast launch error"); - this.deviceState = DEVICE_STATE.ERROR; - - sendConnectionResult(false); - }; - - /** - * Stops the running receiver application associated with the session. - */ - CastPlayer.prototype.stopApp = function () { - - if (this.session) { - this.session.stop(this.onStopAppSuccess.bind(this, 'Session stopped'), - this.errorHandler); - } - - }; - - /** - * Callback function for stop app success - */ - CastPlayer.prototype.onStopAppSuccess = function (message) { - console.log(message); - this.deviceState = DEVICE_STATE.IDLE; - this.castPlayerState = PLAYER_STATE.IDLE; - - console.log('onStopAppSuccess: setting currentMediaSession to null'); - this.currentMediaSession = null; - }; - - /** - * Loads media into a running receiver application - * @param {Number} mediaIndex An index number to indicate current media content - */ - CastPlayer.prototype.loadMedia = function (options, command) { - - if (!this.session) { - console.log("no session"); - return Promise.reject(); - } - - // Convert the items to smaller stubs to send the minimal amount of information - options.items = options.items.map(function (i) { - - return { - Id: i.Id, - Name: i.Name, - Type: i.Type, - MediaType: i.MediaType, - IsFolder: i.IsFolder - }; - }); - - return this.sendMessage({ - options: options, - command: command - }); - }; - - CastPlayer.prototype.sendMessage = function (message) { - - var player = this; - - var receiverName = null; - - if (castPlayer.session && castPlayer.session.receiver && castPlayer.session.receiver.friendlyName) { - receiverName = castPlayer.session.receiver.friendlyName; - } - - message = Object.assign(message, { - userId: Dashboard.getCurrentUserId(), - deviceId: ApiClient.deviceId(), - accessToken: ApiClient.accessToken(), - serverAddress: ApiClient.serverAddress(), - receiverName: receiverName - }); - - var bitrateSetting = appSettings.maxChromecastBitrate(); - if (bitrateSetting) { - message.maxBitrate = bitrateSetting; - } - - return new Promise(function (resolve, reject) { - - require(['chromecasthelpers'], function (chromecasthelpers) { - - chromecasthelpers.getServerAddress(ApiClient).then(function (serverAddress) { - message.serverAddress = serverAddress; - player.sendMessageInternal(message).then(resolve, reject); - - }, reject); - }); - }); - }; - - CastPlayer.prototype.sendMessageInternal = function (message) { - - message = JSON.stringify(message); - //console.log(message); - - this.session.sendMessage(messageNamespace, message, this.onPlayCommandSuccess.bind(this), this.errorHandler); - return Promise.resolve(); - }; - - CastPlayer.prototype.onPlayCommandSuccess = function () { - console.log('Message was sent to receiver ok.'); - }; - - /** - * Callback function for loadMedia success - * @param {Object} mediaSession A new media object. - */ - CastPlayer.prototype.onMediaDiscovered = function (how, mediaSession) { - - console.log("chromecast new media session ID:" + mediaSession.mediaSessionId + ' (' + how + ')'); - this.currentMediaSession = mediaSession; - - if (how == 'loadMedia') { - this.castPlayerState = PLAYER_STATE.PLAYING; - } - - if (how == 'activeSession') { - this.castPlayerState = mediaSession.playerState; - } - - this.currentMediaSession.addUpdateListener(this.mediaStatusUpdateHandler); - }; - - /** - * Callback function for media status update from receiver - * @param {!Boolean} e true/false - */ - CastPlayer.prototype.onMediaStatusUpdate = function (e) { - - if (e == false) { - this.castPlayerState = PLAYER_STATE.IDLE; - } - console.log("chromecast updating media: " + e); - }; - - /** - * Set media volume in Cast mode - * @param {Boolean} mute A boolean - */ - CastPlayer.prototype.setReceiverVolume = function (mute, vol) { - - if (!this.currentMediaSession) { - console.log('this.currentMediaSession is null'); - return; - } - - if (!mute) { - - this.session.setReceiverVolumeLevel((vol || 1), - this.mediaCommandSuccessCallback.bind(this), - this.errorHandler); - } - else { - this.session.setReceiverMuted(true, - this.mediaCommandSuccessCallback.bind(this), - this.errorHandler); - } - }; - - /** - * Mute CC - */ - CastPlayer.prototype.mute = function () { - this.setReceiverVolume(true); - }; - - /** - * Callback function for media command success - */ - CastPlayer.prototype.mediaCommandSuccessCallback = function (info, e) { - console.log(info); - }; - - // Create Cast Player - var castPlayer; - - function chromecastPlayer() { - - var self = this; - - // MediaController needs this - self.name = PlayerName; - - self.getItemsForPlayback = function (query) { - - var userId = Dashboard.getCurrentUserId(); - - if (query.Ids && query.Ids.split(',').length == 1) { - return ApiClient.getItem(userId, query.Ids.split(',')).then(function (item) { - return { - Items: [item], - TotalRecordCount: 1 - }; - }); - } - else { - - query.Limit = query.Limit || 100; - query.ExcludeLocationTypes = "Virtual"; - - return ApiClient.getItems(userId, query); - } - }; - - Events.on(castPlayer, "connect", function (e) { - - 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. - self.lastPlayerData = {}; - }); - - Events.on(castPlayer, "playbackstart", function (e, data) { - - console.log('cc: playbackstart'); - - castPlayer.initializeCastPlayer(); - - var state = self.getPlayerStateInternal(data); - Events.trigger(self, "playbackstart", [state]); - }); - - Events.on(castPlayer, "playbackstop", function (e, data) { - - console.log('cc: playbackstop'); - var state = self.getPlayerStateInternal(data); - - Events.trigger(self, "playbackstop", [state]); - - // Reset this so the next query doesn't make it appear like content is playing. - self.lastPlayerData = {}; - }); - - Events.on(castPlayer, "playbackprogress", function (e, data) { - - console.log('cc: positionchange'); - var state = self.getPlayerStateInternal(data); - - Events.trigger(self, "positionchange", [state]); - }); - - Events.on(castPlayer, "volumechange", function (e, data) { - - console.log('cc: volumechange'); - var state = self.getPlayerStateInternal(data); - - Events.trigger(self, "volumechange", [state]); - }); - - Events.on(castPlayer, "playstatechange", function (e, data) { - - console.log('cc: playstatechange'); - var state = self.getPlayerStateInternal(data); - - Events.trigger(self, "playstatechange", [state]); - }); - - self.play = function (options) { - - return Dashboard.getCurrentUser().then(function (user) { - - if (options.items) { - - return self.playWithCommand(options, 'PlayNow'); - - } else { - - return self.getItemsForPlayback({ - - Ids: options.ids.join(',') - - }).then(function (result) { - - options.items = result.Items; - return self.playWithCommand(options, 'PlayNow'); - - }); - } - - }); - - }; - - self.playWithCommand = function (options, command) { - - if (!options.items) { - return ApiClient.getItem(Dashboard.getCurrentUserId(), options.ids[0]).then(function (item) { - - options.items = [item]; - return self.playWithCommand(options, command); - }); - } - - return castPlayer.loadMedia(options, command); - }; - - self.unpause = function () { - castPlayer.sendMessage({ - options: {}, - command: 'Unpause' - }); - }; - - self.pause = function () { - castPlayer.sendMessage({ - options: {}, - command: 'Pause' - }); - }; - - self.shuffle = function (id) { - - var userId = Dashboard.getCurrentUserId(); - - ApiClient.getItem(userId, id).then(function (item) { - - self.playWithCommand({ - - items: [item] - - }, 'Shuffle'); - - }); - - }; - - self.instantMix = function (id) { - - var userId = Dashboard.getCurrentUserId(); - - ApiClient.getItem(userId, id).then(function (item) { - - self.playWithCommand({ - - items: [item] - - }, 'InstantMix'); - - }); - - }; - - self.canQueueMediaType = function (mediaType) { - return mediaType == "Audio"; - }; - - self.queue = function (options) { - self.playWithCommand(options, 'PlayLast'); - }; - - self.queueNext = function (options) { - self.playWithCommand(options, 'PlayNext'); - }; - - self.stop = function () { - castPlayer.sendMessage({ - options: {}, - command: 'Stop' - }); - }; - - self.displayContent = function (options) { - - castPlayer.sendMessage({ - options: options, - command: 'DisplayContent' - }); - }; - - self.mute = function () { - castPlayer.sendMessage({ - options: {}, - command: 'Mute' - }); - //castPlayer.mute(); - }; - - self.unMute = function () { - self.setVolume(getCurrentVolume() + 2); - }; - - self.setRepeatMode = function (mode) { - castPlayer.sendMessage({ - options: { - RepeatMode: mode - }, - command: 'SetRepeatMode' - }); - }; - - self.toggleMute = function () { - - var state = self.lastPlayerData || {}; - state = state.PlayState || {}; - - if (state.IsMuted) { - self.unMute(); - } else { - self.mute(); - } - }; - - self.getTargets = function () { - - var targets = []; - - if (castPlayer.hasReceivers) { - targets.push(self.getCurrentTargetInfo()); - } - - return Promise.resolve(targets); - }; - - self.getCurrentTargetInfo = function () { - - var appName = null; - - if (castPlayer.session && castPlayer.session.receiver && castPlayer.session.receiver.friendlyName) { - appName = castPlayer.session.receiver.friendlyName; - } - - return { - name: PlayerName, - id: PlayerName, - playerName: PlayerName, - playableMediaTypes: ["Audio", "Video"], - isLocalPlayer: false, - appName: PlayerName, - deviceName: appName, - supportedCommands: ["VolumeUp", - "VolumeDown", - "Mute", - "Unmute", - "ToggleMute", - "SetVolume", - "SetAudioStreamIndex", - "SetSubtitleStreamIndex", - "DisplayContent", - "SetRepeatMode", - "EndSession"] - }; - }; - - self.seek = function (position) { - - position = parseInt(position); - - position = position / 10000000; - - castPlayer.sendMessage({ - options: { - position: position - }, - command: 'Seek' - }); - }; - - self.setAudioStreamIndex = function (index) { - castPlayer.sendMessage({ - options: { - index: index - }, - command: 'SetAudioStreamIndex' - }); - }; - - self.setSubtitleStreamIndex = function (index) { - castPlayer.sendMessage({ - options: { - index: index - }, - command: 'SetSubtitleStreamIndex' - }); - }; - - self.nextTrack = function () { - castPlayer.sendMessage({ - options: {}, - command: 'NextTrack' - }); - }; - - self.previousTrack = function () { - castPlayer.sendMessage({ - options: {}, - command: 'PreviousTrack' - }); - }; - - self.beginPlayerUpdates = function () { - // Setup polling here - }; - - self.endPlayerUpdates = function () { - // Stop polling here - }; - - function getCurrentVolume() { - var state = self.lastPlayerData || {}; - state = state.PlayState || {}; - - return state.VolumeLevel == null ? 100 : state.VolumeLevel; - } - - self.volumeDown = function () { - - castPlayer.sendMessage({ - options: {}, - command: 'VolumeDown' - }); - }; - - self.endSession = function () { - - self.stop(); - setTimeout(function () { - castPlayer.stopApp(); - }, 1000); - }; - - self.volumeUp = function () { - - castPlayer.sendMessage({ - options: {}, - command: 'VolumeUp' - }); - }; - - self.setVolume = function (vol) { - - vol = Math.min(vol, 100); - vol = Math.max(vol, 0); - - //castPlayer.setReceiverVolume(false, (vol / 100)); - castPlayer.sendMessage({ - options: { - volume: vol - }, - command: 'SetVolume' - }); - }; - - self.getPlayerState = function () { - - var result = self.getPlayerStateInternal(); - return Promise.resolve(result); - }; - - self.lastPlayerData = {}; - - self.getPlayerStateInternal = function (data) { - - data = data || self.lastPlayerData; - self.lastPlayerData = data; - - console.log(JSON.stringify(data)); - return data; - }; - - self.tryPair = function (target) { - - if (castPlayer.deviceState != DEVICE_STATE.ACTIVE && castPlayer.isInitialized) { - - return new Promise(function (resolve, reject) { - currentResolve = resolve; - currentReject = reject; - - castPlayer.launchApp(); - }); - } else { - - currentResolve = null; - currentReject = null; - - return Promise.reject(); - } - }; - } - - function initializeChromecast() { - - castPlayer = new CastPlayer(); - - var registeredPlayer = new chromecastPlayer(); - window.CCastPlayer = registeredPlayer; - MediaController.registerPlayer(registeredPlayer); - - // To allow the native android app to override - document.dispatchEvent(new CustomEvent("chromecastloaded", { - detail: { - player: registeredPlayer - } - })); - } - - var fileref = document.createElement('script'); - fileref.setAttribute("type", "text/javascript"); - fileref.onload = initializeChromecast; - fileref.setAttribute("src", "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"); - document.querySelector('head').appendChild(fileref); - +define(['appSettings', 'playbackManager', 'connectionManager', 'globalize', 'events'], function (appSettings, playbackManager, connectionManager, globalize, events) { + 'use strict'; + + // 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 { + playbackManager.removeActivePlayer(PlayerName); + } + } + } + + /** + * Constants of states for Chromecast device + **/ + var DEVICE_STATE = { + 'IDLE': 0, + 'ACTIVE': 1, + 'WARNING': 2, + 'ERROR': 3 + }; + + /** + * Constants of states for CastPlayer + **/ + var PLAYER_STATE = { + 'IDLE': 'IDLE', + 'LOADING': 'LOADING', + 'LOADED': 'LOADED', + 'PLAYING': 'PLAYING', + 'PAUSED': 'PAUSED', + 'STOPPED': 'STOPPED', + 'SEEKING': 'SEEKING', + 'ERROR': 'ERROR' + }; + + var applicationID = "2D4B1DA3"; + + // This is the beta version used for testing new changes + + //applicationID = '27C4EB5B'; + + var messageNamespace = 'urn:x-cast:com.connectsdk'; + + var CastPlayer = function () { + + /* device variables */ + // @type {DEVICE_STATE} A state for device + this.deviceState = DEVICE_STATE.IDLE; + + /* Cast player variables */ + // @type {Object} a chrome.cast.media.Media object + this.currentMediaSession = null; + + // @type {string} a chrome.cast.Session object + this.session = null; + // @type {PLAYER_STATE} A state for Cast media player + this.castPlayerState = PLAYER_STATE.IDLE; + + this.hasReceivers = false; + + // bind once - commit 2ebffc2271da0bc5e8b13821586aee2a2e3c7753 + this.errorHandler = this.onError.bind(this); + this.mediaStatusUpdateHandler = this.onMediaStatusUpdate.bind(this); + + this.initializeCastPlayer(); + }; + + /** + * Initialize Cast media player + * Initializes the API. Note that either successCallback and errorCallback will be + * invoked once the API has finished initialization. The sessionListener and + * receiverListener may be invoked at any time afterwards, and possibly more than once. + */ + CastPlayer.prototype.initializeCastPlayer = function () { + + var chrome = window.chrome; + + if (!chrome) { + return; + } + + if (!chrome.cast || !chrome.cast.isAvailable) { + + setTimeout(this.initializeCastPlayer.bind(this), 1000); + return; + } + + // request session + var sessionRequest = new chrome.cast.SessionRequest(applicationID); + var apiConfig = new chrome.cast.ApiConfig(sessionRequest, + this.sessionListener.bind(this), + this.receiverListener.bind(this), + "origin_scoped"); + + console.log('chromecast.initialize'); + + chrome.cast.initialize(apiConfig, this.onInitSuccess.bind(this), this.errorHandler); + + }; + + /** + * Callback function for init success + */ + CastPlayer.prototype.onInitSuccess = function () { + this.isInitialized = true; + console.log("chromecast init success"); + }; + + /** + * Generic error callback function + */ + CastPlayer.prototype.onError = function () { + console.log("chromecast error"); + }; + + /** + * @param {!Object} e A new session + * This handles auto-join when a page is reloaded + * When active session is detected, playback will automatically + * join existing session and occur in Cast mode and media + * status gets synced up with current media of the session + */ + CastPlayer.prototype.sessionListener = function (e) { + + this.session = e; + if (this.session) { + + console.log('sessionListener ' + JSON.stringify(e)); + + if (this.session.media[0]) { + this.onMediaDiscovered('activeSession', this.session.media[0]); + } + + this.onSessionConnected(e); + } + }; + + function alertText(text, title) { + require(['alert'], function (alert) { + alert({ + text: text, + title: title + }); + }); + } + + CastPlayer.prototype.messageListener = function (namespace, message) { + + if (typeof (message) === 'string') { + message = JSON.parse(message); + } + + if (message.type === 'playbackerror') { + + var errorCode = message.data; + + setTimeout(function () { + alertText(globalize.translate('MessagePlaybackError' + errorCode), globalize.translate('HeaderPlaybackError')); + }, 300); + + } + else if (message.type === 'connectionerror') { + + setTimeout(function () { + alertText(globalize.translate('MessageChromecastConnectionError'), globalize.translate('HeaderError')); + }, 300); + + } + else if (message.type) { + events.trigger(this, message.type, [message.data]); + } + }; + + /** + * @param {string} e Receiver availability + * This indicates availability of receivers but + * does not provide a list of device IDs + */ + CastPlayer.prototype.receiverListener = function (e) { + + if (e === 'available') { + console.log("chromecast receiver found"); + this.hasReceivers = true; + } + else { + console.log("chromecast receiver list empty"); + this.hasReceivers = false; + } + }; + + /** + * session update listener + */ + CastPlayer.prototype.sessionUpdateListener = function (isAlive) { + + console.log('sessionUpdateListener alive: ' + isAlive); + + if (isAlive) { + } + else { + this.session = null; + this.deviceState = DEVICE_STATE.IDLE; + this.castPlayerState = PLAYER_STATE.IDLE; + + console.log('sessionUpdateListener: setting currentMediaSession to null'); + this.currentMediaSession = null; + + sendConnectionResult(false); + } + }; + + /** + * Requests that a receiver application session be created or joined. By default, the SessionRequest + * passed to the API at initialization time is used; this may be overridden by passing a different + * session request in opt_sessionRequest. + */ + CastPlayer.prototype.launchApp = function () { + console.log("chromecast launching app..."); + chrome.cast.requestSession(this.onRequestSessionSuccess.bind(this), this.onLaunchError.bind(this)); + }; + + /** + * Callback function for request session success + * @param {Object} e A chrome.cast.Session object + */ + CastPlayer.prototype.onRequestSessionSuccess = function (e) { + + console.log("chromecast session success: " + e.sessionId); + this.onSessionConnected(e); + }; + + CastPlayer.prototype.onSessionConnected = function (session) { + + this.session = session; + + this.deviceState = DEVICE_STATE.ACTIVE; + + this.session.addMessageListener(messageNamespace, this.messageListener.bind(this)); + this.session.addMediaListener(this.sessionMediaListener.bind(this)); + this.session.addUpdateListener(this.sessionUpdateListener.bind(this)); + + events.trigger(this, 'connect'); + + this.sendMessage({ + options: {}, + command: 'Identify' + }); + }; + + /** + * session update listener + */ + CastPlayer.prototype.sessionMediaListener = function (e) { + + console.log('sessionMediaListener'); + this.currentMediaSession = e; + this.currentMediaSession.addUpdateListener(this.mediaStatusUpdateHandler); + }; + + /** + * Callback function for launch error + */ + CastPlayer.prototype.onLaunchError = function () { + console.log("chromecast launch error"); + this.deviceState = DEVICE_STATE.ERROR; + + sendConnectionResult(false); + }; + + /** + * Stops the running receiver application associated with the session. + */ + CastPlayer.prototype.stopApp = function () { + + if (this.session) { + this.session.stop(this.onStopAppSuccess.bind(this, 'Session stopped'), + this.errorHandler); + } + + }; + + /** + * Callback function for stop app success + */ + CastPlayer.prototype.onStopAppSuccess = function (message) { + console.log(message); + this.deviceState = DEVICE_STATE.IDLE; + this.castPlayerState = PLAYER_STATE.IDLE; + + console.log('onStopAppSuccess: setting currentMediaSession to null'); + this.currentMediaSession = null; + }; + + /** + * Loads media into a running receiver application + * @param {Number} mediaIndex An index number to indicate current media content + */ + CastPlayer.prototype.loadMedia = function (options, command) { + + if (!this.session) { + console.log("no session"); + return Promise.reject(); + } + + // Convert the items to smaller stubs to send the minimal amount of information + options.items = options.items.map(function (i) { + + return { + Id: i.Id, + Name: i.Name, + Type: i.Type, + MediaType: i.MediaType, + IsFolder: i.IsFolder + }; + }); + + return this.sendMessage({ + options: options, + command: command + }); + }; + + CastPlayer.prototype.sendMessage = function (message) { + + var player = this; + + var receiverName = null; + + var session = player.session; + + if (session && session.receiver && session.receiver.friendlyName) { + receiverName = session.receiver.friendlyName; + } + + message = Object.assign(message, { + userId: ApiClient.getCurrentUserId(), + deviceId: ApiClient.deviceId(), + accessToken: ApiClient.accessToken(), + serverAddress: ApiClient.serverAddress(), + receiverName: receiverName + }); + + var bitrateSetting = appSettings.maxChromecastBitrate(); + if (bitrateSetting) { + message.maxBitrate = bitrateSetting; + } + + return new Promise(function (resolve, reject) { + + require(['chromecasthelpers'], function (chromecasthelpers) { + + chromecasthelpers.getServerAddress(ApiClient).then(function (serverAddress) { + message.serverAddress = serverAddress; + player.sendMessageInternal(message).then(resolve, reject); + + }, reject); + }); + }); + }; + + CastPlayer.prototype.sendMessageInternal = function (message) { + + message = JSON.stringify(message); + //console.log(message); + + this.session.sendMessage(messageNamespace, message, this.onPlayCommandSuccess.bind(this), this.errorHandler); + return Promise.resolve(); + }; + + CastPlayer.prototype.onPlayCommandSuccess = function () { + console.log('Message was sent to receiver ok.'); + }; + + /** + * Callback function for loadMedia success + * @param {Object} mediaSession A new media object. + */ + CastPlayer.prototype.onMediaDiscovered = function (how, mediaSession) { + + console.log("chromecast new media session ID:" + mediaSession.mediaSessionId + ' (' + how + ')'); + this.currentMediaSession = mediaSession; + + if (how === 'loadMedia') { + this.castPlayerState = PLAYER_STATE.PLAYING; + } + + if (how === 'activeSession') { + this.castPlayerState = mediaSession.playerState; + } + + this.currentMediaSession.addUpdateListener(this.mediaStatusUpdateHandler); + }; + + /** + * Callback function for media status update from receiver + * @param {!Boolean} e true/false + */ + CastPlayer.prototype.onMediaStatusUpdate = function (e) { + + if (e === false) { + this.castPlayerState = PLAYER_STATE.IDLE; + } + console.log("chromecast updating media: " + e); + }; + + /** + * Set media volume in Cast mode + * @param {Boolean} mute A boolean + */ + CastPlayer.prototype.setReceiverVolume = function (mute, vol) { + + if (!this.currentMediaSession) { + console.log('this.currentMediaSession is null'); + return; + } + + if (!mute) { + + this.session.setReceiverVolumeLevel((vol || 1), + this.mediaCommandSuccessCallback.bind(this), + this.errorHandler); + } + else { + this.session.setReceiverMuted(true, + this.mediaCommandSuccessCallback.bind(this), + this.errorHandler); + } + }; + + /** + * Mute CC + */ + CastPlayer.prototype.mute = function () { + this.setReceiverVolume(true); + }; + + /** + * Callback function for media command success + */ + CastPlayer.prototype.mediaCommandSuccessCallback = function (info, e) { + console.log(info); + }; + + function chromecastPlayer() { + + var self = this; + // Create Cast Player + var castPlayer; + + // playbackManager needs this + self.name = PlayerName; + self.type = 'mediaplayer'; + self.id = 'chromecast'; + self.isLocalPlayer = false; + + self.getItemsForPlayback = function (query) { + + var userId = ApiClient.getCurrentUserId(); + + if (query.Ids && query.Ids.split(',').length === 1) { + return ApiClient.getItem(userId, query.Ids.split(',')).then(function (item) { + return { + Items: [item], + TotalRecordCount: 1 + }; + }); + } + else { + + query.Limit = query.Limit || 100; + query.ExcludeLocationTypes = "Virtual"; + + return ApiClient.getItems(userId, query); + } + }; + + function initializeChromecast() { + + fileref.loaded = true; + castPlayer = new CastPlayer(); + + // To allow the native android app to override + document.dispatchEvent(new CustomEvent("chromecastloaded", { + detail: { + player: self + } + })); + + events.on(castPlayer, "connect", function (e) { + + if (currentResolve) { + sendConnectionResult(true); + } else { + playbackManager.setActivePlayer(PlayerName, self.getCurrentTargetInfo()); + } + + console.log('cc: connect'); + // Reset this so that statechange will fire + self.lastPlayerData = null; + }); + + events.on(castPlayer, "playbackstart", function (e, data) { + + console.log('cc: playbackstart'); + + castPlayer.initializeCastPlayer(); + + var state = self.getPlayerStateInternal(data); + events.trigger(self, "playbackstart", [state]); + }); + + events.on(castPlayer, "playbackstop", function (e, data) { + + console.log('cc: playbackstop'); + var state = self.getPlayerStateInternal(data); + + events.trigger(self, "playbackstop", [state]); + + // Reset this so the next query doesn't make it appear like content is playing. + self.lastPlayerData = {}; + }); + + events.on(castPlayer, "playbackprogress", function (e, data) { + + console.log('cc: positionchange'); + var state = self.getPlayerStateInternal(data); + + events.trigger(self, "timeupdate", [state]); + }); + + events.on(castPlayer, "volumechange", function (e, data) { + + console.log('cc: volumechange'); + var state = self.getPlayerStateInternal(data); + events.trigger(self, "volumechange", [state]); + }); + + events.on(castPlayer, "repeatmodechange", function (e, data) { + + console.log('cc: repeatmodechange'); + var state = self.getPlayerStateInternal(data); + events.trigger(self, "repeatmodechange", [state]); + }); + + events.on(castPlayer, "playstatechange", function (e, data) { + + console.log('cc: playstatechange'); + var state = self.getPlayerStateInternal(data); + + events.trigger(self, "pause", [state]); + }); + } + + self.play = function (options) { + + return ApiClient.getCurrentUser().then(function (user) { + + if (options.items) { + + return self.playWithCommand(options, 'PlayNow'); + + } else { + + return self.getItemsForPlayback({ + + Ids: options.ids.join(',') + + }).then(function (result) { + + options.items = result.Items; + return self.playWithCommand(options, 'PlayNow'); + + }); + } + + }); + + }; + + self.playWithCommand = function (options, command) { + + if (!options.items) { + var apiClient = connectionManager.getApiClient(options.serverId); + return apiClient.getItem(apiClient.getCurrentUserId(), options.ids[0]).then(function (item) { + + options.items = [item]; + return self.playWithCommand(options, command); + }); + } + + return castPlayer.loadMedia(options, command); + }; + + self.unpause = function () { + castPlayer.sendMessage({ + options: {}, + command: 'Unpause' + }); + }; + + self.playPause = function () { + castPlayer.sendMessage({ + options: {}, + command: 'PlayPause' + }); + }; + + self.pause = function () { + castPlayer.sendMessage({ + options: {}, + command: 'Pause' + }); + }; + + self.shuffle = function (item) { + + var apiClient = connectionManager.getApiClient(item.ServerId); + var userId = apiClient.getCurrentUserId(); + + apiClient.getItem(userId, item.Id).then(function (item) { + + self.playWithCommand({ + + items: [item] + + }, 'Shuffle'); + + }); + + }; + + self.instantMix = function (item) { + + var apiClient = connectionManager.getApiClient(item.ServerId); + var userId = apiClient.getCurrentUserId(); + + apiClient.getItem(userId, item.Id).then(function (item) { + + self.playWithCommand({ + + items: [item] + + }, 'InstantMix'); + + }); + + }; + + self.canPlayMediaType = function (mediaType) { + + mediaType = (mediaType || '').toLowerCase(); + return mediaType === 'audio' || mediaType === 'video'; + }; + + self.canQueueMediaType = function (mediaType) { + return self.canPlayMediaType(mediaType); + }; + + self.queue = function (options) { + self.playWithCommand(options, 'PlayLast'); + }; + + self.queueNext = function (options) { + self.playWithCommand(options, 'PlayNext'); + }; + + self.stop = function () { + return castPlayer.sendMessage({ + options: {}, + command: 'Stop' + }); + }; + + self.displayContent = function (options) { + + castPlayer.sendMessage({ + options: options, + command: 'DisplayContent' + }); + }; + + self.isPlaying = function () { + var state = self.lastPlayerData || {}; + return state.NowPlayingItem != null; + }; + + self.isPlayingVideo = function () { + var state = self.lastPlayerData || {}; + state = state.NowPlayingItem || {}; + return state.MediaType === 'Video'; + }; + + self.isPlayingAudio = function () { + var state = self.lastPlayerData || {}; + state = state.NowPlayingItem || {}; + return state.MediaType === 'Audio'; + }; + + self.currentTime = function (val) { + + if (val != null) { + return self.seek(val); + } + + var state = self.lastPlayerData || {}; + state = state.PlayState || {}; + return state.PositionTicks; + }; + + self.duration = function () { + var state = self.lastPlayerData || {}; + state = state.NowPlayingItem || {}; + return state.RunTimeTicks; + }; + + self.paused = function () { + var state = self.lastPlayerData || {}; + state = state.PlayState || {}; + + return state.IsPaused; + }; + + self.isMuted = function () { + var state = self.lastPlayerData || {}; + state = state.PlayState || {}; + + return state.IsMuted; + }; + + self.setMute = function (isMuted) { + + if (isMuted) { + castPlayer.sendMessage({ + options: {}, + command: 'Mute' + }); + } else { + castPlayer.sendMessage({ + options: {}, + command: 'Unmute' + }); + } + }; + + self.getRepeatMode = function () { + var state = self.lastPlayerData || {}; + state = state.PlayState || {}; + return state.RepeatMode; + }; + + self.setRepeatMode = function (mode) { + castPlayer.sendMessage({ + options: { + RepeatMode: mode + }, + command: 'SetRepeatMode' + }); + }; + + self.toggleMute = function () { + + castPlayer.sendMessage({ + options: {}, + command: 'ToggleMute' + }); + }; + + self.getTargets = function () { + + var targets = []; + + if (castPlayer.hasReceivers) { + targets.push(self.getCurrentTargetInfo()); + } + + return Promise.resolve(targets); + }; + + self.getCurrentTargetInfo = function () { + + var appName = null; + + if (castPlayer.session && castPlayer.session.receiver && castPlayer.session.receiver.friendlyName) { + appName = castPlayer.session.receiver.friendlyName; + } + + return { + name: PlayerName, + id: PlayerName, + playerName: PlayerName, + playableMediaTypes: ["Audio", "Video"], + isLocalPlayer: false, + appName: PlayerName, + deviceName: appName, + supportedCommands: ["VolumeUp", + "VolumeDown", + "Mute", + "Unmute", + "ToggleMute", + "SetVolume", + "SetAudioStreamIndex", + "SetSubtitleStreamIndex", + "DisplayContent", + "SetRepeatMode", + "EndSession"] + }; + }; + + self.seek = function (position) { + + position = parseInt(position); + + position = position / 10000000; + + castPlayer.sendMessage({ + options: { + position: position + }, + command: 'Seek' + }); + }; + + self.audioTracks = function () { + var state = self.lastPlayerData || {}; + state = state.NowPlayingItem || {}; + var streams = state.MediaStreams || []; + return streams.filter(function (s) { + return s.Type === 'Audio'; + }); + }; + + self.setAudioStreamIndex = function (index) { + castPlayer.sendMessage({ + options: { + index: index + }, + command: 'SetAudioStreamIndex' + }); + }; + + self.getAudioStreamIndex = function () { + var state = self.lastPlayerData || {}; + state = state.PlayState || {}; + return state.AudioStreamIndex; + }; + + self.subtitleTracks = function () { + var state = self.lastPlayerData || {}; + state = state.NowPlayingItem || {}; + var streams = state.MediaStreams || []; + return streams.filter(function (s) { + return s.Type === 'Subtitle'; + }); + }; + + self.getSubtitleStreamIndex = function () { + var state = self.lastPlayerData || {}; + state = state.PlayState || {}; + return state.SubtitleStreamIndex; + }; + + self.setSubtitleStreamIndex = function (index) { + castPlayer.sendMessage({ + options: { + index: index + }, + command: 'SetSubtitleStreamIndex' + }); + }; + + self.getMaxStreamingBitrate = function () { + var state = self.lastPlayerData || {}; + state = state.PlayState || {}; + return state.MaxStreamingBitrate; + }; + + self.setMaxStreamingBitrate = function (bitrate) { + castPlayer.sendMessage({ + options: { + bitrate: bitrate + }, + command: 'SetMaxStreamingBitrate' + }); + }; + + self.isFullscreen = function () { + var state = self.lastPlayerData || {}; + state = state.PlayState || {}; + return state.IsFullscreen; + }; + + self.toggleFullscreen = function () { + // not supported + }; + + self.nextTrack = function () { + castPlayer.sendMessage({ + options: {}, + command: 'NextTrack' + }); + }; + + self.previousTrack = function () { + castPlayer.sendMessage({ + options: {}, + command: 'PreviousTrack' + }); + }; + + self.beginPlayerUpdates = function () { + // Setup polling here + }; + + self.endPlayerUpdates = function () { + // Stop polling here + }; + + self.getVolume = function () { + + var state = self.lastPlayerData || {}; + state = state.PlayState || {}; + + return state.VolumeLevel == null ? 100 : state.VolumeLevel; + }; + + self.volumeDown = function () { + + castPlayer.sendMessage({ + options: {}, + command: 'VolumeDown' + }); + }; + + self.endSession = function () { + + self.stop().then(function () { + setTimeout(function () { + castPlayer.stopApp(); + }, 1000); + }); + }; + + self.volumeUp = function () { + + castPlayer.sendMessage({ + options: {}, + command: 'VolumeUp' + }); + }; + + self.setVolume = function (vol) { + + vol = Math.min(vol, 100); + vol = Math.max(vol, 0); + + //castPlayer.setReceiverVolume(false, (vol / 100)); + castPlayer.sendMessage({ + options: { + volume: vol + }, + command: 'SetVolume' + }); + }; + + self.getPlaylist = function () { + return Promise.resolve([]); + }; + + self.getCurrentPlaylistItemId = function () { + }; + + self.setCurrentPlaylistItem = function (playlistItemId) { + return Promise.resolve(); + }; + + self.removeFromPlaylist = function (playlistItemIds) { + return Promise.resolve(); + }; + + self.getPlayerState = function () { + + return Promise.resolve(self.getPlayerStateInternal() || {}); + }; + + function normalizeImages(state) { + + if (state && state.NowPlayingItem) { + + var item = state.NowPlayingItem; + + if (!item.ImageTags || !item.ImageTags.Primary) { + if (item.PrimaryImageTag) { + item.ImageTags = item.ImageTags || {}; + item.ImageTags.Primary = item.PrimaryImageTag; + } + } + if (item.BackdropImageTag && item.BackdropItemId === item.Id) { + item.BackdropImageTags = [item.BackdropImageTag]; + } + if (item.BackdropImageTag && item.BackdropItemId !== item.Id) { + item.ParentBackdropImageTags = [item.BackdropImageTag]; + item.ParentBackdropItemId = item.BackdropItemId; + } + } + } + + self.lastPlayerData = {}; + + self.getPlayerStateInternal = function (data) { + + var triggerStateChange = false; + if (data && !self.lastPlayerData) { + triggerStateChange = true; + } + + data = data || self.lastPlayerData; + self.lastPlayerData = data; + + normalizeImages(data); + + //console.log(JSON.stringify(data)); + + if (triggerStateChange) { + events.trigger(self, "statechange", [data]); + } + + return data; + }; + + self.tryPair = function (target) { + + if (castPlayer.deviceState !== DEVICE_STATE.ACTIVE && castPlayer.isInitialized) { + + return new Promise(function (resolve, reject) { + currentResolve = resolve; + currentReject = reject; + + castPlayer.launchApp(); + }); + } else { + + currentResolve = null; + currentReject = null; + + return Promise.reject(); + } + }; + + if (fileref.loaded) { + initializeChromecast(); + } else { + fileref.onload = initializeChromecast; + } + } + + var fileref = document.createElement('script'); + fileref.setAttribute("type", "text/javascript"); + fileref.onload = function () { + fileref.loaded = true; + }; + fileref.setAttribute("src", "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"); + document.querySelector('head').appendChild(fileref); + + return chromecastPlayer; }); \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-webcomponents/dialoghelper/dialoghelper.js b/dashboard-ui/bower_components/emby-webcomponents/dialoghelper/dialoghelper.js index 5a4d9c2cfe..96d0ce5319 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/dialoghelper/dialoghelper.js +++ b/dashboard-ui/bower_components/emby-webcomponents/dialoghelper/dialoghelper.js @@ -10,6 +10,10 @@ return false; } + if (browser.noAnimation) { + return false; + } + return browser.supportsCssAnimation(); } diff --git a/dashboard-ui/bower_components/emby-webcomponents/emby-button/emby-button.css b/dashboard-ui/bower_components/emby-webcomponents/emby-button/emby-button.css index 1627a363d3..913eaddda1 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/emby-button/emby-button.css +++ b/dashboard-ui/bower_components/emby-webcomponents/emby-button/emby-button.css @@ -54,8 +54,8 @@ .emby-button > i { /* For non-fab buttons that have icons */ font-size: 1.36em; - width: auto; - height: auto; + width: 1em; + height: 1em; } .fab { @@ -72,8 +72,8 @@ } .fab > i { - height: auto; - width: auto; + height: 1em; + width: 1em; vertical-align: middle; font-size: 2.85em; } @@ -88,8 +88,8 @@ } .fab.mini > i { - height: auto; - width: auto; + height: 1em; + width: 1em; font-size: 1.72em; } diff --git a/dashboard-ui/bower_components/emby-webcomponents/emby-checkbox/emby-checkbox.css b/dashboard-ui/bower_components/emby-webcomponents/emby-checkbox/emby-checkbox.css index 03a6ea757d..3d784a22b0 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/emby-checkbox/emby-checkbox.css +++ b/dashboard-ui/bower_components/emby-webcomponents/emby-checkbox/emby-checkbox.css @@ -127,3 +127,13 @@ opacity: .7; margin-bottom: 0; } + +@-webkit-keyframes repaintChrome { + from { + padding: 0; + } + + to { + padding: 0; + } +} \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-webcomponents/emby-checkbox/emby-checkbox.js b/dashboard-ui/bower_components/emby-webcomponents/emby-checkbox/emby-checkbox.js index 93eb7e78ea..c1ffe36205 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/emby-checkbox/emby-checkbox.js +++ b/dashboard-ui/bower_components/emby-webcomponents/emby-checkbox/emby-checkbox.js @@ -1,4 +1,4 @@ -define(['css!./emby-checkbox', 'registerElement'], function () { +define(['browser', 'dom', 'css!./emby-checkbox', 'registerElement'], function (browser, dom) { 'use strict'; var EmbyCheckboxPrototype = Object.create(HTMLInputElement.prototype); @@ -19,6 +19,22 @@ } } + var enableRefreshHack = browser.tizen || browser.orsay || browser.operaTv || browser.web0s ? true : false; + + function forceRefresh(loading) { + + var elem = this.parentNode; + + elem.style.webkitAnimationName = 'repaintChrome'; + elem.style.webkitAnimationDelay = (loading === true ? '500ms' : ''); + elem.style.webkitAnimationDuration = '10ms'; + elem.style.webkitAnimationIterationCount = '1'; + + setTimeout(function () { + elem.style.webkitAnimationName = ''; + }, (loading === true ? 520 : 20)); + } + EmbyCheckboxPrototype.attachedCallback = function () { if (this.getAttribute('data-embycheckbox') === 'true') { @@ -47,10 +63,27 @@ labelTextElement.classList.add('checkboxLabel'); this.addEventListener('keydown', onKeyDown); + + if (enableRefreshHack) { + + forceRefresh.call(this, true); + dom.addEventListener(this, 'click', forceRefresh, { + passive: true + }); + dom.addEventListener(this, 'blur', forceRefresh, { + passive: true + }); + dom.addEventListener(this, 'focus', forceRefresh, { + passive: true + }); + dom.addEventListener(this, 'change', forceRefresh, { + passive: true + }); + } }; document.registerElement('emby-checkbox', { prototype: EmbyCheckboxPrototype, extends: 'input' }); -}); \ No newline at end of file +}); diff --git a/dashboard-ui/bower_components/emby-webcomponents/emby-itemscontainer/emby-itemscontainer.js b/dashboard-ui/bower_components/emby-webcomponents/emby-itemscontainer/emby-itemscontainer.js index 313d93fb7f..2a52ea67ca 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/emby-itemscontainer/emby-itemscontainer.js +++ b/dashboard-ui/bower_components/emby-webcomponents/emby-itemscontainer/emby-itemscontainer.js @@ -107,20 +107,35 @@ function onDrop(evt, itemsContainer) { - - loading.show(); - var el = evt.item; var newIndex = evt.newIndex; var itemId = el.getAttribute('data-playlistitemid'); var playlistId = el.getAttribute('data-playlistid'); + if (!playlistId) { + + var oldIndex = evt.oldIndex; + + el.dispatchEvent(new CustomEvent('itemdrop', { + detail: { + oldIndex: oldIndex, + newIndex: newIndex, + playlistItemId: itemId + }, + bubbles: true, + cancelable: false + })); + return; + } + var serverId = el.getAttribute('data-serverid'); var apiClient = connectionManager.getApiClient(serverId); newIndex = Math.max(0, newIndex - 1); + loading.show(); + apiClient.ajax({ url: apiClient.getUrl('Playlists/' + playlistId + '/Items/' + itemId + '/Move/' + newIndex), @@ -129,7 +144,6 @@ }).then(function () { - el.setAttribute('data-index', newIndex); loading.hide(); }, function () { @@ -171,7 +185,7 @@ // dragging ended onEnd: function (/**Event*/evt) { - onDrop(evt, self); + return onDrop(evt, self); } }); }); @@ -237,6 +251,11 @@ } } + ItemsContainerProtoType.createdCallback = function () { + + this.classList.add('itemsContainer'); + }; + ItemsContainerProtoType.attachedCallback = function () { this.addEventListener('click', onClick); @@ -264,6 +283,10 @@ addNotificationEvent(this, 'SeriesTimerCreated', onSeriesTimerCreated); addNotificationEvent(this, 'TimerCancelled', onTimerCancelled); addNotificationEvent(this, 'SeriesTimerCancelled', onSeriesTimerCancelled); + + if (this.getAttribute('data-dragreorder') === 'true') { + this.enableDragReordering(true); + } }; ItemsContainerProtoType.detachedCallback = function () { diff --git a/dashboard-ui/bower_components/emby-webcomponents/emby-slider/emby-slider.css b/dashboard-ui/bower_components/emby-webcomponents/emby-slider/emby-slider.css index da14f584aa..c080aef980 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/emby-slider/emby-slider.css +++ b/dashboard-ui/bower_components/emby-webcomponents/emby-slider/emby-slider.css @@ -16,7 +16,7 @@ _:-ms-input-placeholder, :root .mdl-slider { -ms-user-select: none; user-select: none; outline: 0; - padding: 1.5em 0; + padding: 1em 0; color: #52B54B; -webkit-align-self: center; -ms-flex-item-align: center; @@ -220,9 +220,9 @@ _:-ms-input-placeholder, :root .mdl-slider { .sliderBubble { position: absolute; - top: -3.7em; + top: 0; left: 0; - padding: .5em 1em; + transform: translate3d(-48%, -120%, 0); background: #282828; color: #fff; display: flex; @@ -232,4 +232,5 @@ _:-ms-input-placeholder, :root .mdl-slider { .sliderBubbleText { margin: 0; + padding: .5em .75em; } diff --git a/dashboard-ui/bower_components/emby-webcomponents/emby-slider/emby-slider.js b/dashboard-ui/bower_components/emby-webcomponents/emby-slider/emby-slider.js index 831a62d6f4..7b0625646a 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/emby-slider/emby-slider.js +++ b/dashboard-ui/bower_components/emby-webcomponents/emby-slider/emby-slider.js @@ -38,21 +38,29 @@ function updateBubble(range, value, bubble, bubbleText) { - bubble.style.left = (value - 1) + '%'; + bubble.style.left = value + '%'; - if (range.getBubbleText) { - value = range.getBubbleText(value); + if (range.getBubbleHtml) { + value = range.getBubbleHtml(value); + } else { + if (range.getBubbleText) { + value = range.getBubbleText(value); + } else { + value = Math.round(value); + } + value = '

' + value + '

'; } - bubbleText.innerHTML = value; + + bubble.innerHTML = value; } EmbySliderPrototype.attachedCallback = function () { - if (this.getAttribute('data-embycheckbox') === 'true') { + if (this.getAttribute('data-embyslider') === 'true') { return; } - this.setAttribute('data-embycheckbox', 'true'); + this.setAttribute('data-embyslider', 'true'); this.classList.add('mdl-slider'); this.classList.add('mdl-js-slider'); @@ -70,21 +78,20 @@ htmlToInsert += '
'; } - htmlToInsert += '

'; + htmlToInsert += '
'; containerElement.insertAdjacentHTML('beforeend', htmlToInsert); var backgroundLower = containerElement.querySelector('.mdl-slider__background-lower'); var backgroundUpper = containerElement.querySelector('.mdl-slider__background-upper'); var sliderBubble = containerElement.querySelector('.sliderBubble'); - var sliderBubbleText = containerElement.querySelector('.sliderBubbleText'); var hasHideClass = sliderBubble.classList.contains('hide'); dom.addEventListener(this, 'input', function (e) { this.dragging = true; - updateBubble(this, this.value, sliderBubble, sliderBubbleText); + updateBubble(this, this.value, sliderBubble); if (hasHideClass) { sliderBubble.classList.remove('hide'); @@ -114,7 +121,7 @@ var clientX = e.clientX; var bubbleValue = (clientX - rect.left) / rect.width; bubbleValue *= 100; - updateBubble(this, Math.round(bubbleValue), sliderBubble, sliderBubbleText); + updateBubble(this, bubbleValue, sliderBubble); if (hasHideClass) { sliderBubble.classList.remove('hide'); diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/material-icons/2fcrYFNaTjcS6g4U3t-Y5ewrjPiaoEww8AihgqWRJAo.woff b/dashboard-ui/bower_components/emby-webcomponents/fonts/material-icons/2fcryfnatjcs6g4u3t-y5ewrjpiaoeww8aihgqwrjao.woff similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/material-icons/2fcrYFNaTjcS6g4U3t-Y5ewrjPiaoEww8AihgqWRJAo.woff rename to dashboard-ui/bower_components/emby-webcomponents/fonts/material-icons/2fcryfnatjcs6g4u3t-y5ewrjpiaoeww8aihgqwrjao.woff diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/material-icons/2fcrYFNaTjcS6g4U3t-Y5ZjZjT5FdEJ140U2DJYC3mY.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/material-icons/2fcryfnatjcs6g4u3t-y5zjzjt5fdej140u2djyc3my.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/material-icons/2fcrYFNaTjcS6g4U3t-Y5ZjZjT5FdEJ140U2DJYC3mY.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/material-icons/2fcryfnatjcs6g4u3t-y5zjzjt5fdej140u2djyc3my.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/material-icons/style.css b/dashboard-ui/bower_components/emby-webcomponents/fonts/material-icons/style.css index 2c84045d3d..13ecfdf416 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/fonts/material-icons/style.css +++ b/dashboard-ui/bower_components/emby-webcomponents/fonts/material-icons/style.css @@ -2,7 +2,7 @@ font-family: 'Material Icons'; font-style: normal; font-weight: 400; - src: local('Material Icons'), local('MaterialIcons-Regular'), url(2fcrYFNaTjcS6g4U3t-Y5ZjZjT5FdEJ140U2DJYC3mY.woff2) format('woff2'), url(2fcrYFNaTjcS6g4U3t-Y5ewrjPiaoEww8AihgqWRJAo.woff) format('woff'); + src: local('Material Icons'), local('MaterialIcons-Regular'), url(2fcryfnatjcs6g4u3t-y5zjzjt5fdej140u2djyc3my.woff2) format('woff2'), url(2fcryfnatjcs6g4u3t-y5ewrjpiaoeww8aihgqwrjao.woff) format('woff'); } .md-icon { diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/montserrat/IQHow_FEYlDC4Gzy_m8fcgFhaRv2pGgT5Kf0An0s4MM.woff b/dashboard-ui/bower_components/emby-webcomponents/fonts/montserrat/IQHow_FEYlDC4Gzy_m8fcgFhaRv2pGgT5Kf0An0s4MM.woff deleted file mode 100644 index 99543dc7ea..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/montserrat/IQHow_FEYlDC4Gzy_m8fcgFhaRv2pGgT5Kf0An0s4MM.woff and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/montserrat/IQHow_FEYlDC4Gzy_m8fcoWiMMZ7xLd792ULpGE4W_Y.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/montserrat/IQHow_FEYlDC4Gzy_m8fcoWiMMZ7xLd792ULpGE4W_Y.woff2 deleted file mode 100644 index dc78b576d0..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/montserrat/IQHow_FEYlDC4Gzy_m8fcoWiMMZ7xLd792ULpGE4W_Y.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/montserrat/style.css b/dashboard-ui/bower_components/emby-webcomponents/fonts/montserrat/style.css deleted file mode 100644 index 4f86e139c9..0000000000 --- a/dashboard-ui/bower_components/emby-webcomponents/fonts/montserrat/style.css +++ /dev/null @@ -1,17 +0,0 @@ -/* latin */ -@font-face { - font-family: 'Montserrat'; - font-style: normal; - font-weight: 400; - src: local('Montserrat-Regular'), url(zhcz-_WihjSQC0oHJ9TCYPk_vArhqVIZ0nv9q090hN8.woff2) format('woff2'), url(zhcz-_WihjSQC0oHJ9TCYBsxEYwM7FgeyaSgU71cLG0.woff) format('woff'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; -} - -/* latin */ -@font-face { - font-family: 'Montserrat'; - font-style: normal; - font-weight: 700; - src: local('Montserrat-Bold'), url(IQHow_FEYlDC4Gzy_m8fcoWiMMZ7xLd792ULpGE4W_Y.woff2) format('woff2'), url(IQHow_FEYlDC4Gzy_m8fcgFhaRv2pGgT5Kf0An0s4MM.woff) format('woff'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; -} diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/montserrat/zhcz-_WihjSQC0oHJ9TCYBsxEYwM7FgeyaSgU71cLG0.woff b/dashboard-ui/bower_components/emby-webcomponents/fonts/montserrat/zhcz-_WihjSQC0oHJ9TCYBsxEYwM7FgeyaSgU71cLG0.woff deleted file mode 100644 index 256e12d245..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/montserrat/zhcz-_WihjSQC0oHJ9TCYBsxEYwM7FgeyaSgU71cLG0.woff and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/montserrat/zhcz-_WihjSQC0oHJ9TCYPk_vArhqVIZ0nv9q090hN8.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/montserrat/zhcz-_WihjSQC0oHJ9TCYPk_vArhqVIZ0nv9q090hN8.woff2 deleted file mode 100644 index 560f18bb0f..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/montserrat/zhcz-_WihjSQC0oHJ9TCYPk_vArhqVIZ0nv9q090hN8.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/59ZRklaO5bWGqF5A9baEERJtnKITppOI_IvcXXDNrsc.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/59ZRklaO5bWGqF5A9baEERJtnKITppOI_IvcXXDNrsc.woff2 deleted file mode 100644 index bb845920dd..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/59ZRklaO5bWGqF5A9baEERJtnKITppOI_IvcXXDNrsc.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTRWV49_lSm1NYrwo-zkhivY.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTRWV49_lSm1NYrwo-zkhivY.woff2 deleted file mode 100644 index 04a1081575..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTRWV49_lSm1NYrwo-zkhivY.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTT0LW-43aMEzIO6XUTLjad8.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTT0LW-43aMEzIO6XUTLjad8.woff2 deleted file mode 100644 index 3e198c19ab..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTT0LW-43aMEzIO6XUTLjad8.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTZX5f-9o1vgP2EXwfjgl7AY.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTZX5f-9o1vgP2EXwfjgl7AY.woff2 deleted file mode 100644 index 853563940a..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTZX5f-9o1vgP2EXwfjgl7AY.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTa-j2U0lmluP9RWlSytm3ho.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTa-j2U0lmluP9RWlSytm3ho.woff2 deleted file mode 100644 index acb92ca3e9..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTa-j2U0lmluP9RWlSytm3ho.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTaaRobkAwv3vxw3jMhVENGA.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTaaRobkAwv3vxw3jMhVENGA.woff2 deleted file mode 100644 index ab00dd46ed..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTaaRobkAwv3vxw3jMhVENGA.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTegdm0LZdjqr5-oayXSOefg.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTegdm0LZdjqr5-oayXSOefg.woff2 deleted file mode 100644 index 96ae7edd3e..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTegdm0LZdjqr5-oayXSOefg.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTf8zf_FOSsgRmwsS7Aa9k2w.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTf8zf_FOSsgRmwsS7Aa9k2w.woff2 deleted file mode 100644 index cd40e6ac9e..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/DXI1ORHCpsQm3Vp6mXoaTf8zf_FOSsgRmwsS7Aa9k2w.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/K88pR3goAWT7BTt32Z01mxJtnKITppOI_IvcXXDNrsc.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/K88pR3goAWT7BTt32Z01mxJtnKITppOI_IvcXXDNrsc.woff2 deleted file mode 100644 index 0f1eeae6cc..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/K88pR3goAWT7BTt32Z01mxJtnKITppOI_IvcXXDNrsc.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/LWCjsQkB6EMdfHrEVqA1KRJtnKITppOI_IvcXXDNrsc.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/LWCjsQkB6EMdfHrEVqA1KRJtnKITppOI_IvcXXDNrsc.woff2 deleted file mode 100644 index cd47128624..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/LWCjsQkB6EMdfHrEVqA1KRJtnKITppOI_IvcXXDNrsc.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNShWV49_lSm1NYrwo-zkhivY.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNShWV49_lSm1NYrwo-zkhivY.woff2 deleted file mode 100644 index 97af2592f2..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNShWV49_lSm1NYrwo-zkhivY.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSj0LW-43aMEzIO6XUTLjad8.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSj0LW-43aMEzIO6XUTLjad8.woff2 deleted file mode 100644 index bb98ecd516..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSj0LW-43aMEzIO6XUTLjad8.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSpX5f-9o1vgP2EXwfjgl7AY.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSpX5f-9o1vgP2EXwfjgl7AY.woff2 deleted file mode 100644 index d539aeb5ea..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSpX5f-9o1vgP2EXwfjgl7AY.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSq-j2U0lmluP9RWlSytm3ho.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSq-j2U0lmluP9RWlSytm3ho.woff2 deleted file mode 100644 index 7019bcecde..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSq-j2U0lmluP9RWlSytm3ho.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSqaRobkAwv3vxw3jMhVENGA.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSqaRobkAwv3vxw3jMhVENGA.woff2 deleted file mode 100644 index 4111b36671..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSqaRobkAwv3vxw3jMhVENGA.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSugdm0LZdjqr5-oayXSOefg.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSugdm0LZdjqr5-oayXSOefg.woff2 deleted file mode 100644 index 8993a4de6b..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSugdm0LZdjqr5-oayXSOefg.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSv8zf_FOSsgRmwsS7Aa9k2w.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSv8zf_FOSsgRmwsS7Aa9k2w.woff2 deleted file mode 100644 index 363eedbf59..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/MTP_ySUJH_bn48VBG8sNSv8zf_FOSsgRmwsS7Aa9k2w.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/RjgO7rYTmqiVp7vzi-Q5URJtnKITppOI_IvcXXDNrsc.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/RjgO7rYTmqiVp7vzi-Q5URJtnKITppOI_IvcXXDNrsc.woff2 deleted file mode 100644 index 1fd235c2d3..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/RjgO7rYTmqiVp7vzi-Q5URJtnKITppOI_IvcXXDNrsc.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/cJZKeOuBrn4kERxqtaUH3VtXRa8TVwTICgirnJhmVJw.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/cJZKeOuBrn4kERxqtaUH3VtXRa8TVwTICgirnJhmVJw.woff2 deleted file mode 100644 index 5287058cf9..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/cJZKeOuBrn4kERxqtaUH3VtXRa8TVwTICgirnJhmVJw.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzBWV49_lSm1NYrwo-zkhivY.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzBWV49_lSm1NYrwo-zkhivY.woff2 deleted file mode 100644 index 1503cf2592..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzBWV49_lSm1NYrwo-zkhivY.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzD0LW-43aMEzIO6XUTLjad8.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzD0LW-43aMEzIO6XUTLjad8.woff2 deleted file mode 100644 index 4d8eb89756..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzD0LW-43aMEzIO6XUTLjad8.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzJX5f-9o1vgP2EXwfjgl7AY.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzJX5f-9o1vgP2EXwfjgl7AY.woff2 deleted file mode 100644 index 8394e2ec8a..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzJX5f-9o1vgP2EXwfjgl7AY.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzK-j2U0lmluP9RWlSytm3ho.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzK-j2U0lmluP9RWlSytm3ho.woff2 deleted file mode 100644 index bef0033fff..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzK-j2U0lmluP9RWlSytm3ho.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzKaRobkAwv3vxw3jMhVENGA.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzKaRobkAwv3vxw3jMhVENGA.woff2 deleted file mode 100644 index ffc6ce4387..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzKaRobkAwv3vxw3jMhVENGA.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzOgdm0LZdjqr5-oayXSOefg.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzOgdm0LZdjqr5-oayXSOefg.woff2 deleted file mode 100644 index be4c25b429..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzOgdm0LZdjqr5-oayXSOefg.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzP8zf_FOSsgRmwsS7Aa9k2w.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzP8zf_FOSsgRmwsS7Aa9k2w.woff2 deleted file mode 100644 index cf04c330b9..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/k3k702ZOKiLJc3WVjuplzP8zf_FOSsgRmwsS7Aa9k2w.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/style.css b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/style.css deleted file mode 100644 index 7fa4eaf5dc..0000000000 --- a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/style.css +++ /dev/null @@ -1,280 +0,0 @@ -/* cyrillic-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - src: local('Open Sans Light'), local('OpenSans-Light'), url(DXI1ORHCpsQm3Vp6mXoaTa-j2U0lmluP9RWlSytm3ho.woff2) format('woff2'); - unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; -} -/* cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - src: local('Open Sans Light'), local('OpenSans-Light'), url(DXI1ORHCpsQm3Vp6mXoaTZX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2'); - unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; -} -/* greek-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - src: local('Open Sans Light'), local('OpenSans-Light'), url(DXI1ORHCpsQm3Vp6mXoaTRWV49_lSm1NYrwo-zkhivY.woff2) format('woff2'); - unicode-range: U+1F00-1FFF; -} -/* greek */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - src: local('Open Sans Light'), local('OpenSans-Light'), url(DXI1ORHCpsQm3Vp6mXoaTaaRobkAwv3vxw3jMhVENGA.woff2) format('woff2'); - unicode-range: U+0370-03FF; -} -/* vietnamese */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - src: local('Open Sans Light'), local('OpenSans-Light'), url(DXI1ORHCpsQm3Vp6mXoaTf8zf_FOSsgRmwsS7Aa9k2w.woff2) format('woff2'); - unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB; -} -/* latin-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - src: local('Open Sans Light'), local('OpenSans-Light'), url(DXI1ORHCpsQm3Vp6mXoaTT0LW-43aMEzIO6XUTLjad8.woff2) format('woff2'); - unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - src: local('Open Sans Light'), local('OpenSans-Light'), url(DXI1ORHCpsQm3Vp6mXoaTegdm0LZdjqr5-oayXSOefg.woff2) format('woff2'), url(https://fonts.gstatic.com/s/opensans/v13/DXI1ORHCpsQm3Vp6mXoaTXhCUOGz7vYGh680lGh-uXM.woff) format('woff'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; -} -/* cyrillic-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - src: local('Open Sans'), local('OpenSans'), url(K88pR3goAWT7BTt32Z01mxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); - unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; -} -/* cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - src: local('Open Sans'), local('OpenSans'), url(RjgO7rYTmqiVp7vzi-Q5URJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); - unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; -} -/* greek-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - src: local('Open Sans'), local('OpenSans'), url(LWCjsQkB6EMdfHrEVqA1KRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); - unicode-range: U+1F00-1FFF; -} -/* greek */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - src: local('Open Sans'), local('OpenSans'), url(xozscpT2726on7jbcb_pAhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); - unicode-range: U+0370-03FF; -} -/* vietnamese */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - src: local('Open Sans'), local('OpenSans'), url(59ZRklaO5bWGqF5A9baEERJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); - unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB; -} -/* latin-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - src: local('Open Sans'), local('OpenSans'), url(u-WUoqrET9fUeobQW7jkRRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); - unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - src: local('Open Sans'), local('OpenSans'), url(cJZKeOuBrn4kERxqtaUH3VtXRa8TVwTICgirnJhmVJw.woff2) format('woff2'), url(https://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3T8E0i7KZn-EPnyo3HZu7kw.woff) format('woff'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; -} -/* cyrillic-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 500; - src: local('Open Sans'), local('OpenSans'), url(K88pR3goAWT7BTt32Z01mxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); - unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; -} -/* cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 500; - src: local('Open Sans'), local('OpenSans'), url(RjgO7rYTmqiVp7vzi-Q5URJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); - unicode-range: U+0500-045F, U+0490-0491, U+04B0-04B1, U+2116; -} -/* greek-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 500; - src: local('Open Sans'), local('OpenSans'), url(LWCjsQkB6EMdfHrEVqA1KRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); - unicode-range: U+1F00-1FFF; -} -/* greek */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 500; - src: local('Open Sans'), local('OpenSans'), url(xozscpT2726on7jbcb_pAhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); - unicode-range: U+0370-03FF; -} -/* vietnamese */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 500; - src: local('Open Sans'), local('OpenSans'), url(59ZRklaO5bWGqF5A9baEERJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); - unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB; -} -/* latin-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 500; - src: local('Open Sans'), local('OpenSans'), url(u-WUoqrET9fUeobQW7jkRRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); - unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 500; - src: local('Open Sans'), local('OpenSans'), url(cJZKeOuBrn4kERxqtaUH3VtXRa8TVwTICgirnJhmVJw.woff2) format('woff2'), url(https://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3T8E0i7KZn-EPnyo3HZu7kw.woff) format('woff'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; -} -/* cyrillic-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(MTP_ySUJH_bn48VBG8sNSq-j2U0lmluP9RWlSytm3ho.woff2) format('woff2'); - unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; -} -/* cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(MTP_ySUJH_bn48VBG8sNSpX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2'); - unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; -} -/* greek-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(MTP_ySUJH_bn48VBG8sNShWV49_lSm1NYrwo-zkhivY.woff2) format('woff2'); - unicode-range: U+1F00-1FFF; -} -/* greek */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(MTP_ySUJH_bn48VBG8sNSqaRobkAwv3vxw3jMhVENGA.woff2) format('woff2'); - unicode-range: U+0370-03FF; -} -/* vietnamese */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(MTP_ySUJH_bn48VBG8sNSv8zf_FOSsgRmwsS7Aa9k2w.woff2) format('woff2'); - unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB; -} -/* latin-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(MTP_ySUJH_bn48VBG8sNSj0LW-43aMEzIO6XUTLjad8.woff2) format('woff2'); - unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(MTP_ySUJH_bn48VBG8sNSugdm0LZdjqr5-oayXSOefg.woff2) format('woff2'), url(https://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSnhCUOGz7vYGh680lGh-uXM.woff) format('woff'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; -} -/* cyrillic-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 700; - src: local('Open Sans Bold'), local('OpenSans-Bold'), url(k3k702ZOKiLJc3WVjuplzK-j2U0lmluP9RWlSytm3ho.woff2) format('woff2'); - unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; -} -/* cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 700; - src: local('Open Sans Bold'), local('OpenSans-Bold'), url(k3k702ZOKiLJc3WVjuplzJX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2'); - unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; -} -/* greek-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 700; - src: local('Open Sans Bold'), local('OpenSans-Bold'), url(k3k702ZOKiLJc3WVjuplzBWV49_lSm1NYrwo-zkhivY.woff2) format('woff2'); - unicode-range: U+1F00-1FFF; -} -/* greek */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 700; - src: local('Open Sans Bold'), local('OpenSans-Bold'), url(k3k702ZOKiLJc3WVjuplzKaRobkAwv3vxw3jMhVENGA.woff2) format('woff2'); - unicode-range: U+0370-03FF; -} -/* vietnamese */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 700; - src: local('Open Sans Bold'), local('OpenSans-Bold'), url(k3k702ZOKiLJc3WVjuplzP8zf_FOSsgRmwsS7Aa9k2w.woff2) format('woff2'); - unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB; -} -/* latin-ext */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 700; - src: local('Open Sans Bold'), local('OpenSans-Bold'), url(k3k702ZOKiLJc3WVjuplzD0LW-43aMEzIO6XUTLjad8.woff2) format('woff2'); - unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 700; - src: local('Open Sans Bold'), local('OpenSans-Bold'), url(k3k702ZOKiLJc3WVjuplzOgdm0LZdjqr5-oayXSOefg.woff2) format('woff2'), url(https://fonts.gstatic.com/s/opensans/v13/k3k702ZOKiLJc3WVjuplzHhCUOGz7vYGh680lGh-uXM.woff) format('woff'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; -} \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/u-WUoqrET9fUeobQW7jkRRJtnKITppOI_IvcXXDNrsc.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/u-WUoqrET9fUeobQW7jkRRJtnKITppOI_IvcXXDNrsc.woff2 deleted file mode 100644 index 0b5a66eb4c..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/u-WUoqrET9fUeobQW7jkRRJtnKITppOI_IvcXXDNrsc.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/xozscpT2726on7jbcb_pAhJtnKITppOI_IvcXXDNrsc.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/xozscpT2726on7jbcb_pAhJtnKITppOI_IvcXXDNrsc.woff2 deleted file mode 100644 index 7971f53427..0000000000 Binary files a/dashboard-ui/bower_components/emby-webcomponents/fonts/opensans/xozscpT2726on7jbcb_pAhJtnKITppOI_IvcXXDNrsc.woff2 and /dev/null differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/-L14Jk06m6pUHB-5mXQQnRJtnKITppOI_IvcXXDNrsc.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/-l14jk06m6puhb-5mxqqnrjtnkitppoi_ivcxxdnrsc.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/-L14Jk06m6pUHB-5mXQQnRJtnKITppOI_IvcXXDNrsc.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/-l14jk06m6puhb-5mxqqnrjtnkitppoi_ivcxxdnrsc.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/0eC6fl06luXEYWpBSJvXCBJtnKITppOI_IvcXXDNrsc.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/0ec6fl06luxeywpbsjvxcbjtnkitppoi_ivcxxdnrsc.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/0eC6fl06luXEYWpBSJvXCBJtnKITppOI_IvcXXDNrsc.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/0ec6fl06luxeywpbsjvxcbjtnkitppoi_ivcxxdnrsc.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/2tsd397wLxj96qwHyNIkxPesZW2xOQ-xsNqO47m55DA.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/2tsd397wlxj96qwhynikxpeszw2xoq-xsnqo47m55da.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/2tsd397wLxj96qwHyNIkxPesZW2xOQ-xsNqO47m55DA.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/2tsd397wlxj96qwhynikxpeszw2xoq-xsnqo47m55da.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/97uahxiqZRoncBaCEI3aWxJtnKITppOI_IvcXXDNrsc.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/97uahxiqzroncbacei3awxjtnkitppoi_ivcxxdnrsc.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/97uahxiqZRoncBaCEI3aWxJtnKITppOI_IvcXXDNrsc.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/97uahxiqzroncbacei3awxjtnkitppoi_ivcxxdnrsc.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/aZMswpodYeVhtRvuABJWvBTbgVql8nDJpwnrE27mub0.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/azmswpodyevhtrvuabjwvbtbgvql8ndjpwnre27mub0.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/aZMswpodYeVhtRvuABJWvBTbgVql8nDJpwnrE27mub0.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/azmswpodyevhtrvuabjwvbtbgvql8ndjpwnre27mub0.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/CWB0XYA8bzo0kSThX0UTuA.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/cwb0xya8bzo0ksthx0utua.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/CWB0XYA8bzo0kSThX0UTuA.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/cwb0xya8bzo0ksthx0utua.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/d-6IYplOFocCacKzxwXSOFtXRa8TVwTICgirnJhmVJw.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/d-6iyplofoccackzxwxsoftxra8tvwticgirnjhmvjw.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/d-6IYplOFocCacKzxwXSOFtXRa8TVwTICgirnJhmVJw.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/d-6iyplofoccackzxwxsoftxra8tvwticgirnjhmvjw.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/e7MeVAyvogMqFwwl61PKhBTbgVql8nDJpwnrE27mub0.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/e7mevayvogmqfwwl61pkhbtbgvql8ndjpwnre27mub0.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/e7MeVAyvogMqFwwl61PKhBTbgVql8nDJpwnrE27mub0.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/e7mevayvogmqfwwl61pkhbtbgvql8ndjpwnre27mub0.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/Fcx7Wwv8OzT71A3E1XOAjvesZW2xOQ-xsNqO47m55DA.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/fcx7wwv8ozt71a3e1xoajveszw2xoq-xsnqo47m55da.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/Fcx7Wwv8OzT71A3E1XOAjvesZW2xOQ-xsNqO47m55DA.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/fcx7wwv8ozt71a3e1xoajveszw2xoq-xsnqo47m55da.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/Fl4y0QdOxyyTHEGMXX8kcRJtnKITppOI_IvcXXDNrsc.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/fl4y0qdoxyythegmxx8kcrjtnkitppoi_ivcxxdnrsc.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/Fl4y0QdOxyyTHEGMXX8kcRJtnKITppOI_IvcXXDNrsc.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/fl4y0qdoxyythegmxx8kcrjtnkitppoi_ivcxxdnrsc.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/frNV30OaYdlFRtH2VnZZdhTbgVql8nDJpwnrE27mub0.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/frnv30oaydlfrth2vnzzdhtbgvql8ndjpwnre27mub0.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/frNV30OaYdlFRtH2VnZZdhTbgVql8nDJpwnrE27mub0.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/frnv30oaydlfrth2vnzzdhtbgvql8ndjpwnre27mub0.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/gwVJDERN2Amz39wrSoZ7FxTbgVql8nDJpwnrE27mub0.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/gwvjdern2amz39wrsoz7fxtbgvql8ndjpwnre27mub0.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/gwVJDERN2Amz39wrSoZ7FxTbgVql8nDJpwnrE27mub0.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/gwvjdern2amz39wrsoz7fxtbgvql8ndjpwnre27mub0.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/Hgo13k-tfSpn0qi1SFdUfVtXRa8TVwTICgirnJhmVJw.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/hgo13k-tfspn0qi1sfdufvtxra8tvwticgirnjhmvjw.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/Hgo13k-tfSpn0qi1SFdUfVtXRa8TVwTICgirnJhmVJw.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/hgo13k-tfspn0qi1sfdufvtxra8tvwticgirnjhmvjw.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/I3S1wsgSg9YCurV6PUkTORJtnKITppOI_IvcXXDNrsc.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/i3s1wsgsg9ycurv6puktorjtnkitppoi_ivcxxdnrsc.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/I3S1wsgSg9YCurV6PUkTORJtnKITppOI_IvcXXDNrsc.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/i3s1wsgsg9ycurv6puktorjtnkitppoi_ivcxxdnrsc.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/NYDWBdD4gIq26G5XYbHsFBJtnKITppOI_IvcXXDNrsc.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/nydwbdd4giq26g5xybhsfbjtnkitppoi_ivcxxdnrsc.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/NYDWBdD4gIq26G5XYbHsFBJtnKITppOI_IvcXXDNrsc.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/nydwbdd4giq26g5xybhsfbjtnkitppoi_ivcxxdnrsc.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/oOeFwZNlrTefzLYmlVV1UBJtnKITppOI_IvcXXDNrsc.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/ooefwznlrtefzlymlvv1ubjtnkitppoi_ivcxxdnrsc.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/oOeFwZNlrTefzLYmlVV1UBJtnKITppOI_IvcXXDNrsc.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/ooefwznlrtefzlymlvv1ubjtnkitppoi_ivcxxdnrsc.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/Pru33qjShpZSmG3z6VYwnRJtnKITppOI_IvcXXDNrsc.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/pru33qjshpzsmg3z6vywnrjtnkitppoi_ivcxxdnrsc.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/Pru33qjShpZSmG3z6VYwnRJtnKITppOI_IvcXXDNrsc.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/pru33qjshpzsmg3z6vywnrjtnkitppoi_ivcxxdnrsc.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/RobotoBold.woff b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/robotobold.woff similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/RobotoBold.woff rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/robotobold.woff diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/RobotoLight.woff b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/robotolight.woff similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/RobotoLight.woff rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/robotolight.woff diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/RobotoMedium.woff b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/robotomedium.woff similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/RobotoMedium.woff rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/robotomedium.woff diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/RobotoRegular.woff b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/robotoregular.woff similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/RobotoRegular.woff rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/robotoregular.woff diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/RobotoThin.woff b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/robotothin.woff similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/RobotoThin.woff rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/robotothin.woff diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/RxZJdnzeo3R5zSexge8UUVtXRa8TVwTICgirnJhmVJw.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/rxzjdnzeo3r5zsexge8uuvtxra8tvwticgirnjhmvjw.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/RxZJdnzeo3R5zSexge8UUVtXRa8TVwTICgirnJhmVJw.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/rxzjdnzeo3r5zsexge8uuvtxra8tvwticgirnjhmvjw.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/style.css b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/style.css index 5741754176..a34432cea1 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/style.css +++ b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/style.css @@ -3,7 +3,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 100; - src: local('Roboto Thin'), local('Roboto-Thin'), url(ty9dfvLAziwdqQ2dHoyjphTbgVql8nDJpwnrE27mub0.woff2) format('woff2'), url(RobotoThin.woff) format('woff'); + src: local('Roboto Thin'), local('Roboto-Thin'), url(ty9dfvlaziwdqq2dhoyjphtbgvql8ndjpwnre27mub0.woff2) format('woff2'), url(robotothin.woff) format('woff'); unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; } /* cyrillic */ @@ -11,7 +11,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 100; - src: local('Roboto Thin'), local('Roboto-Thin'), url(frNV30OaYdlFRtH2VnZZdhTbgVql8nDJpwnrE27mub0.woff2) format('woff2'), url(RobotoThin.woff) format('woff'); + src: local('Roboto Thin'), local('Roboto-Thin'), url(frnv30oaydlfrth2vnzzdhtbgvql8ndjpwnre27mub0.woff2) format('woff2'), url(robotothin.woff) format('woff'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -19,7 +19,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 100; - src: local('Roboto Thin'), local('Roboto-Thin'), url(gwVJDERN2Amz39wrSoZ7FxTbgVql8nDJpwnrE27mub0.woff2) format('woff2'), url(RobotoThin.woff) format('woff'); + src: local('Roboto Thin'), local('Roboto-Thin'), url(gwvjdern2amz39wrsoz7fxtbgvql8ndjpwnre27mub0.woff2) format('woff2'), url(robotothin.woff) format('woff'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -27,7 +27,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 100; - src: local('Roboto Thin'), local('Roboto-Thin'), url(aZMswpodYeVhtRvuABJWvBTbgVql8nDJpwnrE27mub0.woff2) format('woff2'), url(RobotoThin.woff) format('woff'); + src: local('Roboto Thin'), local('Roboto-Thin'), url(azmswpodyevhtrvuabjwvbtbgvql8ndjpwnre27mub0.woff2) format('woff2'), url(robotothin.woff) format('woff'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -35,7 +35,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 100; - src: local('Roboto Thin'), local('Roboto-Thin'), url(VvXUGKZXbHtX_S_VCTLpGhTbgVql8nDJpwnrE27mub0.woff2) format('woff2'), url(RobotoThin.woff) format('woff'); + src: local('Roboto Thin'), local('Roboto-Thin'), url(vvxugkzxbhtx_s_vctlpghtbgvql8ndjpwnre27mub0.woff2) format('woff2'), url(robotothin.woff) format('woff'); unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB; } /* latin-ext */ @@ -43,7 +43,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 100; - src: local('Roboto Thin'), local('Roboto-Thin'), url(e7MeVAyvogMqFwwl61PKhBTbgVql8nDJpwnrE27mub0.woff2) format('woff2'), url(RobotoThin.woff) format('woff'); + src: local('Roboto Thin'), local('Roboto-Thin'), url(e7mevayvogmqfwwl61pkhbtbgvql8ndjpwnre27mub0.woff2) format('woff2'), url(robotothin.woff) format('woff'); unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @@ -51,7 +51,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 100; - src: local('Roboto Thin'), local('Roboto-Thin'), url(2tsd397wLxj96qwHyNIkxPesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'), url(RobotoThin.woff) format('woff'); + src: local('Roboto Thin'), local('Roboto-Thin'), url(2tsd397wlxj96qwhynikxpeszw2xoq-xsnqo47m55da.woff2) format('woff2'), url(robotothin.woff) format('woff'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; } /* cyrillic-ext */ @@ -59,7 +59,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(0eC6fl06luXEYWpBSJvXCBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoLight.woff) format('woff'); + src: local('Roboto Light'), local('Roboto-Light'), url(0ec6fl06luxeywpbsjvxcbjtnkitppoi_ivcxxdnrsc.woff2) format('woff2'), url(robotolight.woff) format('woff'); unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; } /* cyrillic */ @@ -67,7 +67,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(Fl4y0QdOxyyTHEGMXX8kcRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoLight.woff) format('woff'); + src: local('Roboto Light'), local('Roboto-Light'), url(fl4y0qdoxyythegmxx8kcrjtnkitppoi_ivcxxdnrsc.woff2) format('woff2'), url(robotolight.woff) format('woff'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -75,7 +75,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(-L14Jk06m6pUHB-5mXQQnRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoLight.woff) format('woff'); + src: local('Roboto Light'), local('Roboto-Light'), url(-l14jk06m6puhb-5mxqqnrjtnkitppoi_ivcxxdnrsc.woff2) format('woff2'), url(robotolight.woff) format('woff'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -83,7 +83,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(I3S1wsgSg9YCurV6PUkTORJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoLight.woff) format('woff'); + src: local('Roboto Light'), local('Roboto-Light'), url(i3s1wsgsg9ycurv6puktorjtnkitppoi_ivcxxdnrsc.woff2) format('woff2'), url(robotolight.woff) format('woff'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -91,7 +91,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(NYDWBdD4gIq26G5XYbHsFBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoLight.woff) format('woff'); + src: local('Roboto Light'), local('Roboto-Light'), url(nydwbdd4giq26g5xybhsfbjtnkitppoi_ivcxxdnrsc.woff2) format('woff2'), url(robotolight.woff) format('woff'); unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB; } /* latin-ext */ @@ -99,7 +99,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(Pru33qjShpZSmG3z6VYwnRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoLight.woff) format('woff'); + src: local('Roboto Light'), local('Roboto-Light'), url(pru33qjshpzsmg3z6vywnrjtnkitppoi_ivcxxdnrsc.woff2) format('woff2'), url(robotolight.woff) format('woff'); unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @@ -107,7 +107,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(Hgo13k-tfSpn0qi1SFdUfVtXRa8TVwTICgirnJhmVJw.woff2) format('woff2'), url(RobotoLight.woff) format('woff'); + src: local('Roboto Light'), local('Roboto-Light'), url(hgo13k-tfspn0qi1sfdufvtxra8tvwticgirnjhmvjw.woff2) format('woff2'), url(robotolight.woff) format('woff'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; } /* cyrillic-ext */ @@ -115,7 +115,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 400; - src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/ek4gzZ-GeXAPcSbHtCeQI_esZW2xOQ-xsNqO47m55DA.woff2) format('woff2'), url(RobotoRegular.woff) format('woff'); + src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/ek4gzZ-GeXAPcSbHtCeQI_esZW2xOQ-xsNqO47m55DA.woff2) format('woff2'), url(robotoregular.woff) format('woff'); unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; } /* cyrillic */ @@ -123,7 +123,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 400; - src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/mErvLBYg_cXG3rLvUsKT_fesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'), url(RobotoRegular.woff) format('woff'); + src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/mErvLBYg_cXG3rLvUsKT_fesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'), url(robotoregular.woff) format('woff'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -131,7 +131,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 400; - src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/-2n2p-_Y08sg57CNWQfKNvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'), url(RobotoRegular.woff) format('woff'); + src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/-2n2p-_Y08sg57CNWQfKNvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'), url(robotoregular.woff) format('woff'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -139,7 +139,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 400; - src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/u0TOpm082MNkS5K0Q4rhqvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'), url(RobotoRegular.woff) format('woff'); + src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/u0TOpm082MNkS5K0Q4rhqvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'), url(robotoregular.woff) format('woff'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -147,7 +147,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 400; - src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/NdF9MtnOpLzo-noMoG0miPesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'), url(RobotoRegular.woff) format('woff'); + src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/NdF9MtnOpLzo-noMoG0miPesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'), url(robotoregular.woff) format('woff'); unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB; } /* latin-ext */ @@ -155,7 +155,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 400; - src: local('Roboto'), local('Roboto-Regular'), url(Fcx7Wwv8OzT71A3E1XOAjvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'), url(RobotoRegular.woff) format('woff'); + src: local('Roboto'), local('Roboto-Regular'), url(fcx7wwv8ozt71a3e1xoajveszw2xoq-xsnqo47m55da.woff2) format('woff2'), url(robotoregular.woff) format('woff'); unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @@ -163,7 +163,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 400; - src: local('Roboto'), local('Roboto-Regular'), url(CWB0XYA8bzo0kSThX0UTuA.woff2) format('woff2'), url(RobotoRegular.woff) format('woff'); + src: local('Roboto'), local('Roboto-Regular'), url(cwb0xya8bzo0ksthx0utua.woff2) format('woff2'), url(robotoregular.woff) format('woff'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; } /* cyrillic-ext */ @@ -171,7 +171,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 500; - src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v15/ZLqKeelYbATG60EpZBSDyxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoMedium.woff) format('woff'); + src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v15/ZLqKeelYbATG60EpZBSDyxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(robotomedium.woff) format('woff'); unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; } /* cyrillic */ @@ -179,7 +179,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 500; - src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v15/oHi30kwQWvpCWqAhzHcCSBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoMedium.woff) format('woff'); + src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v15/oHi30kwQWvpCWqAhzHcCSBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(robotomedium.woff) format('woff'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -187,7 +187,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 500; - src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v15/rGvHdJnr2l75qb0YND9NyBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoMedium.woff) format('woff'); + src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v15/rGvHdJnr2l75qb0YND9NyBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(robotomedium.woff) format('woff'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -195,7 +195,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 500; - src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v15/mx9Uck6uB63VIKFYnEMXrRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoMedium.woff) format('woff'); + src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v15/mx9Uck6uB63VIKFYnEMXrRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(robotomedium.woff) format('woff'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -203,7 +203,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 500; - src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v15/mbmhprMH69Zi6eEPBYVFhRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoMedium.woff) format('woff'); + src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v15/mbmhprMH69Zi6eEPBYVFhRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(robotomedium.woff) format('woff'); unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB; } /* latin-ext */ @@ -211,7 +211,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 500; - src: local('Roboto Medium'), local('Roboto-Medium'), url(oOeFwZNlrTefzLYmlVV1UBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoMedium.woff) format('woff'); + src: local('Roboto Medium'), local('Roboto-Medium'), url(ooefwznlrtefzlymlvv1ubjtnkitppoi_ivcxxdnrsc.woff2) format('woff2'), url(robotomedium.woff) format('woff'); unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @@ -219,7 +219,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 500; - src: local('Roboto Medium'), local('Roboto-Medium'), url(RxZJdnzeo3R5zSexge8UUVtXRa8TVwTICgirnJhmVJw.woff2) format('woff2'), url(RobotoMedium.woff) format('woff'); + src: local('Roboto Medium'), local('Roboto-Medium'), url(rxzjdnzeo3r5zsexge8uuvtxra8tvwticgirnjhmvjw.woff2) format('woff2'), url(robotomedium.woff) format('woff'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; } /* cyrillic-ext */ @@ -227,7 +227,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 700; - src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v15/77FXFjRbGzN4aCrSFhlh3hJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoBold.woff) format('woff'); + src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v15/77FXFjRbGzN4aCrSFhlh3hJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(robotobold.woff) format('woff'); unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; } /* cyrillic */ @@ -235,7 +235,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 700; - src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v15/isZ-wbCXNKAbnjo6_TwHThJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoBold.woff) format('woff'); + src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v15/isZ-wbCXNKAbnjo6_TwHThJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(robotobold.woff) format('woff'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @@ -243,7 +243,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 700; - src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v15/UX6i4JxQDm3fVTc1CPuwqhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoBold.woff) format('woff'); + src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v15/UX6i4JxQDm3fVTc1CPuwqhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(robotobold.woff) format('woff'); unicode-range: U+1F00-1FFF; } /* greek */ @@ -251,7 +251,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 700; - src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v15/jSN2CGVDbcVyCnfJfjSdfBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoBold.woff) format('woff'); + src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v15/jSN2CGVDbcVyCnfJfjSdfBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(robotobold.woff) format('woff'); unicode-range: U+0370-03FF; } /* vietnamese */ @@ -259,7 +259,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 700; - src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v15/PwZc-YbIL414wB9rB1IAPRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoBold.woff) format('woff'); + src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v15/PwZc-YbIL414wB9rB1IAPRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(robotobold.woff) format('woff'); unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB; } /* latin-ext */ @@ -267,7 +267,7 @@ font-family: 'Roboto'; font-style: normal; font-weight: 700; - src: local('Roboto Bold'), local('Roboto-Bold'), url(97uahxiqZRoncBaCEI3aWxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'), url(RobotoBold.woff) format('woff'); + src: local('Roboto Bold'), local('Roboto-Bold'), url(97uahxiqzroncbacei3awxjtnkitppoi_ivcxxdnrsc.woff2) format('woff2'), url(robotobold.woff) format('woff'); unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @@ -275,6 +275,6 @@ font-family: 'Roboto'; font-style: normal; font-weight: 700; - src: local('Roboto Bold'), local('Roboto-Bold'), url(d-6IYplOFocCacKzxwXSOFtXRa8TVwTICgirnJhmVJw.woff2) format('woff2'), url(RobotoBold.woff) format('woff'); + src: local('Roboto Bold'), local('Roboto-Bold'), url(d-6iyplofoccackzxwxsoftxra8tvwticgirnjhmvjw.woff2) format('woff2'), url(robotobold.woff) format('woff'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; } \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/ty9dfvLAziwdqQ2dHoyjphTbgVql8nDJpwnrE27mub0.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/ty9dfvlaziwdqq2dhoyjphtbgvql8ndjpwnre27mub0.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/ty9dfvLAziwdqQ2dHoyjphTbgVql8nDJpwnrE27mub0.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/ty9dfvlaziwdqq2dhoyjphtbgvql8ndjpwnre27mub0.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/VvXUGKZXbHtX_S_VCTLpGhTbgVql8nDJpwnrE27mub0.woff2 b/dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/vvxugkzxbhtx_s_vctlpghtbgvql8ndjpwnre27mub0.woff2 similarity index 100% rename from dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/VvXUGKZXbHtX_S_VCTLpGhTbgVql8nDJpwnrE27mub0.woff2 rename to dashboard-ui/bower_components/emby-webcomponents/fonts/roboto/vvxugkzxbhtx_s_vctlpghtbgvql8ndjpwnre27mub0.woff2 diff --git a/dashboard-ui/bower_components/emby-webcomponents/fullscreen/fullscreen-doubleclick.js b/dashboard-ui/bower_components/emby-webcomponents/fullscreen/fullscreen-doubleclick.js index 762e391633..bc12a6a76d 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/fullscreen/fullscreen-doubleclick.js +++ b/dashboard-ui/bower_components/emby-webcomponents/fullscreen/fullscreen-doubleclick.js @@ -1,12 +1,23 @@ define(['dom', 'fullscreenManager'], function (dom, fullscreenManager) { 'use strict'; - dom.addEventListener(window, 'dblclick', function () { + function isTargetValid(target) { - if (fullscreenManager.isFullScreen()) { - fullscreenManager.exitFullscreen(); - } else { - fullscreenManager.requestFullscreen(); + if (dom.parentWithTag(target, ['BUTTON', 'INPUT', 'TEXTAREA'])) { + return false; + } + + return true; + } + + dom.addEventListener(window, 'dblclick', function (e) { + + if (isTargetValid(e.target)) { + if (fullscreenManager.isFullScreen()) { + fullscreenManager.exitFullscreen(); + } else { + fullscreenManager.requestFullscreen(); + } } }, { diff --git a/dashboard-ui/bower_components/emby-webcomponents/fullscreen/fullscreenmanager.js b/dashboard-ui/bower_components/emby-webcomponents/fullscreen/fullscreenmanager.js index 18a4eb7b12..79030d325b 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/fullscreen/fullscreenmanager.js +++ b/dashboard-ui/bower_components/emby-webcomponents/fullscreen/fullscreenmanager.js @@ -1,4 +1,4 @@ -define([], function () { +define(['events', 'dom'], function (events, dom) { 'use strict'; function fullscreenManager() { @@ -38,5 +38,23 @@ define([], function () { return document.fullscreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement ? true : false; }; - return new fullscreenManager(); + var manager = new fullscreenManager(); + + function onFullScreenChange() { + events.trigger(manager, 'fullscreenchange'); + } + + dom.addEventListener(document, 'fullscreenchange', onFullScreenChange, { + passive: true + }); + + dom.addEventListener(document, 'webkitfullscreenchange', onFullScreenChange, { + passive: true + }); + + dom.addEventListener(document, 'mozfullscreenchange', onFullScreenChange, { + passive: true + }); + + return manager; }); \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-webcomponents/guide/guide.css b/dashboard-ui/bower_components/emby-webcomponents/guide/guide.css index 108519e265..1d7a3ae28b 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/guide/guide.css +++ b/dashboard-ui/bower_components/emby-webcomponents/guide/guide.css @@ -66,6 +66,8 @@ white-space: nowrap; position: relative; contain: strict; + box-sizing: border-box; + border-bottom: .2em solid #121212; } .timeslotHeadersInner { @@ -79,7 +81,7 @@ width: 100%; height: 2px; display: flex; - margin-left: .65vh; + margin-left: .25em; background-color: #52B54B; height: 2px; transform-origin: left; @@ -88,10 +90,10 @@ .currentTimeIndicatorArrowContainer { position: absolute; - bottom: -1vh; + bottom: -.4em; width: 100%; color: #52B54B; - margin-left: .65vh; + margin-left: .25em; transform-origin: left; transition: transform 500ms ease-out; } @@ -102,11 +104,11 @@ } .currentTimeIndicatorArrow { - width: 3vh; - height: 3vh; - font-size: 3vh; + width: 1em; + height: 1em; + font-size: 1.2em; color: #52B54B; - margin-left: -1.5vh; + margin-left: -.52em; } .channelPrograms, .timeslotHeadersInner { @@ -144,8 +146,7 @@ overflow: hidden; text-overflow: ellipsis; border-right: 1px solid #121212; - width: 24vw; - background: rgba(40, 40, 40, .9); + background: rgb(38, 38, 38); display: flex; align-items: center; text-decoration: none; @@ -154,30 +155,37 @@ contain: strict; } -@media all and (min-width: 500px) { +/* Important - have to put the fixed width on channelsContainer, not the individual channelHeaderCell + This was causing channelsContainer to extend beyond the fixed width on ps4, tizen, lg and opera tv. +*/ +.channelsContainer, .channelTimeslotHeader { + width: 24vw; +} - .channelHeaderCell, .channelTimeslotHeader { +.channelHeaderCell { + width: 100%; +} + +@media all and (min-width:500px) { + .channelsContainer, .channelTimeslotHeader { width: 16vw; } } -@media all and (min-width: 600px) { - - .channelHeaderCell, .channelTimeslotHeader { +@media all and (min-width:600px) { + .channelsContainer, .channelTimeslotHeader { width: 16vw; } } -@media all and (min-width: 800px) { - - .channelHeaderCell, .channelTimeslotHeader { +@media all and (min-width:800px) { + .channelsContainer, .channelTimeslotHeader { width: 14vw; } } -@media all and (min-width: 1280px) { - - .channelHeaderCell, .channelTimeslotHeader { +@media all and (min-width:1280px) { + .channelsContainer, .channelTimeslotHeader { width: 12vw; } } @@ -196,7 +204,7 @@ } .channelHeaderCell { - border-bottom: .65vh solid #121212 !important; + border-bottom: .2em solid #121212 !important; background-size: auto 70%; background-position: 92% center; background-repeat: no-repeat; @@ -209,15 +217,8 @@ } } -@media all and (max-width: 1200px) { - - .guideChannelNumberWithImage { - display: none; - } -} - .channelPrograms, .channelHeaderCell { - height: 4em; + height: 4.4em; contain: strict; } @@ -227,7 +228,7 @@ } .channelPrograms-tv, .channelHeaderCell-tv { - height: 3.2em; + height: 3.4em; } .channelTimeslotHeader { @@ -236,9 +237,6 @@ .channelTimeslotHeader, .timeslotHeader { background: transparent; -} - -.timeslotHeader, .channelTimeslotHeader { height: 2.2em; } @@ -254,10 +252,9 @@ .programCell { position: absolute; top: 0; - /* Unfortunately the borders using vh get rounded while the bottom property doesn't. So this is a little hack to try and make them even*/ - bottom: .59vh; - border-left: .65vh solid #121212 !important; - background-color: rgba(32, 32, 32, .95); + bottom: 0; + border-left: .2em solid #121212 !important; + background-color: rgb(30, 30, 30); display: flex; text-decoration: none; overflow: hidden; @@ -265,6 +262,9 @@ /* Needed for Firefox */ text-align: left; contain: strict; + flex-grow: 1; + margin: 0 !important; + padding: 0 !important; } .programAccent { @@ -304,6 +304,20 @@ display: block; } +.guideProgramNameText { + margin: 0; + font-weight: normal; +} + +.guideProgramSecondaryInfo { + display: flex; + align-items: center; +} + +.programSecondaryTitle { + opacity: .6; +} + .programIcon { margin-left: auto; margin-right: .25em; @@ -326,6 +340,8 @@ padding: .18em .32em; border-radius: .25em; margin-right: .35em; + width: auto; + height: auto; } .programTextIcon-tv { @@ -337,6 +353,8 @@ max-width: 30%; text-overflow: ellipsis; overflow: hidden; + font-weight: normal; + margin: 0; } .guideChannelName { @@ -364,14 +382,10 @@ .channelsContainer { display: flex; flex-shrink: 0; -} - -.channelList { - display: flex; flex-direction: column; } -.channelList, .programGrid { +.channelsContainer, .programGrid { contain: layout style; } diff --git a/dashboard-ui/bower_components/emby-webcomponents/guide/guide.js b/dashboard-ui/bower_components/emby-webcomponents/guide/guide.js index 4461279b0a..d314996411 100644 --- a/dashboard-ui/bower_components/emby-webcomponents/guide/guide.js +++ b/dashboard-ui/bower_components/emby-webcomponents/guide/guide.js @@ -512,20 +512,32 @@ html += '
'; + html += '
' + program.Name + '
'; + + var indicatorHtml = null; if (program.IsLive && options.showLiveIndicator) { - html += '' + globalize.translate('sharedcomponents#Live') + ''; + indicatorHtml = '' + globalize.translate('sharedcomponents#Live') + ''; } else if (program.IsPremiere && options.showPremiereIndicator) { - html += '' + globalize.translate('sharedcomponents#Premiere') + ''; + indicatorHtml = '' + globalize.translate('sharedcomponents#Premiere') + ''; } else if (program.IsSeries && !program.IsRepeat && options.showNewIndicator) { - html += '' + globalize.translate('sharedcomponents#AttributeNew') + ''; + indicatorHtml = '' + globalize.translate('sharedcomponents#AttributeNew') + ''; } else if (program.IsSeries && program.IsRepeat && options.showRepeatIndicator) { - html += '' + globalize.translate('sharedcomponents#Repeat') + ''; + indicatorHtml = '' + globalize.translate('sharedcomponents#Repeat') + ''; + } + if (indicatorHtml || (program.EpisodeTitle && options.showEpisodeTitle)) { + html += '
'; + + html += indicatorHtml || ''; + + if (program.EpisodeTitle && options.showEpisodeTitle) { + html += '' + program.EpisodeTitle + ''; + } + html += '
'; } - html += program.Name; html += '
'; if (program.IsHD && options.showHdIcon) { @@ -566,7 +578,8 @@ showLiveIndicator: allowIndicators && userSettings.get('guide-indicator-live') !== 'false', showPremiereIndicator: allowIndicators && userSettings.get('guide-indicator-premiere') !== 'false', showNewIndicator: allowIndicators && userSettings.get('guide-indicator-new') === 'true', - showRepeatIndicator: allowIndicators && userSettings.get('guide-indicator-repeat') === 'true' + showRepeatIndicator: allowIndicators && userSettings.get('guide-indicator-repeat') === 'true', + showEpisodeTitle: layoutManager.tv ? false : true }; for (var i = 0, length = channels.length; i < length; i++) { @@ -610,20 +623,17 @@ html += ''; } - var channelList = context.querySelector('.channelList'); + var channelList = context.querySelector('.channelsContainer'); channelList.innerHTML = html; imageLoader.lazyChildren(channelList); } diff --git a/dashboard-ui/bower_components/emby-webcomponents/htmlaudioplayer/blank.mp3 b/dashboard-ui/bower_components/emby-webcomponents/htmlaudioplayer/blank.mp3 new file mode 100644 index 0000000000..dbf05cbdd5 Binary files /dev/null and b/dashboard-ui/bower_components/emby-webcomponents/htmlaudioplayer/blank.mp3 differ diff --git a/dashboard-ui/bower_components/emby-webcomponents/htmlaudioplayer/plugin.js b/dashboard-ui/bower_components/emby-webcomponents/htmlaudioplayer/plugin.js new file mode 100644 index 0000000000..a2b411497e --- /dev/null +++ b/dashboard-ui/bower_components/emby-webcomponents/htmlaudioplayer/plugin.js @@ -0,0 +1,405 @@ +define(['events', 'browser', 'pluginManager', 'apphost', 'appSettings'], function (events, browser, pluginManager, appHost, appSettings) { + "use strict"; + + return function () { + + var self = this; + + self.name = 'Html Audio Player'; + self.type = 'mediaplayer'; + self.id = 'htmlaudioplayer'; + + // Let any players created by plugins take priority + self.priority = 1; + + var mediaElement; + var currentSrc; + + function getSavedVolume() { + return appSettings.get("volume") || 1; + } + + function saveVolume(value) { + if (value) { + appSettings.set("volume", value); + } + } + + self.canPlayMediaType = function (mediaType) { + + return (mediaType || '').toLowerCase() === 'audio'; + }; + + self.getDeviceProfile = function () { + + return new Promise(function (resolve, reject) { + + require(['browserdeviceprofile'], function (profileBuilder) { + + var profile = profileBuilder({ + }); + resolve(profile); + }); + }); + }; + + self.currentSrc = function () { + return currentSrc; + }; + + self.play = function (options) { + + _currentTime = null; + var elem = createMediaElement(); + + var val = options.url; + + elem.crossOrigin = getCrossOriginValue(options.mediaSource); + elem.title = options.title; + + // Opera TV guidelines suggest using source elements, so let's do that if we have a valid mimeType + if (options.mimeType && browser.operaTv) { + + // Need to do this or we won't be able to restart a new stream + if (elem.currentSrc) { + elem.src = ''; + elem.removeAttribute('src'); + } + + elem.innerHTML = ''; + } else { + elem.src = val; + } + + currentSrc = val; + + return playWithPromise(elem); + }; + + function playWithPromise(elem) { + + try { + var promise = elem.play(); + if (promise && promise.then) { + // Chrome now returns a promise + return promise.catch(function (e) { + + var errorName = (e.name || '').toLowerCase(); + // safari uses aborterror + if (errorName === 'notallowederror' || + errorName === 'aborterror') { + // swallow this error because the user can still click the play button on the video element + return Promise.resolve(); + } + return Promise.reject(); + }); + } else { + return Promise.resolve(); + } + } catch (err) { + console.log('error calling video.play: ' + err); + return Promise.reject(); + } + } + + function getCrossOriginValue(mediaSource) { + + return 'anonymous'; + } + + // Save this for when playback stops, because querying the time at that point might return 0 + var _currentTime; + self.currentTime = function (val) { + + if (mediaElement) { + if (val != null) { + mediaElement.currentTime = val / 1000; + return; + } + + if (_currentTime) { + return _currentTime * 1000; + } + + return (mediaElement.currentTime || 0) * 1000; + } + }; + + self.duration = function (val) { + + if (mediaElement) { + var duration = mediaElement.duration; + if (duration && !isNaN(duration) && duration !== Number.POSITIVE_INFINITY && duration !== Number.NEGATIVE_INFINITY) { + return duration * 1000; + } + } + + return null; + }; + + function supportsFade() { + + if (browser.tv) { + // Not working on tizen. + // We could possibly enable on other tv's, but all smart tv browsers tend to be pretty primitive + return false; + } + + return true; + } + + self.stop = function (destroyPlayer) { + + cancelFadeTimeout(); + + var elem = mediaElement; + var src = currentSrc; + + if (elem && src) { + + if (!destroyPlayer || !supportsFade()) { + + if (!elem.paused) { + elem.pause(); + } + elem.src = ''; + elem.innerHTML = ''; + elem.removeAttribute("src"); + onEnded(); + return Promise.resolve(); + } + + var originalVolume = elem.volume; + + return fade(elem, elem.volume).then(function () { + if (!elem.paused) { + elem.pause(); + } + elem.src = ''; + elem.innerHTML = ''; + elem.removeAttribute("src"); + + elem.volume = originalVolume; + onEnded(); + }); + } + return Promise.resolve(); + }; + + self.destroy = function () { + + }; + + var fadeTimeout; + + function fade(elem, startingVolume) { + + // Need to record the starting volume on each pass rather than querying elem.volume + // This is due to iOS safari not allowing volume changes and always returning the system volume value + + var newVolume = Math.max(0, startingVolume - 0.15); + console.log('fading volume to ' + newVolume); + elem.volume = newVolume; + + if (newVolume <= 0) { + return Promise.resolve(); + } + + return new Promise(function (resolve, reject) { + + cancelFadeTimeout(); + + fadeTimeout = setTimeout(function () { + fade(elem, newVolume).then(resolve, reject); + }, 100); + }); + } + + function cancelFadeTimeout() { + var timeout = fadeTimeout; + if (timeout) { + clearTimeout(timeout); + fadeTimeout = null; + } + } + + self.pause = function () { + if (mediaElement) { + mediaElement.pause(); + } + }; + + // This is a retry after error + self.resume = function () { + if (mediaElement) { + mediaElement.play(); + } + }; + + self.unpause = function () { + if (mediaElement) { + mediaElement.play(); + } + }; + + self.paused = function () { + + if (mediaElement) { + return mediaElement.paused; + } + + return false; + }; + + self.setVolume = function (val) { + if (mediaElement) { + mediaElement.volume = val / 100; + } + }; + + self.getVolume = function () { + if (mediaElement) { + return mediaElement.volume * 100; + } + }; + + self.volumeUp = function () { + self.setVolume(Math.min(self.getVolume() + 2, 100)); + }; + + self.volumeDown = function () { + self.setVolume(Math.max(self.getVolume() - 2, 0)); + }; + + self.setMute = function (mute) { + + if (mediaElement) { + mediaElement.muted = mute; + } + }; + + self.isMuted = function () { + if (mediaElement) { + return mediaElement.muted; + } + return false; + }; + + function onEnded() { + + var stopInfo = { + src: currentSrc + }; + + events.trigger(self, 'stopped', [stopInfo]); + + _currentTime = null; + currentSrc = null; + } + + function onTimeUpdate() { + + // Get the player position + the transcoding offset + var time = this.currentTime; + _currentTime = time; + events.trigger(self, 'timeupdate'); + } + + function onVolumeChange() { + + if (!fadeTimeout) { + saveVolume(this.volume); + events.trigger(self, 'volumechange'); + } + } + + function onPlaying() { + + events.trigger(self, 'playing'); + } + + function onPause() { + events.trigger(self, 'pause'); + } + + function onError() { + + var errorCode = this.error ? this.error.code : ''; + errorCode = (errorCode || '').toString(); + console.log('Media element error code: ' + errorCode); + + var type; + + switch (errorCode) { + case 1: + // MEDIA_ERR_ABORTED + // This will trigger when changing media while something is playing + return; + case 2: + // MEDIA_ERR_NETWORK + type = 'network'; + break; + case 3: + // MEDIA_ERR_DECODE + break; + case 4: + // MEDIA_ERR_SRC_NOT_SUPPORTED + break; + } + + //events.trigger(self, 'error', [ + //{ + // type: type + //}]); + } + + function createMediaElement() { + + var elem = document.querySelector('.mediaPlayerAudio'); + + if (!elem) { + elem = document.createElement('audio'); + elem.classList.add('mediaPlayerAudio'); + elem.classList.add('hide'); + + document.body.appendChild(elem); + + elem.volume = getSavedVolume(); + + elem.addEventListener('timeupdate', onTimeUpdate); + elem.addEventListener('ended', onEnded); + elem.addEventListener('volumechange', onVolumeChange); + elem.addEventListener('pause', onPause); + elem.addEventListener('playing', onPlaying); + elem.addEventListener('error', onError); + } + + mediaElement = elem; + + return elem; + } + + function onDocumentClick() { + document.removeEventListener('click', onDocumentClick); + + var elem = document.createElement('audio'); + elem.classList.add('mediaPlayerAudio'); + elem.classList.add('hide'); + + document.body.appendChild(elem); + + elem.src = pluginManager.mapPath(self, 'blank.mp3'); + elem.play(); + + setTimeout(function () { + elem.src = ''; + elem.removeAttribute("src"); + }, 1000); + } + + // Mobile browsers don't allow autoplay, so this is a nice workaround + if (!appHost.supports('htmlaudioautoplay')) { + document.addEventListener('click', onDocumentClick); + } + }; +}); \ No newline at end of file diff --git a/dashboard-ui/bower_components/emby-webcomponents/htmlvideoplayer/plugin.js b/dashboard-ui/bower_components/emby-webcomponents/htmlvideoplayer/plugin.js new file mode 100644 index 0000000000..819fddd689 --- /dev/null +++ b/dashboard-ui/bower_components/emby-webcomponents/htmlvideoplayer/plugin.js @@ -0,0 +1,1299 @@ +define(['browser', 'pluginManager', 'events', 'apphost', 'loading', 'playbackManager', 'embyRouter', 'appSettings', 'connectionManager'], function (browser, pluginManager, events, appHost, loading, playbackManager, embyRouter, appSettings, connectionManager) { + "use strict"; + + return function () { + + var self = this; + + self.name = 'Html Video Player'; + self.type = 'mediaplayer'; + self.id = 'htmlvideoplayer'; + + // Let any players created by plugins take priority + self.priority = 1; + + var mediaElement; + var videoDialog; + var currentSrc; + var started = false; + var hlsPlayer; + + var winJsPlaybackItem; + var currentPlayOptions; + + var subtitleTrackIndexToSetOnPlaying; + + var lastCustomTrackMs = 0; + var currentClock; + var currentAssRenderer; + var customTrackIndex = -1; + + self.canPlayMediaType = function (mediaType) { + + return (mediaType || '').toLowerCase() === 'video'; + }; + + function getSavedVolume() { + return appSettings.get("volume") || 1; + } + + function saveVolume(value) { + if (value) { + appSettings.set("volume", value); + } + } + + function getBaseProfileOptions(item) { + + var disableHlsVideoAudioCodecs = []; + if (!canPlayNativeHls() || (browser.edge && !item.RunTimeTicks)) { + + // this does not work with hls.js + edge, but seems to be fine in other browsers + if (browser.edge) { + disableHlsVideoAudioCodecs.push('mp3'); + } + + // hls.js does not support this + disableHlsVideoAudioCodecs.push('ac3'); + } + + var enableMkvProgressive = (item.RunTimeTicks && browser.edgeUwp) ? true : false; + + return { + enableMkvProgressive: enableMkvProgressive, + disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs + }; + } + + function getDeviceProfileForWindowsUwp(item) { + + return new Promise(function (resolve, reject) { + + require(['browserdeviceprofile', 'environments/windows-uwp/mediacaps'], function (profileBuilder, uwpMediaCaps) { + + var profileOptions = getBaseProfileOptions(item); + profileOptions.supportsDts = uwpMediaCaps.supportsDTS(); + profileOptions.supportsTrueHd = uwpMediaCaps.supportsDolby(); + profileOptions.audioChannels = uwpMediaCaps.getAudioChannels(); + + resolve(profileBuilder(profileOptions)); + }); + }); + } + + self.getDeviceProfile = function (item) { + + if (window.Windows) { + return getDeviceProfileForWindowsUwp(item); + } + + return new Promise(function (resolve, reject) { + + require(['browserdeviceprofile'], function (profileBuilder) { + + var profile = profileBuilder(getBaseProfileOptions(item)); + + if (!browser.edge && !browser.msie) { + // libjass not working here + profile.SubtitleProfiles.push({ + Format: 'ass', + Method: 'External' + }); + profile.SubtitleProfiles.push({ + Format: 'ssa', + Method: 'External' + }); + } + + resolve(profile); + }); + }); + }; + + self.currentSrc = function () { + return currentSrc; + }; + + function updateVideoUrl(streamInfo) { + + var isHls = streamInfo.url.toLowerCase().indexOf('.m3u8') !== -1; + + var mediaSource = streamInfo.mediaSource; + var item = streamInfo.item; + + // Huge hack alert. Safari doesn't seem to like if the segments aren't available right away when playback starts + // This will start the transcoding process before actually feeding the video url into the player + // Edit: Also seeing stalls from hls.js + if (mediaSource && item && !mediaSource.RunTimeTicks && isHls && (browser.iOS || browser.osx)) { + + var hlsPlaylistUrl = streamInfo.url.replace('master.m3u8', 'live.m3u8'); + + loading.show(); + + console.log('prefetching hls playlist: ' + hlsPlaylistUrl); + + return connectionManager.getApiClient(item.ServerId).ajax({ + + type: 'GET', + url: hlsPlaylistUrl + + }).then(function () { + + console.log('completed prefetching hls playlist: ' + hlsPlaylistUrl); + + loading.hide(); + streamInfo.url = hlsPlaylistUrl; + + return Promise.resolve(); + + }, function () { + + console.log('error prefetching hls playlist: ' + hlsPlaylistUrl); + + loading.hide(); + return Promise.resolve(); + }); + + } else { + return Promise.resolve(); + } + } + + self.play = function (options) { + + started = false; + _currentTime = null; + + return createMediaElement(options).then(function (elem) { + + return updateVideoUrl(options, options.mediaSource).then(function () { + return setCurrentSrc(elem, options); + }); + }); + }; + + var supportedFeatures; + function getSupportedFeatures() { + + var list = []; + + var video = document.createElement('video'); + //if (video.webkitSupportsPresentationMode && video.webkitSupportsPresentationMode('picture-in-picture') && typeof video.webkitSetPresentationMode === "function") { + // list.push('pictureinpicture'); + //} + if (browser.ipad) { + + // Unfortunately this creates a false positive on devices where its' not actually supported + if (navigator.userAgent.toLowerCase().indexOf('os 9') === -1) { + if (video.webkitSupportsPresentationMode && video.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === "function") { + list.push('pictureinpicture'); + } + } + } + + return list; + } + + self.supports = function (feature) { + + if (!supportedFeatures) { + supportedFeatures = getSupportedFeatures(); + } + + return supportedFeatures.indexOf(feature) !== -1; + }; + + self.togglePictureInPicture = function () { + return self.setPictureInPictureEnabled(!self.isPictureInPictureEnabled()); + }; + + self.setPictureInPictureEnabled = function (isEnabled) { + + var video = mediaElement; + if (video) { + if (video.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === "function") { + video.webkitSetPresentationMode(isEnabled ? "picture-in-picture" : "inline"); + } + } + }; + + self.isPictureInPictureEnabled = function (isEnabled) { + + var video = mediaElement; + if (video) { + return video.webkitPresentationMode === "picture-in-picture"; + } + + return false; + }; + + function getCrossOriginValue(mediaSource) { + + return 'anonymous'; + } + + function requireHlsPlayer(callback) { + require(['hlsjs'], function (hls) { + window.Hls = hls; + callback(); + }); + } + + function setCurrentSrc(elem, options) { + + //if (!elem) { + // currentSrc = null; + // resolve(); + // return; + //} + + //if (!options) { + // currentSrc = null; + // elem.src = null; + // elem.src = ""; + + // // When the browser regains focus it may start auto-playing the last video + // //if ($.browser.safari) { + // // elem.src = 'files/dummy.mp4'; + // // elem.play(); + // //} + + // resolve(); + // return; + //} + + var val = options.url; + + console.log('playing url: ' + val); + + //if (AppInfo.isNativeApp && $.browser.safari) { + // val = val.replace('file://', ''); + //} + + // Convert to seconds + var seconds = (options.playerStartPositionTicks || 0) / 10000000; + if (seconds) { + val += '#t=' + seconds; + } + + destroyHlsPlayer(); + + var tracks = getMediaStreamTextTracks(options.mediaSource); + + var currentTrackIndex = -1; + for (var i = 0, length = tracks.length; i < length; i++) { + if (tracks[i].Index === options.mediaSource.DefaultSubtitleStreamIndex) { + currentTrackIndex = tracks[i].Index; + break; + } + } + subtitleTrackIndexToSetOnPlaying = currentTrackIndex; + + currentPlayOptions = options; + + elem.crossOrigin = getCrossOriginValue(options.mediaSource); + + if (enableHlsPlayer(val, options.item, options.mediaSource)) { + + setTracks(elem, tracks, options.mediaSource, options.item.ServerId); + + return new Promise(function (resolve, reject) { + + requireHlsPlayer(function () { + var hls = new Hls({ + manifestLoadingTimeOut: 20000 + //appendErrorMaxRetry: 6, + //debug: true + }); + hls.loadSource(val); + hls.attachMedia(elem); + hls.on(Hls.Events.MANIFEST_PARSED, function () { + playWithPromise(elem).then(resolve, reject); + }); + + hls.on(Hls.Events.ERROR, function (event, data) { + + console.log('HLS Error: Type: ' + data.type + ' Details: ' + (data.details || '') + ' Fatal: ' + (data.fatal || false)); + + if (data.fatal) { + switch (data.type) { + case Hls.ErrorTypes.NETWORK_ERROR: + // try to recover network error + console.log("fatal network error encountered, try to recover"); + hls.startLoad(); + break; + case Hls.ErrorTypes.MEDIA_ERROR: + console.log("fatal media error encountered, try to recover"); + handleMediaError(); + break; + default: + // cannot recover + hls.destroy(); + break; + } + } + }); + + hlsPlayer = hls; + + // This is needed in setCurrentTrackElement + currentSrc = val; + + setCurrentTrackElement(currentTrackIndex); + }); + }); + + } else { + + elem.autoplay = true; + var mimeType = options.mimeType; + + // Opera TV guidelines suggest using source elements, so let's do that if we have a valid mimeType + if (mimeType && browser.operaTv) { + + if (browser.chrome && mimeType === 'video/x-matroska') { + mimeType = 'video/webm'; + } + + // Need to do this or we won't be able to restart a new stream + if (elem.currentSrc) { + elem.src = ''; + elem.removeAttribute('src'); + } + + elem.innerHTML = '' + getTracksHtml(tracks, options.mediaSource, options.item.ServerId); + + elem.addEventListener('loadedmetadata', onLoadedMetadata); + } else { + applySrc(elem, val); + setTracks(elem, tracks, options.mediaSource, options.item.ServerId); + } + + // This is needed in setCurrentTrackElement + currentSrc = val; + + setCurrentTrackElement(currentTrackIndex); + return playWithPromise(elem); + } + } + + var recoverDecodingErrorDate, recoverSwapAudioCodecDate; + + function handleMediaError() { + + if (!hlsPlayer) { + return; + } + + var now = Date.now(); + + if (window.performance && window.performance.now) { + now = performance.now(); + } + + if (!recoverDecodingErrorDate || (now - recoverDecodingErrorDate) > 3000) { + recoverDecodingErrorDate = now; + console.log('try to recover media Error ...'); + hlsPlayer.recoverMediaError(); + } else { + if (!recoverSwapAudioCodecDate || (now - recoverSwapAudioCodecDate) > 3000) { + recoverSwapAudioCodecDate = now; + console.log('try to swap Audio Codec and recover media Error ...'); + hlsPlayer.swapAudioCodec(); + hlsPlayer.recoverMediaError(); + } else { + console.error('cannot recover, last media error recovery failed ...'); + } + } + } + + function applySrc(elem, src) { + + if (window.Windows) { + + var playlist = new Windows.Media.Playback.MediaPlaybackList(); + var source1 = Windows.Media.Core.MediaSource.createFromUri(new Windows.Foundation.Uri(src)); + + winJsPlaybackItem = new Windows.Media.Playback.MediaPlaybackItem(source1); + playlist.items.append(winJsPlaybackItem); + + elem.src = URL.createObjectURL(playlist, { oneTimeOnly: true }); + + } else { + + elem.src = src; + } + } + + function playWithPromise(elem) { + + try { + var promise = elem.play(); + if (promise && promise.then) { + // Chrome now returns a promise + return promise.catch(function (e) { + + var errorName = (e.name || '').toLowerCase(); + // safari uses aborterror + if (errorName === 'notallowederror' || + errorName === 'aborterror') { + // swallow this error because the user can still click the play button on the video element + return Promise.resolve(); + } + return Promise.reject(); + }); + } else { + return Promise.resolve(); + } + } catch (err) { + console.log('error calling video.play: ' + err); + return Promise.reject(); + } + } + + self.setSubtitleStreamIndex = function (index) { + + setCurrentTrackElement(index); + }; + + self.canSetAudioStreamIndex = function () { + + if (winJsPlaybackItem) { + return true; + } + + return false; + }; + + self.setAudioStreamIndex = function (index) { + + var audioTracks = getMediaStreamAudioTracks(currentPlayOptions.mediaSource); + + var track = audioTracks.filter(function (t) { + return t.Index === index; + })[0]; + + if (!track) { + return; + } + + if (winJsPlaybackItem) { + var audioIndex = audioTracks.indexOf(track); + winJsPlaybackItem.audioTracks.selectedIndex = audioIndex; + } + }; + + // Save this for when playback stops, because querying the time at that point might return 0 + var _currentTime; + self.currentTime = function (val) { + + if (mediaElement) { + if (val != null) { + mediaElement.currentTime = val / 1000; + return; + } + + if (_currentTime) { + return _currentTime * 1000; + } + + return (mediaElement.currentTime || 0) * 1000; + } + }; + + self.duration = function (val) { + + if (mediaElement) { + var duration = mediaElement.duration; + if (duration && !isNaN(duration) && duration !== Number.POSITIVE_INFINITY && duration !== Number.NEGATIVE_INFINITY) { + return duration * 1000; + } + } + + return null; + }; + + self.stop = function (destroyPlayer, reportEnded) { + + var elem = mediaElement; + var src = currentSrc; + + if (elem && src) { + + elem.pause(); + + elem.src = ''; + elem.innerHTML = ''; + elem.removeAttribute("src"); + + destroyHlsPlayer(); + + onEndedInternal(reportEnded); + + if (destroyPlayer) { + self.destroy(); + } + } + + destroyCustomTrack(elem); + + return Promise.resolve(); + }; + + self.destroy = function () { + + destroyHlsPlayer(); + embyRouter.setTransparency('none'); + + var videoElement = mediaElement; + + if (videoElement) { + + mediaElement = null; + + destroyCustomTrack(videoElement); + + videoElement.removeEventListener('timeupdate', onTimeUpdate); + videoElement.removeEventListener('ended', onEnded); + videoElement.removeEventListener('volumechange', onVolumeChange); + videoElement.removeEventListener('pause', onPause); + videoElement.removeEventListener('playing', onPlaying); + videoElement.removeEventListener('error', onError); + videoElement.removeEventListener('loadedmetadata', onLoadedMetadata); + videoElement.removeEventListener('click', onClick); + videoElement.removeEventListener('dblclick', onDblClick); + + videoElement.parentNode.removeChild(videoElement); + } + + var dlg = videoDialog; + if (dlg) { + + videoDialog = null; + + dlg.parentNode.removeChild(dlg); + } + }; + + function destroyHlsPlayer() { + var player = hlsPlayer; + if (player) { + try { + player.destroy(); + } catch (err) { + console.log(err); + } + + hlsPlayer = null; + } + } + + self.pause = function () { + if (mediaElement) { + mediaElement.pause(); + } + }; + + // This is a retry after error + self.resume = function () { + if (mediaElement) { + mediaElement.play(); + } + }; + + self.unpause = function () { + if (mediaElement) { + mediaElement.play(); + } + }; + + self.paused = function () { + + if (mediaElement) { + return mediaElement.paused; + } + + return false; + }; + + self.setVolume = function (val) { + if (mediaElement) { + mediaElement.volume = val / 100; + } + }; + + self.getVolume = function () { + if (mediaElement) { + return mediaElement.volume * 100; + } + }; + + self.volumeUp = function () { + self.setVolume(Math.min(self.getVolume() + 2, 100)); + }; + + self.volumeDown = function () { + self.setVolume(Math.max(self.getVolume() - 2, 0)); + }; + + self.setMute = function (mute) { + + if (mediaElement) { + mediaElement.muted = mute; + } + }; + + self.isMuted = function () { + if (mediaElement) { + return mediaElement.muted; + } + return false; + }; + + function onEnded() { + + destroyCustomTrack(this); + onEndedInternal(true); + } + + function onEndedInternal(triggerEnded) { + + if (self.originalDocumentTitle) { + document.title = self.originalDocumentTitle; + self.originalDocumentTitle = null; + } + + if (triggerEnded) { + + var stopInfo = { + src: currentSrc + }; + + events.trigger(self, 'stopped', [stopInfo]); + + _currentTime = null; + } + + currentSrc = null; + winJsPlaybackItem = null; + } + + function onTimeUpdate(e) { + + // Get the player position + the transcoding offset + var time = this.currentTime; + _currentTime = time; + var timeMs = time * 1000; + timeMs += ((currentPlayOptions.transcodingOffsetTicks || 0) / 10000); + updateSubtitleText(timeMs); + + events.trigger(self, 'timeupdate'); + } + + function onVolumeChange() { + + saveVolume(this.volume); + events.trigger(self, 'volumechange'); + } + + function onNavigatedToOsd() { + + videoDialog.classList.remove('videoPlayerContainer-withBackdrop'); + videoDialog.classList.remove('videoPlayerContainer-onTop'); + } + + function onPlaying(e) { + + if (!started) { + started = true; + this.removeAttribute('controls'); + + if (currentPlayOptions.title) { + self.originalDocumentTitle = document.title; + document.title = currentPlayOptions.title; + } else { + self.originalDocumentTitle = null; + } + + setCurrentTrackElement(subtitleTrackIndexToSetOnPlaying); + + seekOnPlaybackStart(e.target); + + if (currentPlayOptions.fullscreen) { + + embyRouter.showVideoOsd().then(onNavigatedToOsd); + + } else { + embyRouter.setTransparency('backdrop'); + videoDialog.classList.remove('videoPlayerContainer-withBackdrop'); + videoDialog.classList.remove('videoPlayerContainer-onTop'); + } + + loading.hide(); + } + events.trigger(self, 'playing'); + } + + function seekOnPlaybackStart(element) { + + var seconds = (currentPlayOptions.playerStartPositionTicks || 0) / 10000000; + + if (seconds) { + var src = (self.currentSrc() || '').toLowerCase(); + + // Appending #t=xxx to the query string doesn't seem to work with HLS + // For plain video files, not all browsers support it either + if (!browser.chrome || src.indexOf('.m3u8') !== -1) { + + var delay = browser.safari ? 2500 : 0; + if (delay) { + setTimeout(function () { + element.currentTime = seconds; + }, delay); + } else { + element.currentTime = seconds; + } + } + } + } + + function onClick() { + events.trigger(self, 'click'); + } + + function onDblClick() { + events.trigger(self, 'dblclick'); + } + + function onPause() { + events.trigger(self, 'pause'); + } + + function onError() { + + var errorCode = this.error ? (this.error.code || 0) : 0; + console.log('Media element error code: ' + errorCode.toString()); + + var type; + + switch (errorCode) { + case 1: + // MEDIA_ERR_ABORTED + // This will trigger when changing media while something is playing + return; + case 2: + // MEDIA_ERR_NETWORK + type = 'network'; + break; + case 3: + // MEDIA_ERR_DECODE + handleMediaError(); + return; + case 4: + // MEDIA_ERR_SRC_NOT_SUPPORTED + break; + } + + destroyCustomTrack(this); + + //events.trigger(self, 'error', [ + //{ + // type: type + //}]); + } + + function onLoadedMetadata(e) { + + var mediaElem = e.target; + mediaElem.removeEventListener('loadedmetadata', onLoadedMetadata); + + if (!hlsPlayer) { + + try { + mediaElem.play(); + } catch (err) { + console.log('error calling mediaElement.play: ' + err); + } + } + } + + function enableHlsPlayer(src, item, mediaSource) { + + if (src) { + if (src.indexOf('.m3u8') === -1) { + return false; + } + } + + if (window.MediaSource == null) { + return false; + } + + if (canPlayNativeHls()) { + + // simple playback should use the native support + if (mediaSource.RunTimeTicks) { + //if (!browser.edge) { + return false; + //} + } + + //return false; + } + + // hls.js is only in beta. needs more testing. + if (browser.safari && !browser.osx) { + return false; + } + + return true; + } + + function canPlayNativeHls() { + var media = document.createElement('video'); + + if (media.canPlayType('application/x-mpegURL').replace(/no/, '') || + media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, '')) { + return true; + } + + return false; + } + + function setTracks(elem, tracks, mediaSource, serverId) { + + elem.innerHTML = getTracksHtml(tracks, mediaSource, serverId); + } + + function getTextTrackUrl(track, serverId) { + return playbackManager.getSubtitleUrl(track, serverId); + } + + function getTracksHtml(tracks, mediaSource, serverId) { + return tracks.map(function (t) { + + var defaultAttribute = mediaSource.DefaultSubtitleStreamIndex === t.Index ? ' default' : ''; + + var language = t.Language || 'und'; + var label = t.Language || 'und'; + return ''; + + }).join(''); + } + + var _supportsTextTracks; + + function supportsTextTracks() { + + if (_supportsTextTracks == null) { + _supportsTextTracks = document.createElement('video').textTracks != null; + } + + // For now, until ready + return _supportsTextTracks; + } + + function enableNativeTrackSupport(track) { + + if (browser.firefox) { + if ((currentSrc || '').toLowerCase().indexOf('.m3u8') !== -1) { + return false; + } + } + + if (browser.ps4) { + return false; + } + + if (track) { + var format = (track.Codec || '').toLowerCase(); + if (format === 'ssa' || format === 'ass') { + // libjass is needed here + return false; + } + } + + return true; + } + + function destroyCustomTrack(videoElement, isPlaying) { + + window.removeEventListener('resize', onVideoResize); + window.removeEventListener('orientationchange', onVideoResize); + + if (isPlaying) { + + var allTracks = mediaElement.textTracks; // get list of tracks + for (var i = 0; i < allTracks.length; i++) { + + var currentTrack = allTracks[i]; + + if (currentTrack.label.indexOf('manualTrack') !== -1) { + currentTrack.mode = 'disabled'; + } + } + } + + customTrackIndex = -1; + currentClock = null; + + var renderer = currentAssRenderer; + if (renderer) { + renderer.setEnabled(false); + } + currentAssRenderer = null; + } + + function fetchSubtitles(track, serverId) { + + return new Promise(function (resolve, reject) { + + require(['fetchHelper'], function (fetchHelper) { + fetchHelper.ajax({ + url: getTextTrackUrl(track, serverId).replace('.vtt', '.js'), + type: 'GET', + dataType: 'json' + }).then(resolve, reject); + }); + }); + } + + function setTrackForCustomDisplay(videoElement, track) { + + if (!track) { + destroyCustomTrack(videoElement, true); + return; + } + + // if already playing thids track, skip + if (customTrackIndex === track.Index) { + return; + } + + var serverId = currentPlayOptions.item.ServerId; + + destroyCustomTrack(videoElement, true); + customTrackIndex = track.Index; + renderTracksEvents(videoElement, track, serverId); + lastCustomTrackMs = 0; + } + + function renderWithLibjass(videoElement, track, serverId) { + + var rendererSettings = {}; + + require(['libjass'], function (libjass) { + + libjass.ASS.fromUrl(getTextTrackUrl(track, serverId)).then(function (ass) { + + var clock = new libjass.renderers.ManualClock(); + currentClock = clock; + + // Create a DefaultRenderer using the video element and the ASS object + var renderer = new libjass.renderers.WebRenderer(ass, clock, videoElement.parentNode, rendererSettings); + + currentAssRenderer = renderer; + + renderer.addEventListener("ready", function () { + try { + renderer.resize(videoElement.offsetWidth, videoElement.offsetHeight, 0, 0); + window.removeEventListener('resize', onVideoResize); + window.addEventListener('resize', onVideoResize); + window.removeEventListener('orientationchange', onVideoResize); + window.addEventListener('orientationchange', onVideoResize); + //clock.pause(); + } catch (ex) { + } + }); + }); + }); + } + + function onVideoResize() { + var renderer = currentAssRenderer; + if (renderer) { + var videoElement = mediaElement; + var width = videoElement.offsetWidth; + var height = videoElement.offsetHeight; + console.log('videoElement resized: ' + width + 'x' + height); + renderer.resize(width, height, 0, 0); + } + } + + function renderTracksEvents(videoElement, track, serverId) { + + var format = (track.Codec || '').toLowerCase(); + if (format === 'ssa' || format === 'ass') { + // libjass is needed here + renderWithLibjass(videoElement, track, serverId); + return; + } + + var trackElement = null; + var expectedId = 'manualTrack' + track.Index; + + var allTracks = videoElement.textTracks; // get list of tracks + for (var i = 0; i < allTracks.length; i++) { + + var currentTrack = allTracks[i]; + + if (currentTrack.label === expectedId) { + trackElement = currentTrack; + break; + } else { + currentTrack.mode = 'disabled'; + } + } + + if (!trackElement) { + trackElement = videoElement.addTextTrack('subtitles', 'manualTrack' + track.Index, track.Language || 'und'); + + // download the track json + fetchSubtitles(track, serverId).then(function (data) { + + // show in ui + console.log('downloaded ' + data.TrackEvents.length + ' track events'); + // add some cues to show the text + // in safari, the cues need to be added before setting the track mode to showing + data.TrackEvents.forEach(function (trackEvent) { + + var trackCueObject = window.VTTCue || window.TextTrackCue; + var cue = new trackCueObject(trackEvent.StartPositionTicks / 10000000, trackEvent.EndPositionTicks / 10000000, trackEvent.Text.replace(/\\N/gi, '\n')); + + trackElement.addCue(cue); + }); + trackElement.mode = 'showing'; + }); + } else { + trackElement.mode = 'showing'; + } + } + + function updateSubtitleText(timeMs) { + + var clock = currentClock; + if (clock) { + clock.seek(timeMs / 1000); + } + } + + function getMediaStreamAudioTracks(mediaSource) { + + return mediaSource.MediaStreams.filter(function (s) { + return s.Type === 'Audio'; + }); + } + + function getMediaStreamTextTracks(mediaSource) { + + return mediaSource.MediaStreams.filter(function (s) { + return s.Type === 'Subtitle' && s.DeliveryMethod === 'External'; + }); + } + + function setCurrentTrackElement(streamIndex) { + + console.log('Setting new text track index to: ' + streamIndex); + + var mediaStreamTextTracks = getMediaStreamTextTracks(currentPlayOptions.mediaSource); + + var track = streamIndex === -1 ? null : mediaStreamTextTracks.filter(function (t) { + return t.Index === streamIndex; + })[0]; + + if (enableNativeTrackSupport(track)) { + + setTrackForCustomDisplay(mediaElement, null); + } else { + setTrackForCustomDisplay(mediaElement, track); + + // null these out to disable the player's native display (handled below) + streamIndex = -1; + track = null; + } + + var expectedId = 'textTrack' + streamIndex; + var trackIndex = streamIndex === -1 || !track ? -1 : mediaStreamTextTracks.indexOf(track); + var modes = ['disabled', 'showing', 'hidden']; + + var allTracks = mediaElement.textTracks; // get list of tracks + for (var i = 0; i < allTracks.length; i++) { + + var currentTrack = allTracks[i]; + + console.log('currentTrack id: ' + currentTrack.id); + + var mode; + + console.log('expectedId: ' + expectedId + '--currentTrack.Id:' + currentTrack.id); + + // IE doesn't support track id + if (browser.msie || browser.edge) { + if (trackIndex === i) { + mode = 1; // show this track + } else { + mode = 0; // hide all other tracks + } + } else { + + if (currentTrack.label.indexOf('manualTrack') !== -1) { + continue; + } + if (currentTrack.id === expectedId) { + mode = 1; // show this track + } else { + mode = 0; // hide all other tracks + } + } + + console.log('Setting track ' + i + ' mode to: ' + mode); + + // Safari uses integers for the mode property + // http://www.jwplayer.com/html5/scripting/ + // edit: not anymore + var useNumericMode = false; + + if (!isNaN(currentTrack.mode)) { + //useNumericMode = true; + } + + if (useNumericMode) { + currentTrack.mode = mode; + } else { + currentTrack.mode = modes[mode]; + } + } + } + + function updateTextStreamUrls(startPositionTicks) { + + if (!supportsTextTracks()) { + return; + } + + var allTracks = mediaElement.textTracks; // get list of tracks + var i; + var track; + + for (i = 0; i < allTracks.length; i++) { + + track = allTracks[i]; + + // This throws an error in IE, but is fine in chrome + // In IE it's not necessary anyway because changing the src seems to be enough + try { + while (track.cues.length) { + track.removeCue(track.cues[0]); + } + } catch (e) { + console.log('Error removing cue from textTrack'); + } + } + + var tracks = mediaElement.querySelectorAll('track'); + for (i = 0; i < tracks.length; i++) { + + track = tracks[i]; + + track.src = replaceQueryString(track.src, 'startPositionTicks', startPositionTicks); + } + } + + function zoomIn(elem, iterations) { + var keyframes = [ + { transform: 'scale3d(.2, .2, .2) ', opacity: '.6', offset: 0 }, + { transform: 'none', opacity: '1', offset: 1 } + ]; + + var timing = { duration: 240, iterations: iterations }; + return elem.animate(keyframes, timing); + } + + function createMediaElement(options) { + + if (browser.tv || browser.noAnimation || browser.iOS) { + // too slow + // also on iOS, the backdrop image doesn't look right + options.backdropUrl = null; + } + return new Promise(function (resolve, reject) { + + var dlg = document.querySelector('.videoPlayerContainer'); + + if (!dlg) { + + require(['css!' + pluginManager.mapPath(self, 'style.css')], function () { + + loading.show(); + + var dlg = document.createElement('div'); + + dlg.classList.add('videoPlayerContainer'); + + if (options.backdropUrl) { + + dlg.classList.add('videoPlayerContainer-withBackdrop'); + dlg.style.backgroundImage = "url('" + options.backdropUrl + "')"; + } + + if (options.fullscreen) { + dlg.classList.add('videoPlayerContainer-onTop'); + } + + // playsinline new for iOS 10 + // https://developer.apple.com/library/content/releasenotes/General/WhatsNewInSafari/Articles/Safari_10_0.html + + var html = ''; + // Can't autoplay in these browsers so we need to use the full controls, at least until playback starts + if (!appHost.supports('htmlvideoautoplay')) { + html += '