diff --git a/dashboard-ui/apiclient/apiclient.js b/dashboard-ui/apiclient/apiclient.js index 9ca03640b4..6c5e0f96be 100644 --- a/dashboard-ui/apiclient/apiclient.js +++ b/dashboard-ui/apiclient/apiclient.js @@ -398,7 +398,7 @@ self.openWebSocket = function () { - var accessToken = self.serverInfo().AccessToken; + var accessToken = self.accessToken(); if (!accessToken) { throw new Error("Cannot open web socket without access token."); @@ -653,7 +653,7 @@ self.setAuthenticationInfo(null, null); }; - if (self.serverInfo().AccessToken) { + if (self.accessToken()) { var url = self.getUrl("Sessions/Logout"); return self.ajax({ @@ -663,8 +663,9 @@ } var deferred = DeferredBuilder.Deferred(); + done(); deferred.resolveWith(null, []); - return deferred.promise().always(done); + return deferred.promise(); }; function getRemoteImagePrefix(options) { @@ -2222,6 +2223,25 @@ }); }; + /** + * Gets a user by id + * @param {String} id + */ + self.getOfflineUser = function (id) { + + if (!id) { + throw new Error("Must supply a userId"); + } + + var url = self.getUrl("Users/" + id + "/Offline"); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + /** * Gets a studio */ diff --git a/dashboard-ui/apiclient/connectionmanager.js b/dashboard-ui/apiclient/connectionmanager.js index 924bfb112a..8fefd279e7 100644 --- a/dashboard-ui/apiclient/connectionmanager.js +++ b/dashboard-ui/apiclient/connectionmanager.js @@ -322,12 +322,12 @@ function saveUserInfoIntoCredentials(server, user) { - //ServerUserInfo info = new ServerUserInfo(); - //info.setIsSignedInOffline(true); - //info.setId(user.getId()); + var info = new { + Id: user.Id, + IsSignedInOffline: true + } - //// Record user info here - //server.AddOrUpdate(info); + credentialProvider.addOrUpdateUser(server, info); } function afterConnected(apiClient, options) { diff --git a/dashboard-ui/apiclient/credentials.js b/dashboard-ui/apiclient/credentials.js index f379e142a7..82abd6e6aa 100644 --- a/dashboard-ui/apiclient/credentials.js +++ b/dashboard-ui/apiclient/credentials.js @@ -13,7 +13,7 @@ function ensure() { if (!credentials) { - + var json = appStorage.getItem(key) || '{}'; Logger.log('credentials initialized with: ' + json); @@ -108,6 +108,24 @@ return server; } }; + + self.addOrUpdateUser = function (server, user) { + + server.Users = server.Users || []; + + var existing = server.Users.filter(function (s) { + return s.Id == user.Id; + })[0]; + + if (existing) { + + // Merge the data + existing.IsSignedInOffline = true; + } + else { + server.Users.push(user); + } + }; }; })(window, window.JSON); \ No newline at end of file diff --git a/dashboard-ui/apiclient/fileupload.js b/dashboard-ui/apiclient/fileupload.js new file mode 100644 index 0000000000..36c2f995f6 --- /dev/null +++ b/dashboard-ui/apiclient/fileupload.js @@ -0,0 +1,23 @@ +(function (globalScope) { + + function fileUpload() { + + var self = this; + + self.upload = function (file, name, url) { + + var deferred = DeferredBuilder.Deferred(); + + deferred.reject(); + + return deferred.promise(); + }; + } + + if (!globalScope.MediaBrowser) { + globalScope.MediaBrowser = {}; + } + + globalScope.MediaBrowser.FileUpload = fileUpload; + +})(this); \ No newline at end of file diff --git a/dashboard-ui/apiclient/localassetmanager.js b/dashboard-ui/apiclient/localassetmanager.js index 749e5286f3..fe948c9046 100644 --- a/dashboard-ui/apiclient/localassetmanager.js +++ b/dashboard-ui/apiclient/localassetmanager.js @@ -4,8 +4,22 @@ return null; } + function saveOfflineUser(user) { + var deferred = DeferredBuilder.Deferred(); + deferred.resolve(); + return deferred.promise(); + } + + function getCameraPhotos() { + var deferred = DeferredBuilder.Deferred(); + deferred.resolveWith(null, [[]]); + return deferred.promise(); + } + window.LocalAssetManager = { - getLocalMediaSource: getLocalMediaSource + getLocalMediaSource: getLocalMediaSource, + saveOfflineUser: saveOfflineUser, + getCameraPhotos: getCameraPhotos }; })(); \ No newline at end of file diff --git a/dashboard-ui/apiclient/sync/contentuploader.js b/dashboard-ui/apiclient/sync/contentuploader.js index aa1a6c45b7..f11559e5c9 100644 --- a/dashboard-ui/apiclient/sync/contentuploader.js +++ b/dashboard-ui/apiclient/sync/contentuploader.js @@ -40,12 +40,93 @@ function uploadImagesWithHistory(server, uploadHistory, apiClient, deferred) { - require(['localassetmanager'], function () { + require(['localassetmanager', "cryptojs-sha1"], function () { - // TODO: Mimic java version of ContentUploader.UploadImagesInternal - deferred.resolve(); + LocalAssetManager.getCameraPhotos().done(function (photos) { + + photos = getFilesToUpload(photos, uploadHistory); + + Logger.log('Found ' + photos.length + ' files to upload'); + + uploadNext(photos, 0, server, apiClient, deferred); + + }).fail(function () { + deferred.reject(); + }); }); } + + function getFilesToUpload(files, uploadHistory) { + + return files.filter(function (file) { + + // Seeing some null entries for some reason + if (!file) { + return false; + } + + return uploadHistory.FilesUploaded.filter(function (u) { + + return getUploadId(file) == u.Id; + + }).length == 0; + }); + } + + function getUploadId(file) { + return CryptoJS.SHA1(file + "1").toString(); + } + + function uploadNext(files, index, server, apiClient, deferred) { + + var length = files.length; + + if (index >= length) { + + deferred.resolve(); + return; + } + + uploadFile(files[index], apiClient).done(function () { + + uploadNext(files, index + 1, server, apiClient, deferred); + }).fail(function () { + uploadNext(files, index + 1, server, apiClient, deferred); + }); + } + + function uploadFile(file, apiClient) { + + var deferred = DeferredBuilder.Deferred(); + + require(['fileupload', "cryptojs-sha1"], function () { + + var name = 'camera image ' + new Date().getTime(); + + var url = apiClient.getUrl('Devices/CameraUploads', { + DeviceId: apiClient.deviceId(), + Name: name, + Album: 'Camera Roll', + Id: getUploadId(file), + api_key: apiClient.accessToken() + }); + + Logger.log('Uploading file to ' + url); + + new MediaBrowser.FileUpload().upload(file, name, url).done(function () { + + Logger.log('File upload succeeded'); + deferred.resolve(); + + }).fail(function () { + + Logger.log('File upload failed'); + deferred.reject(); + }); + }); + + return deferred.promise(); + } } if (!globalScope.MediaBrowser) { diff --git a/dashboard-ui/apiclient/sync/offlineusersync.js b/dashboard-ui/apiclient/sync/offlineusersync.js index 2db47b06ea..8102d6db93 100644 --- a/dashboard-ui/apiclient/sync/offlineusersync.js +++ b/dashboard-ui/apiclient/sync/offlineusersync.js @@ -4,14 +4,56 @@ var self = this; - self.sync = function (apiClient) { + self.sync = function (apiClient, server) { var deferred = DeferredBuilder.Deferred(); - deferred.resolve(); + var users = server.Users || []; + syncNext(users, 0, deferred, apiClient, server); return deferred.promise(); }; + + function syncNext(users, index, deferred, apiClient, server) { + + var length = users.length; + + if (index >= length) { + + deferred.resolve(); + return; + } + + syncUser(users[index], apiClient).done(function () { + + syncNext(users, index + 1, deferred, apiClient, server); + }).fail(function () { + syncNext(users, index + 1, deferred, apiClient, server); + }); + } + + function syncUser(user, apiClient) { + + var deferred = DeferredBuilder.Deferred(); + + apiClient.getOfflineUser(user.Id).done(function (result) { + + require(['localassetmanager'], function () { + + LocalAssetManager.saveOfflineUser(result).done(function () { + deferred.resolve(); + }).fail(function () { + deferred.resolve(); + }); + }); + + }).fail(function () { + deferred.reject(); + }); + + return deferred.promise(); + } + } if (!globalScope.MediaBrowser) { diff --git a/dashboard-ui/apiclient/sync/serversync.js b/dashboard-ui/apiclient/sync/serversync.js index 40016a12b6..f212503229 100644 --- a/dashboard-ui/apiclient/sync/serversync.js +++ b/dashboard-ui/apiclient/sync/serversync.js @@ -12,7 +12,7 @@ Logger.log('Skipping sync to server ' + server.Id + ' because there is no saved authentication information.'); deferred.resolve(); - return; + return deferred.promise(); } var connectionOptions = { @@ -66,7 +66,7 @@ var apiClient = connectionManager.getApiClient(server.Id); - new MediaBrowser.OfflineUserSync().sync(apiClient).done(function () { + new MediaBrowser.OfflineUserSync().sync(apiClient, server).done(function () { Logger.log("OfflineUserSync succeeded to server: " + server.Id); diff --git a/dashboard-ui/cordova/android/localassetmanager.js b/dashboard-ui/cordova/android/localassetmanager.js index abba977519..043f660837 100644 --- a/dashboard-ui/cordova/android/localassetmanager.js +++ b/dashboard-ui/cordova/android/localassetmanager.js @@ -10,8 +10,16 @@ return null; } + function getCameraPhotos() { + var deferred = DeferredBuilder.Deferred(); + deferred.resolveWith(null, [[]]); + return deferred.promise(); + } + window.LocalAssetManager = { - getLocalMediaSource: getLocalMediaSource + getLocalMediaSource: getLocalMediaSource, + saveOfflineUser: saveOfflineUser, + getCameraPhotos: getCameraPhotos }; })(); \ No newline at end of file diff --git a/dashboard-ui/cordova/fileupload.js b/dashboard-ui/cordova/fileupload.js new file mode 100644 index 0000000000..c44b33927f --- /dev/null +++ b/dashboard-ui/cordova/fileupload.js @@ -0,0 +1,44 @@ +(function (globalScope) { + + function fileUpload() { + + var self = this; + + self.upload = function (file, name, url) { + + var deferred = DeferredBuilder.Deferred(); + + var onSuccess = function (r) { + console.log("Code = " + r.responseCode); + console.log("Response = " + r.response); + console.log("Sent = " + r.bytesSent); + deferred.resolve(); + } + + var onFail = function (error) { + console.log("upload error source " + error.source); + console.log("upload error target " + error.target); + deferred.reject(); + } + + var options = new FileUploadOptions(); + options.fileKey = "file"; + options.fileName = name; + options.mimeType = 'image/jpg'; + + var params = {}; + options.params = params; + + new FileTransfer().upload(file, url, onSuccess, onFail, options); + + return deferred.promise(); + }; + } + + if (!globalScope.MediaBrowser) { + globalScope.MediaBrowser = {}; + } + + globalScope.MediaBrowser.FileUpload = fileUpload; + +})(this); \ No newline at end of file diff --git a/dashboard-ui/cordova/ios/vlcplayer.js b/dashboard-ui/cordova/ios/vlcplayer.js index 97679269c8..898f4efd3c 100644 --- a/dashboard-ui/cordova/ios/vlcplayer.js +++ b/dashboard-ui/cordova/ios/vlcplayer.js @@ -4,38 +4,41 @@ var self = this; + // Need to use this to fire events because the iOS vlc callbacks always go to the first instance + window.AudioRenderer.Current = self; + self.enableProgressReporting = options.type == 'audio'; function onEnded() { - Events.trigger(self, 'ended'); + Events.trigger(window.AudioRenderer.Current, 'ended'); } function onTimeUpdate() { - Events.trigger(self, 'timeupdate'); + Events.trigger(window.AudioRenderer.Current, 'timeupdate'); } function onVolumeChange() { - Events.trigger(self, 'volumechange'); + Events.trigger(window.AudioRenderer.Current, 'volumechange'); } function onPlaying() { - Events.trigger(self, 'playing'); + Events.trigger(window.AudioRenderer.Current, 'playing'); } function onPlay() { - Events.trigger(self, 'play'); + Events.trigger(window.AudioRenderer.Current, 'play'); } function onPause() { - Events.trigger(self, 'pause'); + Events.trigger(window.AudioRenderer.Current, 'pause'); } function onClick() { - Events.trigger(self, 'click'); + Events.trigger(window.AudioRenderer.Current, 'click'); } function onDblClick() { - Events.trigger(self, 'dblclick'); + Events.trigger(window.AudioRenderer.Current, 'dblclick'); } function onError() { @@ -43,10 +46,10 @@ var errorCode = this.error ? this.error.code : ''; Logger.log('Media element error code: ' + errorCode); - Events.trigger(self, 'error'); + Events.trigger(window.AudioRenderer.Current, 'error'); } - var playerState = {}; + self.playerState = {}; self.currentTime = function (val) { @@ -60,16 +63,12 @@ return; } - return playerState.currentTime; + return self.playerState.currentTime; }; self.duration = function (val) { - if (playerState) { - return playerState.duration; - } - - return null; + return self.playerState.duration; }; self.stop = function () { @@ -103,14 +102,12 @@ }; self.volume = function (val) { - if (playerState) { - if (val != null) { - // TODO - return; - } - - return playerState.volume; + if (val != null) { + // TODO + return; } + + return self.playerState.volume; }; self.setCurrentSrc = function (val, item, mediaSource, tracks) { @@ -161,31 +158,22 @@ } - playerState.currentSrc = val; + AudioRenderer.Current.playerState.currentSrc = val; reportEvent('playing', {}); }; self.currentSrc = function () { - return playerState.currentSrc; + return self.playerState.currentSrc; }; self.paused = function () { - if (playerState) { - return playerState.paused; - } - - return false; + return self.playerState.paused; }; self.cleanup = function (destroyRenderer) { - playerState = {}; - }; - - self.enableCustomVideoControls = function () { - - return false; + self.playerState = {}; }; function reportEvent(eventName, result) { @@ -195,13 +183,11 @@ Logger.log('eventName: ' + eventName + '. position: ' + position); - var isPaused = result.state == 3 || eventName == 'paused'; - - var state = playerState; + var state = AudioRenderer.Current.playerState; state.duration = duration; state.currentTime = position; - state.paused = isPaused; + state.paused = result.state == 3 || eventName == 'paused'; state.volume = 0; if (eventName == 'playbackstop') { @@ -237,17 +223,20 @@ } } + function errorHandler() { + onError(); + } + self.init = function () { var deferred = DeferredBuilder.Deferred(); - window.audioplayer.configure(function () { - Logger.log('audioplayer.configure success'); + window.audioplayer.configure(successHandler, errorHandler); + + setTimeout(function () { deferred.resolve(); - }, function () { - Logger.log('audioplayer.configure error'); - deferred.resolve(); - }); + }, 500); + return deferred.promise(); }; } diff --git a/dashboard-ui/cordova/localassetmanager.js b/dashboard-ui/cordova/localassetmanager.js index 749e5286f3..487181cfbe 100644 --- a/dashboard-ui/cordova/localassetmanager.js +++ b/dashboard-ui/cordova/localassetmanager.js @@ -4,8 +4,42 @@ return null; } + function saveOfflineUser(user) { + var deferred = DeferredBuilder.Deferred(); + deferred.resolve(); + return deferred.promise(); + } + + function getCameraPhotos() { + var deferred = DeferredBuilder.Deferred(); + + if (window.CameraRoll) { + + var photos = []; + + CameraRoll.getPhotos(function (result) { + photos.push(result); + }); + + setTimeout(function () { + + // clone the array in case the callback is still getting called + Logger.log('Found ' + photos.length + ' in camera roll'); + + deferred.resolveWith(null, [photos]); + + }, 2000); + + } else { + deferred.resolveWith(null, [[]]); + } + return deferred.promise(); + } + window.LocalAssetManager = { - getLocalMediaSource: getLocalMediaSource + getLocalMediaSource: getLocalMediaSource, + saveOfflineUser: saveOfflineUser, + getCameraPhotos: getCameraPhotos }; })(); \ No newline at end of file diff --git a/dashboard-ui/scripts/appsettings.js b/dashboard-ui/scripts/appsettings.js index 5500396e7c..82eee97837 100644 --- a/dashboard-ui/scripts/appsettings.js +++ b/dashboard-ui/scripts/appsettings.js @@ -56,14 +56,6 @@ return appStorage.getItem('externalplayers') == 'true'; }, - enableItemPreviews: function (val) { - - if (val != null) { - update('enableItemPreviews', val.toString()); - } - - return appStorage.getItem('enableItemPreviews') == 'true'; - }, enableFullScreen: function (val) { if (val != null) { diff --git a/dashboard-ui/scripts/librarylist.js b/dashboard-ui/scripts/librarylist.js index eadee90049..56d33d8470 100644 --- a/dashboard-ui/scripts/librarylist.js +++ b/dashboard-ui/scripts/librarylist.js @@ -824,59 +824,6 @@ return elem; } - function onCardClick(e) { - - var targetElem = parentWithClass(e.target, 'mediaItem'); - - if (!targetElem) { - return; - } - - if (isClickable(targetElem)) { - return; - } - - if (targetElem.classList.contains('itemSelectionPanel') || this.querySelector('.itemSelectionPanel')) { - return; - } - - var info = LibraryBrowser.getListItemInfo(this); - var itemId = info.id; - var context = info.context; - - var card = this; - - if (card.classList.contains('itemWithAction')) { - return; - } - - if (!card.classList.contains('card')) { - card = $(card).parents('.card')[0]; - } - - if (card.classList.contains('groupedCard')) { - return; - } - - if (card.getAttribute('data-detailsmenu') != 'true') { - return; - } - - var target = $(targetElem); - if (target.parents('a').length || target.parents('button').length) { - return; - } - - if (AppSettings.enableItemPreviews()) { - showItemsOverlay({ - ids: [itemId], - context: context - }); - - return false; - } - } - $.fn.createCardMenus = function (options) { var preventHover = false; @@ -981,9 +928,6 @@ this.on("touchstart", '.card:not(.bannerCard) .cardContent', preventTouchHover); } - this.off('click', onCardClick); - this.on('click', onCardClick); - return this; }; @@ -1192,7 +1136,14 @@ function playAllFromHere(index, itemsContainer, method) { var ids = $('.mediaItem', itemsContainer).get().map(function (i) { - return i.getAttribute('data-itemid') || i.parentNode.getAttribute('data-itemid') || i.parentNode.parentNode.getAttribute('data-itemid'); + + var node = i; + var id = node.getAttribute('data-itemid'); + while (!id) { + node = node.parentNode; + id = node.getAttribute('data-itemid'); + } + return id; }); ids = ids.slice(index); diff --git a/dashboard-ui/scripts/localsync.js b/dashboard-ui/scripts/localsync.js index f367523348..1949d133dc 100644 --- a/dashboard-ui/scripts/localsync.js +++ b/dashboard-ui/scripts/localsync.js @@ -1,18 +1,47 @@ (function () { + var syncPromise; + window.LocalSync = { isSupported: function () { - return false; + return AppInfo.isNativeApp; }, startSync: function () { + if (!syncPromise) { + require(['multiserversync'], function () { + + syncPromise = new MediaBrowser.MultiServerSync(ConnectionManager).sync().done(function () { + + syncPromise = null; + + }).fail(function () { + + syncPromise = null; + }); + }); + } }, getSyncStatus: function () { + + if (syncPromise != null) { + return 'Syncing'; + } return 'Idle'; } }; + Dashboard.ready(function () { + if (LocalSync.isSupported) { + setInterval(function () { + + LocalSync.startSync(); + + }, 3600000); + } + }); + })(); \ No newline at end of file diff --git a/dashboard-ui/scripts/mediacontroller.js b/dashboard-ui/scripts/mediacontroller.js index 1b6c64d587..f2647abda7 100644 --- a/dashboard-ui/scripts/mediacontroller.js +++ b/dashboard-ui/scripts/mediacontroller.js @@ -839,15 +839,23 @@ self.supportsDirectPlay = function (mediaSource) { - if (mediaSource.SupportsDirectPlay && mediaSource.Protocol == 'Http' && !mediaSource.RequiredHttpHeaders.length) { + if (mediaSource.SupportsDirectPlay) { - // TODO: Need to verify the host is going to be reachable - return true; - } + if (mediaSource.Protocol == 'Http' && !mediaSource.RequiredHttpHeaders.length) { - if (mediaSource.SupportsDirectPlay && mediaSource.Protocol == 'File') { + // If this is the only way it can be played, then allow it + if (!mediaSource.SupportsDirectStream && !mediaSource.SupportsTranscoding) { + return true; + } - return FileSystemBridge.fileExists(mediaSource.Path); + // TODO: Need to verify the host is going to be reachable + return mediaSource.Path.toLowerCase().replace('https:', 'http').indexOf(ApiClient.serverAddress().toLowerCase().replace('https:', 'http').substring(0, 14)) == 0; + } + + if (mediaSource.Protocol == 'File') { + + return FileSystemBridge.fileExists(mediaSource.Path); + } } return false; diff --git a/dashboard-ui/scripts/mediaplayer-video.js b/dashboard-ui/scripts/mediaplayer-video.js index c7e966265c..4822edef9b 100644 --- a/dashboard-ui/scripts/mediaplayer-video.js +++ b/dashboard-ui/scripts/mediaplayer-video.js @@ -1095,7 +1095,7 @@ var requiresNativeControls = false; - if (self.currentMediaRenderer && !self.currentMediaRenderer.enableCustomVideoControls) { + if (self.currentMediaRenderer && self.currentMediaRenderer.enableCustomVideoControls) { requiresNativeControls = self.currentMediaRenderer.enableCustomVideoControls(); } diff --git a/dashboard-ui/scripts/serversecurity.js b/dashboard-ui/scripts/serversecurity.js index 59f6f9ea50..c84df11fac 100644 --- a/dashboard-ui/scripts/serversecurity.js +++ b/dashboard-ui/scripts/serversecurity.js @@ -10,7 +10,7 @@ ApiClient.ajax({ type: "DELETE", - url: ApiClient.getUrl('Auth/Keys/' + key) + url: ApiClient.getUrl('Auth/Keys' + key) }).done(function () { @@ -103,7 +103,7 @@ ApiClient.ajax({ type: "POST", - url: ApiClient.getUrl('Auth/Keys/', { + url: ApiClient.getUrl('Auth/Keys', { App: $('#txtAppName', form).val() diff --git a/dashboard-ui/scripts/site.js b/dashboard-ui/scripts/site.js index 96de994438..e496e728a2 100644 --- a/dashboard-ui/scripts/site.js +++ b/dashboard-ui/scripts/site.js @@ -1516,7 +1516,7 @@ var Dashboard = { SupportedLiveMediaTypes: ['Audio', 'Video'] }; - if (Dashboard.isRunningInCordova() && $.browser.android) { + if (Dashboard.isRunningInCordova()) { caps.SupportsOfflineAccess = true; caps.SupportsSync = true; caps.SupportsContentUploading = true; @@ -2148,6 +2148,12 @@ var AppInfo = {}; define("offlineusersync", ["apiclient/sync/offlineusersync"]); define("mediasync", ["apiclient/sync/mediasync"]); + if (Dashboard.isRunningInCordova()) { + define("fileupload", ["cordova/fileupload"]); + } else { + define("fileupload", ["apiclient/fileupload"]); + } + var deps = []; if (!deviceId) { @@ -2270,6 +2276,10 @@ var AppInfo = {}; Dashboard.initPromiseDone = true; $.mobile.initializePage(); deferred.resolve(); + + if (AppInfo.isNativeApp && !$.browser.android) { + require(['localsync']); + } } function initCordovaWithDeviceId(deferred, deviceId) {