mirror of
https://github.com/jellyfin/jellyfin-chromecast.git
synced 2024-11-27 16:20:58 +00:00
update components
This commit is contained in:
parent
21fe336eda
commit
688ecab75e
1083
bower_components/emby-webcomponents/chromecastplayer.js
vendored
Normal file
1083
bower_components/emby-webcomponents/chromecastplayer.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
40
bower_components/emby-webcomponents/deletehelper.js
vendored
Normal file
40
bower_components/emby-webcomponents/deletehelper.js
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
define(['connectionManager', 'confirm', 'embyRouter', 'globalize'], function (connectionManager, confirm, embyRouter, globalize) {
|
||||
'use strict';
|
||||
|
||||
function deleteItem(options) {
|
||||
|
||||
var item = options.item;
|
||||
var itemId = item.Id;
|
||||
var parentId = item.SeasonId || item.SeriesId || item.ParentId;
|
||||
var serverId = item.ServerId;
|
||||
|
||||
var msg = globalize.translate('sharedcomponents#ConfirmDeleteItem');
|
||||
var title = globalize.translate('sharedcomponents#HeaderDeleteItem');
|
||||
var apiClient = connectionManager.getApiClient(item.ServerId);
|
||||
|
||||
return confirm({
|
||||
|
||||
title: title,
|
||||
text: msg,
|
||||
confirmText: globalize.translate('sharedcomponents#Delete'),
|
||||
primary: 'cancel'
|
||||
|
||||
}).then(function () {
|
||||
|
||||
return apiClient.deleteItem(itemId).then(function () {
|
||||
|
||||
if (options.navigate) {
|
||||
if (parentId) {
|
||||
embyRouter.showItem(parentId, serverId);
|
||||
} else {
|
||||
embyRouter.goHome();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
deleteItem: deleteItem
|
||||
};
|
||||
});
|
180
bower_components/emby-webcomponents/emby-connect/connecthelper.js
vendored
Normal file
180
bower_components/emby-webcomponents/emby-connect/connecthelper.js
vendored
Normal file
@ -0,0 +1,180 @@
|
||||
define(['globalize', 'loading', 'alert'], function (globalize, loading, alert) {
|
||||
'use strict';
|
||||
|
||||
function showNewUserInviteMessage(result) {
|
||||
|
||||
if (!result.IsNewUserInvitation && !result.IsPending) {
|
||||
|
||||
// It was immediately approved
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
var message = result.IsNewUserInvitation ?
|
||||
globalize.translate('MessageInvitationSentToNewUser', result.GuestDisplayName) :
|
||||
globalize.translate('MessageInvitationSentToUser', result.GuestDisplayName);
|
||||
|
||||
alert({
|
||||
text: message,
|
||||
title: globalize.translate('HeaderInvitationSent')
|
||||
});
|
||||
}
|
||||
|
||||
function inviteGuest(options) {
|
||||
|
||||
var apiClient = options.apiClient;
|
||||
|
||||
loading.show();
|
||||
|
||||
// Add/Update connect info
|
||||
return apiClient.ajax({
|
||||
|
||||
type: "POST",
|
||||
url: apiClient.getUrl('Connect/Invite'),
|
||||
dataType: 'json',
|
||||
data: options.guestOptions || {}
|
||||
|
||||
}).then(function (result) {
|
||||
|
||||
loading.hide();
|
||||
return showNewUserInviteMessage(result);
|
||||
|
||||
}, function (response) {
|
||||
|
||||
loading.hide();
|
||||
|
||||
if (response.status === 404) {
|
||||
// User doesn't exist
|
||||
alert({
|
||||
text: globalize.translate('GuestUserNotFound')
|
||||
});
|
||||
|
||||
} else if ((response.status || 0) >= 500) {
|
||||
|
||||
// Unable to reach connect server ?
|
||||
alert({
|
||||
text: globalize.translate('ErrorReachingEmbyConnect')
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
// status 400 = account not activated
|
||||
|
||||
// General error
|
||||
showGuestGeneralErrorMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showGuestGeneralErrorMessage() {
|
||||
|
||||
var html = globalize.translate('ErrorAddingGuestAccount1', '<a href="https://emby.media/connect" target="_blank">https://emby.media/connect</a>');
|
||||
html += '<br/><br/>' + globalize.translate('ErrorAddingGuestAccount2', 'apps@emby.media');
|
||||
|
||||
var text = globalize.translate('ErrorAddingGuestAccount1', 'https://emby.media/connect');
|
||||
text += '\n\n' + globalize.translate('ErrorAddingGuestAccount2', 'apps@emby.media');
|
||||
|
||||
alert({
|
||||
text: text,
|
||||
html: html
|
||||
});
|
||||
}
|
||||
|
||||
function showLinkUserMessage(username) {
|
||||
|
||||
var html;
|
||||
var text;
|
||||
|
||||
if (username) {
|
||||
|
||||
html = globalize.translate('ErrorAddingEmbyConnectAccount1', '<a href="https://emby.media/connect" target="_blank">https://emby.media/connect</a>');
|
||||
html += '<br/><br/>' + globalize.translate('ErrorAddingEmbyConnectAccount2', 'apps@emby.media');
|
||||
|
||||
text = globalize.translate('ErrorAddingEmbyConnectAccount1', 'https://emby.media/connect');
|
||||
text += '\n\n' + globalize.translate('ErrorAddingEmbyConnectAccount2', 'apps@emby.media');
|
||||
|
||||
} else {
|
||||
html = text = globalize.translate('DefaultErrorMessage');
|
||||
}
|
||||
|
||||
return alert({
|
||||
text: text,
|
||||
html: html
|
||||
});
|
||||
}
|
||||
|
||||
function updateUserLink(apiClient, user, newConnectUsername) {
|
||||
var currentConnectUsername = user.ConnectUserName || '';
|
||||
var enteredConnectUsername = newConnectUsername;
|
||||
|
||||
var linkUrl = apiClient.getUrl('Users/' + user.Id + '/Connect/Link');
|
||||
|
||||
if (currentConnectUsername && !enteredConnectUsername) {
|
||||
|
||||
// Remove connect info
|
||||
// Add/Update connect info
|
||||
return apiClient.ajax({
|
||||
|
||||
type: "DELETE",
|
||||
url: linkUrl
|
||||
|
||||
}).then(function () {
|
||||
|
||||
return alert({
|
||||
text: globalize.translate('MessageEmbyAccontRemoved'),
|
||||
title: globalize.translate('HeaderEmbyAccountRemoved'),
|
||||
|
||||
}).catch(function () {
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
}, function () {
|
||||
|
||||
return alert({
|
||||
text: globalize.translate('ErrorRemovingEmbyConnectAccount')
|
||||
|
||||
}).then(function () {
|
||||
return Promise.reject();
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
else if (currentConnectUsername !== enteredConnectUsername) {
|
||||
|
||||
// Add/Update connect info
|
||||
return apiClient.ajax({
|
||||
type: "POST",
|
||||
url: linkUrl,
|
||||
data: {
|
||||
ConnectUsername: enteredConnectUsername
|
||||
},
|
||||
dataType: 'json'
|
||||
|
||||
}).then(function (result) {
|
||||
|
||||
var msgKey = result.IsPending ? 'MessagePendingEmbyAccountAdded' : 'MessageEmbyAccountAdded';
|
||||
|
||||
return alert({
|
||||
text: globalize.translate(msgKey),
|
||||
title: globalize.translate('HeaderEmbyAccountAdded'),
|
||||
|
||||
}).catch(function () {
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
}, function () {
|
||||
|
||||
return showLinkUserMessage('.').then(function () {
|
||||
return Promise.reject();
|
||||
});
|
||||
});
|
||||
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
inviteGuest: inviteGuest,
|
||||
updateUserLink: updateUserLink
|
||||
};
|
||||
});
|
12
bower_components/emby-webcomponents/filesystem.js
vendored
Normal file
12
bower_components/emby-webcomponents/filesystem.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
define([], function () {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
fileExists: function (path) {
|
||||
return Promise.reject();
|
||||
},
|
||||
directoryExists: function (path) {
|
||||
return Promise.reject();
|
||||
}
|
||||
};
|
||||
});
|
BIN
bower_components/emby-webcomponents/htmlaudioplayer/blank.mp3
vendored
Normal file
BIN
bower_components/emby-webcomponents/htmlaudioplayer/blank.mp3
vendored
Normal file
Binary file not shown.
444
bower_components/emby-webcomponents/htmlaudioplayer/plugin.js
vendored
Normal file
444
bower_components/emby-webcomponents/htmlaudioplayer/plugin.js
vendored
Normal file
@ -0,0 +1,444 @@
|
||||
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;
|
||||
var currentPlayOptions;
|
||||
var started;
|
||||
var _currentTime;
|
||||
|
||||
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;
|
||||
started = false;
|
||||
var elem = createMediaElement();
|
||||
|
||||
var val = options.url;
|
||||
|
||||
var seconds = (options.playerStartPositionTicks || 0) / 10000000;
|
||||
if (seconds) {
|
||||
val += '#t=' + seconds;
|
||||
}
|
||||
|
||||
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 = '<source src="' + val + '" type="' + options.mimeType + '">';
|
||||
} else {
|
||||
elem.src = val;
|
||||
}
|
||||
|
||||
currentSrc = val;
|
||||
currentPlayOptions = options;
|
||||
|
||||
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
|
||||
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;
|
||||
currentPlayOptions = 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(e) {
|
||||
|
||||
if (!started) {
|
||||
started = true;
|
||||
this.removeAttribute('controls');
|
||||
|
||||
seekOnPlaybackStart(e.target);
|
||||
}
|
||||
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 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);
|
||||
}
|
||||
};
|
||||
});
|
1299
bower_components/emby-webcomponents/htmlvideoplayer/plugin.js
vendored
Normal file
1299
bower_components/emby-webcomponents/htmlvideoplayer/plugin.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
44
bower_components/emby-webcomponents/htmlvideoplayer/style.css
vendored
Normal file
44
bower_components/emby-webcomponents/htmlvideoplayer/style.css
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
.videoPlayerContainer {
|
||||
position: fixed !important;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.videoPlayerContainer:not(.videoPlayerContainer-withBackdrop) {
|
||||
background: #000 !important;
|
||||
}
|
||||
|
||||
.videoPlayerContainer-withBackdrop {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.videoPlayerContainer-onTop {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.htmlvideoplayer {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.htmlvideoplayer::-webkit-media-text-track-display {
|
||||
/*Style the text itself*/
|
||||
margin-top: -2.5em;
|
||||
}
|
||||
|
||||
.htmlvideoplayer::cue {
|
||||
background-color: transparent;
|
||||
text-shadow: 2px 2px 2px rgba(0, 0, 0, 1);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-family: inherit;
|
||||
}
|
323
bower_components/emby-webcomponents/idb.js
vendored
Normal file
323
bower_components/emby-webcomponents/idb.js
vendored
Normal file
@ -0,0 +1,323 @@
|
||||
// from https://github.com/jakearchibald/idb
|
||||
|
||||
(function () {
|
||||
|
||||
'use strict';
|
||||
|
||||
function toArray(arr) {
|
||||
return Array.prototype.slice.call(arr);
|
||||
}
|
||||
|
||||
function promisifyRequest(request) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
request.onsuccess = function () {
|
||||
resolve(request.result);
|
||||
};
|
||||
|
||||
request.onerror = function () {
|
||||
reject(request.error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function promisifyRequestCall(obj, method, args) {
|
||||
var request;
|
||||
var p = new Promise(function (resolve, reject) {
|
||||
request = obj[method].apply(obj, args);
|
||||
promisifyRequest(request).then(resolve, reject);
|
||||
});
|
||||
|
||||
p.request = request;
|
||||
return p;
|
||||
}
|
||||
|
||||
function promisifyCursorRequestCall(obj, method, args) {
|
||||
var p = promisifyRequestCall(obj, method, args);
|
||||
return p.then(function (value) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
return new Cursor(value, p.request);
|
||||
});
|
||||
}
|
||||
|
||||
function proxyProperties(ProxyClass, targetProp, properties) {
|
||||
properties.forEach(function (prop) {
|
||||
Object.defineProperty(ProxyClass.prototype, prop, {
|
||||
get: function () {
|
||||
return this[targetProp][prop];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function proxyRequestMethods(ProxyClass, targetProp, Constructor, properties) {
|
||||
properties.forEach(function (prop) {
|
||||
if (!(prop in Constructor.prototype)) {
|
||||
return;
|
||||
}
|
||||
ProxyClass.prototype[prop] = function () {
|
||||
return promisifyRequestCall(this[targetProp], prop, arguments);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function proxyMethods(ProxyClass, targetProp, Constructor, properties) {
|
||||
properties.forEach(function (prop) {
|
||||
if (!(prop in Constructor.prototype)) {
|
||||
return;
|
||||
}
|
||||
ProxyClass.prototype[prop] = function () {
|
||||
return this[targetProp][prop].apply(this[targetProp], arguments);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function proxyCursorRequestMethods(ProxyClass, targetProp, Constructor, properties) {
|
||||
properties.forEach(function (prop) {
|
||||
if (!(prop in Constructor.prototype)) {
|
||||
return;
|
||||
}
|
||||
ProxyClass.prototype[prop] = function () {
|
||||
return promisifyCursorRequestCall(this[targetProp], prop, arguments);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function Index(index) {
|
||||
this._index = index;
|
||||
}
|
||||
|
||||
proxyProperties(Index, '_index', [
|
||||
'name',
|
||||
'keyPath',
|
||||
'multiEntry',
|
||||
'unique'
|
||||
]);
|
||||
|
||||
proxyRequestMethods(Index, '_index', IDBIndex, [
|
||||
'get',
|
||||
'getKey',
|
||||
'getAll',
|
||||
'getAllKeys',
|
||||
'count'
|
||||
]);
|
||||
|
||||
proxyCursorRequestMethods(Index, '_index', IDBIndex, [
|
||||
'openCursor',
|
||||
'openKeyCursor'
|
||||
]);
|
||||
|
||||
function Cursor(cursor, request) {
|
||||
this._cursor = cursor;
|
||||
this._request = request;
|
||||
}
|
||||
|
||||
proxyProperties(Cursor, '_cursor', [
|
||||
'direction',
|
||||
'key',
|
||||
'primaryKey',
|
||||
'value'
|
||||
]);
|
||||
|
||||
proxyRequestMethods(Cursor, '_cursor', IDBCursor, [
|
||||
'update',
|
||||
'delete'
|
||||
]);
|
||||
|
||||
// proxy 'next' methods
|
||||
['advance', 'continue', 'continuePrimaryKey'].forEach(function (methodName) {
|
||||
if (!(methodName in IDBCursor.prototype)) {
|
||||
return;
|
||||
}
|
||||
Cursor.prototype[methodName] = function () {
|
||||
var cursor = this;
|
||||
var args = arguments;
|
||||
return Promise.resolve().then(function () {
|
||||
cursor._cursor[methodName].apply(cursor._cursor, args);
|
||||
return promisifyRequest(cursor._request).then(function (value) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
return new Cursor(value, cursor._request);
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
function ObjectStore(store) {
|
||||
this._store = store;
|
||||
}
|
||||
|
||||
ObjectStore.prototype.createIndex = function () {
|
||||
return new Index(this._store.createIndex.apply(this._store, arguments));
|
||||
};
|
||||
|
||||
ObjectStore.prototype.index = function () {
|
||||
return new Index(this._store.index.apply(this._store, arguments));
|
||||
};
|
||||
|
||||
proxyProperties(ObjectStore, '_store', [
|
||||
'name',
|
||||
'keyPath',
|
||||
'indexNames',
|
||||
'autoIncrement'
|
||||
]);
|
||||
|
||||
proxyRequestMethods(ObjectStore, '_store', IDBObjectStore, [
|
||||
'put',
|
||||
'add',
|
||||
'delete',
|
||||
'clear',
|
||||
'get',
|
||||
'getAll',
|
||||
'getAllKeys',
|
||||
'count'
|
||||
]);
|
||||
|
||||
proxyCursorRequestMethods(ObjectStore, '_store', IDBObjectStore, [
|
||||
'openCursor',
|
||||
'openKeyCursor'
|
||||
]);
|
||||
|
||||
proxyMethods(ObjectStore, '_store', IDBObjectStore, [
|
||||
'deleteIndex'
|
||||
]);
|
||||
|
||||
function Transaction(idbTransaction) {
|
||||
this._tx = idbTransaction;
|
||||
this.complete = new Promise(function (resolve, reject) {
|
||||
idbTransaction.oncomplete = function () {
|
||||
resolve();
|
||||
};
|
||||
idbTransaction.onerror = function () {
|
||||
reject(idbTransaction.error);
|
||||
};
|
||||
idbTransaction.onabort = function () {
|
||||
reject(idbTransaction.error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Transaction.prototype.objectStore = function () {
|
||||
return new ObjectStore(this._tx.objectStore.apply(this._tx, arguments));
|
||||
};
|
||||
|
||||
proxyProperties(Transaction, '_tx', [
|
||||
'objectStoreNames',
|
||||
'mode'
|
||||
]);
|
||||
|
||||
proxyMethods(Transaction, '_tx', IDBTransaction, [
|
||||
'abort'
|
||||
]);
|
||||
|
||||
function UpgradeDB(db, oldVersion, transaction) {
|
||||
this._db = db;
|
||||
this.oldVersion = oldVersion;
|
||||
this.transaction = new Transaction(transaction);
|
||||
}
|
||||
|
||||
UpgradeDB.prototype.createObjectStore = function () {
|
||||
return new ObjectStore(this._db.createObjectStore.apply(this._db, arguments));
|
||||
};
|
||||
|
||||
proxyProperties(UpgradeDB, '_db', [
|
||||
'name',
|
||||
'version',
|
||||
'objectStoreNames'
|
||||
]);
|
||||
|
||||
proxyMethods(UpgradeDB, '_db', IDBDatabase, [
|
||||
'deleteObjectStore',
|
||||
'close'
|
||||
]);
|
||||
|
||||
function DB(db) {
|
||||
this._db = db;
|
||||
}
|
||||
|
||||
DB.prototype.transaction = function () {
|
||||
return new Transaction(this._db.transaction.apply(this._db, arguments));
|
||||
};
|
||||
|
||||
proxyProperties(DB, '_db', [
|
||||
'name',
|
||||
'version',
|
||||
'objectStoreNames'
|
||||
]);
|
||||
|
||||
proxyMethods(DB, '_db', IDBDatabase, [
|
||||
'close'
|
||||
]);
|
||||
|
||||
// Add cursor iterators
|
||||
// TODO: remove this once browsers do the right thing with promises
|
||||
['openCursor', 'openKeyCursor'].forEach(function (funcName) {
|
||||
[ObjectStore, Index].forEach(function (Constructor) {
|
||||
Constructor.prototype[funcName.replace('open', 'iterate')] = function () {
|
||||
var args = toArray(arguments);
|
||||
var callback = args[args.length - 1];
|
||||
var nativeObject = this._store || this._index;
|
||||
var request = nativeObject[funcName].apply(nativeObject, args.slice(0, -1));
|
||||
request.onsuccess = function () {
|
||||
callback(request.result);
|
||||
};
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// polyfill getAll
|
||||
[Index, ObjectStore].forEach(function (Constructor) {
|
||||
if (Constructor.prototype.getAll) {
|
||||
return;
|
||||
}
|
||||
Constructor.prototype.getAll = function (query, count) {
|
||||
var instance = this;
|
||||
var items = [];
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
instance.iterateCursor(query, function (cursor) {
|
||||
if (!cursor) {
|
||||
resolve(items);
|
||||
return;
|
||||
}
|
||||
items.push(cursor.value);
|
||||
|
||||
if (count !== undefined && items.length === count) {
|
||||
resolve(items);
|
||||
return;
|
||||
}
|
||||
cursor.continue();
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
var exp = {
|
||||
open: function (name, version, upgradeCallback) {
|
||||
var p = promisifyRequestCall(indexedDB, 'open', [name, version]);
|
||||
var request = p.request;
|
||||
|
||||
request.onupgradeneeded = function (event) {
|
||||
if (upgradeCallback) {
|
||||
upgradeCallback(new UpgradeDB(request.result, event.oldVersion, request.transaction));
|
||||
}
|
||||
};
|
||||
|
||||
return p.then(function (db) {
|
||||
return new DB(db);
|
||||
});
|
||||
},
|
||||
delete: function (name) {
|
||||
return promisifyRequestCall(indexedDB, 'deleteDatabase', [name]);
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = exp;
|
||||
}
|
||||
else {
|
||||
self.idb = exp;
|
||||
}
|
||||
}());
|
16
bower_components/emby-webcomponents/imageeditor/imageeditor.css
vendored
Normal file
16
bower_components/emby-webcomponents/imageeditor/imageeditor.css
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
.imageEditor-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 1em 0 1em;
|
||||
}
|
||||
|
||||
.first-imageEditor-buttons {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
@media all and (min-width: 1200px) {
|
||||
|
||||
.imageEditorCard {
|
||||
width: 20%;
|
||||
}
|
||||
}
|
341
bower_components/emby-webcomponents/input/gamepadtokey.js
vendored
Normal file
341
bower_components/emby-webcomponents/input/gamepadtokey.js
vendored
Normal file
@ -0,0 +1,341 @@
|
||||
// # The MIT License (MIT)
|
||||
// #
|
||||
// # Copyright (c) 2016 Microsoft. All rights reserved.
|
||||
// #
|
||||
// # 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:
|
||||
// #
|
||||
// # The above copyright notice and this permission notice shall be included in
|
||||
// # all copies or substantial portions of the Software.
|
||||
// #
|
||||
// # 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.
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var _GAMEPAD_A_BUTTON_INDEX = 0,
|
||||
_GAMEPAD_B_BUTTON_INDEX = 1,
|
||||
_GAMEPAD_DPAD_UP_BUTTON_INDEX = 12,
|
||||
_GAMEPAD_DPAD_DOWN_BUTTON_INDEX = 13,
|
||||
_GAMEPAD_DPAD_LEFT_BUTTON_INDEX = 14,
|
||||
_GAMEPAD_DPAD_RIGHT_BUTTON_INDEX = 15,
|
||||
_GAMEPAD_A_KEY = "GamepadA",
|
||||
_GAMEPAD_B_KEY = "GamepadB",
|
||||
_GAMEPAD_DPAD_UP_KEY = "GamepadDPadUp",
|
||||
_GAMEPAD_DPAD_DOWN_KEY = "GamepadDPadDown",
|
||||
_GAMEPAD_DPAD_LEFT_KEY = "GamepadDPadLeft",
|
||||
_GAMEPAD_DPAD_RIGHT_KEY = "GamepadDPadRight",
|
||||
_GAMEPAD_LEFT_THUMBSTICK_UP_KEY = "GamepadLeftThumbStickUp",
|
||||
_GAMEPAD_LEFT_THUMBSTICK_DOWN_KEY = "GamepadLeftThumbStickDown",
|
||||
_GAMEPAD_LEFT_THUMBSTICK_LEFT_KEY = "GamepadLeftThumbStickLeft",
|
||||
_GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEY = "GamepadLeftThumbStickRight",
|
||||
_GAMEPAD_A_KEYCODE = 0,
|
||||
_GAMEPAD_B_KEYCODE = 27,
|
||||
_GAMEPAD_DPAD_UP_KEYCODE = 38,
|
||||
_GAMEPAD_DPAD_DOWN_KEYCODE = 40,
|
||||
_GAMEPAD_DPAD_LEFT_KEYCODE = 37,
|
||||
_GAMEPAD_DPAD_RIGHT_KEYCODE = 39,
|
||||
_GAMEPAD_LEFT_THUMBSTICK_UP_KEYCODE = 38,
|
||||
_GAMEPAD_LEFT_THUMBSTICK_DOWN_KEYCODE = 40,
|
||||
_GAMEPAD_LEFT_THUMBSTICK_LEFT_KEYCODE = 37,
|
||||
_GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEYCODE = 39,
|
||||
_THUMB_STICK_THRESHOLD = 0.75;
|
||||
|
||||
var _leftThumbstickUpPressed = false,
|
||||
_leftThumbstickDownPressed = false,
|
||||
_leftThumbstickLeftPressed = false,
|
||||
_leftThumbstickRightPressed = false,
|
||||
_dPadUpPressed = false,
|
||||
_dPadDownPressed = false,
|
||||
_dPadLeftPressed = false,
|
||||
_dPadRightPressed = false,
|
||||
_gamepadAPressed = false,
|
||||
_gamepadBPressed = false;
|
||||
|
||||
// The set of buttons on the gamepad we listen for.
|
||||
var ProcessedButtons = [
|
||||
_GAMEPAD_DPAD_UP_BUTTON_INDEX,
|
||||
_GAMEPAD_DPAD_DOWN_BUTTON_INDEX,
|
||||
_GAMEPAD_DPAD_LEFT_BUTTON_INDEX,
|
||||
_GAMEPAD_DPAD_RIGHT_BUTTON_INDEX,
|
||||
_GAMEPAD_A_BUTTON_INDEX,
|
||||
_GAMEPAD_B_BUTTON_INDEX
|
||||
];
|
||||
|
||||
var _ButtonPressedState = {};
|
||||
_ButtonPressedState.getgamepadA = function () {
|
||||
return _gamepadAPressed;
|
||||
};
|
||||
|
||||
_ButtonPressedState.setgamepadA = function (newPressedState) {
|
||||
raiseKeyEvent(_gamepadAPressed, newPressedState, _GAMEPAD_A_KEY, _GAMEPAD_A_KEYCODE, false, true);
|
||||
_gamepadAPressed = newPressedState;
|
||||
};
|
||||
|
||||
_ButtonPressedState.getgamepadB = function () {
|
||||
return _gamepadBPressed;
|
||||
};
|
||||
|
||||
_ButtonPressedState.setgamepadB = function (newPressedState) {
|
||||
raiseKeyEvent(_gamepadBPressed, newPressedState, _GAMEPAD_B_KEY, _GAMEPAD_B_KEYCODE);
|
||||
_gamepadBPressed = newPressedState;
|
||||
};
|
||||
|
||||
_ButtonPressedState.getleftThumbstickUp = function () {
|
||||
return _leftThumbstickUpPressed;
|
||||
};
|
||||
|
||||
_ButtonPressedState.setleftThumbstickUp = function (newPressedState) {
|
||||
raiseKeyEvent(_leftThumbstickUpPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_UP_KEY, _GAMEPAD_LEFT_THUMBSTICK_UP_KEYCODE, true);
|
||||
_leftThumbstickUpPressed = newPressedState;
|
||||
};
|
||||
|
||||
_ButtonPressedState.getleftThumbstickDown = function () {
|
||||
return _leftThumbstickDownPressed;
|
||||
};
|
||||
|
||||
_ButtonPressedState.setleftThumbstickDown = function (newPressedState) {
|
||||
raiseKeyEvent(_leftThumbstickDownPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEY, _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEYCODE, true);
|
||||
_leftThumbstickDownPressed = newPressedState;
|
||||
};
|
||||
|
||||
_ButtonPressedState.getleftThumbstickLeft = function () {
|
||||
return _leftThumbstickLeftPressed;
|
||||
};
|
||||
|
||||
_ButtonPressedState.setleftThumbstickLeft = function (newPressedState) {
|
||||
raiseKeyEvent(_leftThumbstickLeftPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEY, _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEYCODE, true);
|
||||
_leftThumbstickLeftPressed = newPressedState;
|
||||
};
|
||||
|
||||
_ButtonPressedState.getleftThumbstickRight = function () {
|
||||
return _leftThumbstickRightPressed;
|
||||
};
|
||||
|
||||
_ButtonPressedState.setleftThumbstickRight = function (newPressedState) {
|
||||
raiseKeyEvent(_leftThumbstickRightPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEY, _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEYCODE, true);
|
||||
_leftThumbstickRightPressed = newPressedState;
|
||||
};
|
||||
|
||||
_ButtonPressedState.getdPadUp = function () {
|
||||
return _dPadUpPressed;
|
||||
};
|
||||
|
||||
_ButtonPressedState.setdPadUp = function (newPressedState) {
|
||||
raiseKeyEvent(_dPadUpPressed, newPressedState, _GAMEPAD_DPAD_UP_KEY, _GAMEPAD_DPAD_UP_KEYCODE, true);
|
||||
_dPadUpPressed = newPressedState;
|
||||
};
|
||||
|
||||
_ButtonPressedState.getdPadDown = function () {
|
||||
return _dPadDownPressed;
|
||||
};
|
||||
|
||||
_ButtonPressedState.setdPadDown = function (newPressedState) {
|
||||
raiseKeyEvent(_dPadDownPressed, newPressedState, _GAMEPAD_DPAD_DOWN_KEY, _GAMEPAD_DPAD_DOWN_KEYCODE, true);
|
||||
_dPadDownPressed = newPressedState;
|
||||
};
|
||||
|
||||
_ButtonPressedState.getdPadLeft = function () {
|
||||
return _dPadLeftPressed;
|
||||
};
|
||||
|
||||
_ButtonPressedState.setdPadLeft = function (newPressedState) {
|
||||
raiseKeyEvent(_dPadLeftPressed, newPressedState, _GAMEPAD_DPAD_LEFT_KEY, _GAMEPAD_DPAD_LEFT_KEYCODE, true);
|
||||
_dPadLeftPressed = newPressedState;
|
||||
};
|
||||
|
||||
_ButtonPressedState.getdPadRight = function () {
|
||||
return _dPadRightPressed;
|
||||
};
|
||||
|
||||
_ButtonPressedState.setdPadRight = function (newPressedState) {
|
||||
raiseKeyEvent(_dPadRightPressed, newPressedState, _GAMEPAD_DPAD_RIGHT_KEY, _GAMEPAD_DPAD_RIGHT_KEYCODE, true);
|
||||
_dPadRightPressed = newPressedState;
|
||||
};
|
||||
|
||||
var times = {};
|
||||
|
||||
function throttle(key) {
|
||||
var time = times[key] || 0;
|
||||
var now = new Date().getTime();
|
||||
|
||||
if ((now - time) >= 200) {
|
||||
//times[key] = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function resetThrottle(key) {
|
||||
times[key] = new Date().getTime();
|
||||
}
|
||||
|
||||
function raiseEvent(name, key, keyCode) {
|
||||
var event = document.createEvent('Event');
|
||||
event.initEvent(name, true, true);
|
||||
event.key = key;
|
||||
event.keyCode = keyCode;
|
||||
(document.activeElement || document.body).dispatchEvent(event);
|
||||
}
|
||||
|
||||
function raiseKeyEvent(oldPressedState, newPressedState, key, keyCode, enableRepeatKeyDown, clickonKeyUp) {
|
||||
|
||||
// No-op if oldPressedState === newPressedState
|
||||
if (newPressedState === true) {
|
||||
|
||||
// button down
|
||||
var fire = false;
|
||||
|
||||
// always fire if this is the initial down press
|
||||
if (oldPressedState === false) {
|
||||
fire = true;
|
||||
resetThrottle(key);
|
||||
} else if (enableRepeatKeyDown) {
|
||||
fire = throttle(key);
|
||||
}
|
||||
|
||||
if (fire && keyCode) {
|
||||
raiseEvent("keydown", key, keyCode);
|
||||
}
|
||||
|
||||
} else if (newPressedState === false && oldPressedState === true) {
|
||||
|
||||
resetThrottle(key);
|
||||
|
||||
// button up
|
||||
if (keyCode) {
|
||||
raiseEvent("keyup", key, keyCode);
|
||||
}
|
||||
if (clickonKeyUp) {
|
||||
(document.activeElement || window).click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runInputLoop() {
|
||||
// Get the latest gamepad state.
|
||||
var gamepads;
|
||||
if (navigator.getGamepads) {
|
||||
gamepads = navigator.getGamepads();
|
||||
} else if (navigator.webkitGetGamepads) {
|
||||
gamepads = navigator.webkitGetGamepads();
|
||||
}
|
||||
gamepads = gamepads || [];
|
||||
var i, j, len;
|
||||
for (i = 0, len = gamepads.length; i < len; i++) {
|
||||
var gamepad = gamepads[i];
|
||||
if (gamepad) {
|
||||
// Iterate through the axes
|
||||
var axes = gamepad.axes;
|
||||
var leftStickX = axes[0];
|
||||
var leftStickY = axes[1];
|
||||
if (leftStickX > _THUMB_STICK_THRESHOLD) { // Right
|
||||
_ButtonPressedState.setleftThumbstickRight(true);
|
||||
} else if (leftStickX < -_THUMB_STICK_THRESHOLD) { // Left
|
||||
_ButtonPressedState.setleftThumbstickLeft(true);
|
||||
} else if (leftStickY < -_THUMB_STICK_THRESHOLD) { // Up
|
||||
_ButtonPressedState.setleftThumbstickUp(true);
|
||||
} else if (leftStickY > _THUMB_STICK_THRESHOLD) { // Down
|
||||
_ButtonPressedState.setleftThumbstickDown(true);
|
||||
} else {
|
||||
_ButtonPressedState.setleftThumbstickLeft(false);
|
||||
_ButtonPressedState.setleftThumbstickRight(false);
|
||||
_ButtonPressedState.setleftThumbstickUp(false);
|
||||
_ButtonPressedState.setleftThumbstickDown(false);
|
||||
}
|
||||
// Iterate through the buttons to see if Left thumbstick, DPad, A and B are pressed.
|
||||
var buttons = gamepad.buttons;
|
||||
for (j = 0, len = buttons.length; j < len; j++) {
|
||||
if (ProcessedButtons.indexOf(j) !== -1) {
|
||||
|
||||
if (buttons[j].pressed) {
|
||||
switch (j) {
|
||||
case _GAMEPAD_DPAD_UP_BUTTON_INDEX:
|
||||
_ButtonPressedState.setdPadUp(true);
|
||||
break;
|
||||
case _GAMEPAD_DPAD_DOWN_BUTTON_INDEX:
|
||||
_ButtonPressedState.setdPadDown(true);
|
||||
break;
|
||||
case _GAMEPAD_DPAD_LEFT_BUTTON_INDEX:
|
||||
_ButtonPressedState.setdPadLeft(true);
|
||||
break;
|
||||
case _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX:
|
||||
_ButtonPressedState.setdPadRight(true);
|
||||
break;
|
||||
case _GAMEPAD_A_BUTTON_INDEX:
|
||||
_ButtonPressedState.setgamepadA(true);
|
||||
break;
|
||||
case _GAMEPAD_B_BUTTON_INDEX:
|
||||
_ButtonPressedState.setgamepadB(true);
|
||||
break;
|
||||
default:
|
||||
// No-op
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (j) {
|
||||
case _GAMEPAD_DPAD_UP_BUTTON_INDEX:
|
||||
if (_ButtonPressedState.getdPadUp()) {
|
||||
_ButtonPressedState.setdPadUp(false);
|
||||
}
|
||||
break;
|
||||
case _GAMEPAD_DPAD_DOWN_BUTTON_INDEX:
|
||||
if (_ButtonPressedState.getdPadDown()) {
|
||||
_ButtonPressedState.setdPadDown(false);
|
||||
}
|
||||
break;
|
||||
case _GAMEPAD_DPAD_LEFT_BUTTON_INDEX:
|
||||
if (_ButtonPressedState.getdPadLeft()) {
|
||||
_ButtonPressedState.setdPadLeft(false);
|
||||
}
|
||||
break;
|
||||
case _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX:
|
||||
if (_ButtonPressedState.getdPadRight()) {
|
||||
_ButtonPressedState.setdPadRight(false);
|
||||
}
|
||||
break;
|
||||
case _GAMEPAD_A_BUTTON_INDEX:
|
||||
if (_ButtonPressedState.getgamepadA()) {
|
||||
_ButtonPressedState.setgamepadA(false);
|
||||
}
|
||||
break;
|
||||
case _GAMEPAD_B_BUTTON_INDEX:
|
||||
if (_ButtonPressedState.getgamepadB()) {
|
||||
_ButtonPressedState.setgamepadB(false);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// No-op
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Schedule the next one
|
||||
requestAnimationFrame(runInputLoop);
|
||||
}
|
||||
|
||||
runInputLoop();
|
||||
|
||||
// The gamepadInputEmulation is a string property that exists in JavaScript UWAs and in WebViews in UWAs.
|
||||
// It won't exist in Win8.1 style apps or browsers.
|
||||
if (window.navigator && typeof window.navigator.gamepadInputEmulation === "string") {
|
||||
// We want the gamepad to provide gamepad VK keyboard events rather than moving a
|
||||
// mouse like cursor. Set to "keyboard", the gamepad will provide such keyboard events
|
||||
// and provide input to the DOM navigator.getGamepads API.
|
||||
window.navigator.gamepadInputEmulation = "gamepad";
|
||||
}
|
||||
|
||||
})();
|
113
bower_components/emby-webcomponents/input/mouse.js
vendored
Normal file
113
bower_components/emby-webcomponents/input/mouse.js
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
define(['inputManager', 'focusManager', 'browser', 'layoutManager', 'events', 'dom'], function (inputmanager, focusManager, browser, layoutManager, events, dom) {
|
||||
'use strict';
|
||||
|
||||
var self = {};
|
||||
|
||||
var lastMouseInputTime = new Date().getTime();
|
||||
var isMouseIdle;
|
||||
|
||||
function mouseIdleTime() {
|
||||
return new Date().getTime() - lastMouseInputTime;
|
||||
}
|
||||
|
||||
function notifyApp() {
|
||||
|
||||
inputmanager.notifyMouseMove();
|
||||
}
|
||||
|
||||
var lastMouseMoveData;
|
||||
dom.addEventListener(document, 'mousemove', function (e) {
|
||||
|
||||
var eventX = e.screenX;
|
||||
var eventY = e.screenY;
|
||||
|
||||
// if coord don't exist how could it move
|
||||
if (typeof eventX === "undefined" && typeof eventY === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
var obj = lastMouseMoveData;
|
||||
if (!obj) {
|
||||
lastMouseMoveData = {
|
||||
x: eventX,
|
||||
y: eventY
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// if coord are same, it didn't move
|
||||
if (Math.abs(eventX - obj.x) < 10 && Math.abs(eventY - obj.y) < 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
obj.x = eventX;
|
||||
obj.y = eventY;
|
||||
|
||||
lastMouseInputTime = new Date().getTime();
|
||||
notifyApp();
|
||||
|
||||
if (isMouseIdle) {
|
||||
isMouseIdle = false;
|
||||
document.body.classList.remove('mouseIdle');
|
||||
events.trigger(self, 'mouseactive');
|
||||
}
|
||||
}, {
|
||||
passive: true
|
||||
});
|
||||
|
||||
function onMouseEnter(e) {
|
||||
|
||||
var parent = focusManager.focusableParent(e.target);
|
||||
if (parent) {
|
||||
focusManager.focus(e.target);
|
||||
}
|
||||
}
|
||||
|
||||
function enableFocusWithMouse() {
|
||||
|
||||
if (!layoutManager.tv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (browser.xboxOne) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (browser.tv) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function initMouseFocus() {
|
||||
|
||||
dom.removeEventListener(document, 'mouseenter', onMouseEnter, {
|
||||
capture: true,
|
||||
passive: true
|
||||
});
|
||||
|
||||
if (enableFocusWithMouse()) {
|
||||
dom.addEventListener(document, 'mouseenter', onMouseEnter, {
|
||||
capture: true,
|
||||
passive: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
initMouseFocus();
|
||||
|
||||
events.on(layoutManager, 'modechange', initMouseFocus);
|
||||
|
||||
setInterval(function () {
|
||||
|
||||
if (mouseIdleTime() >= 5000) {
|
||||
isMouseIdle = true;
|
||||
document.body.classList.add('mouseIdle');
|
||||
events.trigger(self, 'mouseidle');
|
||||
}
|
||||
|
||||
}, 5000);
|
||||
|
||||
return self;
|
||||
});
|
94
bower_components/emby-webcomponents/lazyloader/lazyloader-intersectionobserver.js
vendored
Normal file
94
bower_components/emby-webcomponents/lazyloader/lazyloader-intersectionobserver.js
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
define([], function () {
|
||||
'use strict';
|
||||
|
||||
function LazyLoader(options) {
|
||||
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
LazyLoader.prototype.createObserver = function () {
|
||||
|
||||
var observerOptions = {};
|
||||
var options = this.options;
|
||||
var loadedCount = 0;
|
||||
var callback = options.callback;
|
||||
|
||||
//options.rootMargin = "300%";
|
||||
|
||||
var observerId = 'obs' + new Date().getTime();
|
||||
|
||||
var self = this;
|
||||
var observer = new IntersectionObserver(function (entries) {
|
||||
for (var j = 0, length2 = entries.length; j < length2; j++) {
|
||||
var entry = entries[j];
|
||||
var target = entry.target;
|
||||
|
||||
observer.unobserve(target);
|
||||
|
||||
if (!target[observerId]) {
|
||||
target[observerId] = 1;
|
||||
callback(target);
|
||||
loadedCount++;
|
||||
|
||||
if (loadedCount >= self.elementCount) {
|
||||
self.destroyObserver();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
observerOptions
|
||||
);
|
||||
|
||||
this.observer = observer;
|
||||
};
|
||||
|
||||
LazyLoader.prototype.addElements = function (elements) {
|
||||
|
||||
var observer = this.observer;
|
||||
|
||||
if (!observer) {
|
||||
this.createObserver();
|
||||
observer = this.observer;
|
||||
}
|
||||
|
||||
this.elementCount = (this.elementCount || 0) + elements.length;
|
||||
|
||||
for (var i = 0, length = elements.length; i < length; i++) {
|
||||
observer.observe(elements[i]);
|
||||
}
|
||||
};
|
||||
|
||||
LazyLoader.prototype.destroyObserver = function (elements) {
|
||||
|
||||
var observer = this.observer;
|
||||
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
this.observer = null;
|
||||
}
|
||||
};
|
||||
|
||||
LazyLoader.prototype.destroy = function (elements) {
|
||||
|
||||
this.destroyObserver();
|
||||
this.options = null;
|
||||
};
|
||||
|
||||
function unveilElements(elements, root, callback) {
|
||||
|
||||
if (!elements.length) {
|
||||
return;
|
||||
}
|
||||
var lazyLoader = new LazyLoader({
|
||||
callback: callback
|
||||
});
|
||||
lazyLoader.addElements(elements);
|
||||
}
|
||||
|
||||
LazyLoader.lazyChildren = function (elem, callback) {
|
||||
|
||||
unveilElements(elem.getElementsByClassName('lazy'), elem, callback);
|
||||
};
|
||||
|
||||
return LazyLoader;
|
||||
});
|
185
bower_components/emby-webcomponents/lazyloader/lazyloader-scroll.js
vendored
Normal file
185
bower_components/emby-webcomponents/lazyloader/lazyloader-scroll.js
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
define(['visibleinviewport', 'browser', 'dom'], function (visibleinviewport, browser, dom) {
|
||||
'use strict';
|
||||
|
||||
var thresholdX;
|
||||
var thresholdY;
|
||||
|
||||
var requestIdleCallback = window.requestIdleCallback || function (fn) {
|
||||
fn();
|
||||
};
|
||||
|
||||
function resetThresholds() {
|
||||
|
||||
var x = screen.availWidth;
|
||||
var y = screen.availHeight;
|
||||
|
||||
if (browser.touch) {
|
||||
x *= 1.5;
|
||||
y *= 1.5;
|
||||
}
|
||||
|
||||
thresholdX = x;
|
||||
thresholdY = y;
|
||||
}
|
||||
|
||||
dom.addEventListener(window, "orientationchange", resetThresholds, { passive: true });
|
||||
dom.addEventListener(window, 'resize', resetThresholds, { passive: true });
|
||||
resetThresholds();
|
||||
|
||||
function isVisible(elem) {
|
||||
return visibleinviewport(elem, true, thresholdX, thresholdY);
|
||||
}
|
||||
|
||||
var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel' : 'mousewheel');
|
||||
var self = {};
|
||||
|
||||
function cancelAll(tokens) {
|
||||
for (var i = 0, length = tokens.length; i < length; i++) {
|
||||
|
||||
tokens[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
function unveilElementsInternal(instance, callback) {
|
||||
|
||||
var unveiledElements = [];
|
||||
var cancellationTokens = [];
|
||||
var loadedCount = 0;
|
||||
|
||||
function unveilInternal(tokenIndex) {
|
||||
|
||||
var anyFound = false;
|
||||
var out = false;
|
||||
|
||||
var elements = instance.elements;
|
||||
// TODO: This out construct assumes left to right, top to bottom
|
||||
|
||||
for (var i = 0, length = elements.length; i < length; i++) {
|
||||
|
||||
if (cancellationTokens[tokenIndex]) {
|
||||
return;
|
||||
}
|
||||
if (unveiledElements[i]) {
|
||||
continue;
|
||||
}
|
||||
var elem = elements[i];
|
||||
if (!out && isVisible(elem)) {
|
||||
anyFound = true;
|
||||
unveiledElements[i] = true;
|
||||
callback(elem);
|
||||
loadedCount++;
|
||||
} else {
|
||||
|
||||
if (anyFound) {
|
||||
out = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (loadedCount >= elements.length) {
|
||||
dom.removeEventListener(document, 'focus', unveil, {
|
||||
capture: true,
|
||||
passive: true
|
||||
});
|
||||
dom.removeEventListener(document, 'scroll', unveil, {
|
||||
capture: true,
|
||||
passive: true
|
||||
});
|
||||
dom.removeEventListener(document, wheelEvent, unveil, {
|
||||
capture: true,
|
||||
passive: true
|
||||
});
|
||||
dom.removeEventListener(window, 'resize', unveil, {
|
||||
capture: true,
|
||||
passive: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function unveil() {
|
||||
|
||||
cancelAll(cancellationTokens);
|
||||
|
||||
var index = cancellationTokens.length;
|
||||
cancellationTokens.length++;
|
||||
|
||||
setTimeout(function () {
|
||||
unveilInternal(index);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
dom.addEventListener(document, 'focus', unveil, {
|
||||
capture: true,
|
||||
passive: true
|
||||
});
|
||||
dom.addEventListener(document, 'scroll', unveil, {
|
||||
capture: true,
|
||||
passive: true
|
||||
});
|
||||
dom.addEventListener(document, wheelEvent, unveil, {
|
||||
capture: true,
|
||||
passive: true
|
||||
});
|
||||
dom.addEventListener(window, 'resize', unveil, {
|
||||
capture: true,
|
||||
passive: true
|
||||
});
|
||||
|
||||
unveil();
|
||||
}
|
||||
|
||||
function LazyLoader(options) {
|
||||
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
LazyLoader.prototype.createObserver = function () {
|
||||
|
||||
unveilElementsInternal(this, this.options.callback);
|
||||
this.observer = 1;
|
||||
};
|
||||
|
||||
LazyLoader.prototype.addElements = function (elements) {
|
||||
|
||||
this.elements = this.elements || [];
|
||||
|
||||
for (var i = 0, length = elements.length; i < length; i++) {
|
||||
this.elements.push(elements[i]);
|
||||
}
|
||||
|
||||
var observer = this.observer;
|
||||
|
||||
if (!observer) {
|
||||
this.createObserver();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
LazyLoader.prototype.destroyObserver = function (elements) {
|
||||
|
||||
};
|
||||
|
||||
LazyLoader.prototype.destroy = function (elements) {
|
||||
|
||||
this.destroyObserver();
|
||||
this.options = null;
|
||||
};
|
||||
|
||||
function unveilElements(elements, root, callback) {
|
||||
|
||||
if (!elements.length) {
|
||||
return;
|
||||
}
|
||||
var lazyLoader = new LazyLoader({
|
||||
callback: callback
|
||||
});
|
||||
lazyLoader.addElements(elements);
|
||||
}
|
||||
|
||||
LazyLoader.lazyChildren = function (elem, callback) {
|
||||
|
||||
unveilElements(elem.getElementsByClassName('lazy'), elem, callback);
|
||||
};
|
||||
|
||||
return LazyLoader;
|
||||
});
|
137
bower_components/emby-webcomponents/native-promise-only/README.md
vendored
Normal file
137
bower_components/emby-webcomponents/native-promise-only/README.md
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
# Native Promise Only (NPO)
|
||||
|
||||
A polyfill for native ES6 Promises as close as possible (no extensions) to the strict spec definitions.
|
||||
|
||||
## Intent
|
||||
|
||||
The aim of this project is to be the smallest polyfill for Promises, staying as close as possible to what's specified in both [Promises/A+](http://promisesaplus.com) and the [upcoming ES6 specification](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-objects).
|
||||
|
||||
An equally important goal is to avoid exposing any capability for promise-state to be mutated externally. The [Known Limitations](#known-limitations) section below explains the trade-offs of that balance.
|
||||
|
||||
## Usage
|
||||
|
||||
To use this polyfill in the browser, include the "npo.js" file (see the instructions in [Tests/Compliance section](#testscompliance) below for how to build "npo.js" if you don't have it already) with your site's scripts. It's a polyfill, which means it will not overwrite `Promise` if it exists as a global already, so it's safe to include unconditionally.
|
||||
|
||||
To use with AMD, import the "npo.js" file module.
|
||||
|
||||
To install the polyfill via bower, run:
|
||||
|
||||
```
|
||||
bower install native-promise-only
|
||||
```
|
||||
|
||||
To install the polyfill via npm, run:
|
||||
|
||||
```
|
||||
npm install native-promise-only
|
||||
```
|
||||
|
||||
Then require the module into your node code:
|
||||
|
||||
```js
|
||||
require("native-promise-only");
|
||||
```
|
||||
|
||||
Notice that using the module in this way, we don't assign the module's public API to any variable. **We don't need to**, because it's a polyfill that intentionally patches the global environment (in this case to the `Promise` name) once included.
|
||||
|
||||
If you *want* to also have a reference pointing to the same `Promise` global, you *can also* assign the return value from the `require(..)` statement, but it's strongly recommended that you use the same `Promise` name so as to not create confusion:
|
||||
|
||||
```js
|
||||
var Promise = require("native-promise-only");
|
||||
|
||||
// Promise === global.Promise; // true!
|
||||
```
|
||||
|
||||
Other than the below [Known Limitations](#known-limitations) discussion and some browser bugs (such as [these](https://gist.github.com/getify/bd11ccf1eff2efdac0fb)) which **this polyfill doesn't suffer from**, your promises should operate the same in all JS environments.
|
||||
|
||||
Exactly like native promises, here's a quick example of how you create and use the polyfilled promises:
|
||||
|
||||
```js
|
||||
var p = new Promise(function(resolve,reject){
|
||||
setTimeout(function(){
|
||||
resolve("Yay!");
|
||||
},100);
|
||||
});
|
||||
|
||||
p.then(function(msg){
|
||||
console.log(msg); // Yay!
|
||||
});
|
||||
```
|
||||
|
||||
For more on promises, check these blog posts out:
|
||||
|
||||
1. Back-story on the hows and whys behind promises (chaining, errors, etc): [multi-part blog post series "Promises"](http://blog.getify.com/promises-part-1/) by [getify](http://twitter.com/getify) (me).
|
||||
2. Using and enjoying native promises: [JavaScript Promises](http://www.html5rocks.com/en/tutorials/es6/promises/) by [Jake Archibald](http://twitter.com/jaffathecake).
|
||||
|
||||
## Known Limitations
|
||||
|
||||
A promise object from this polyfill **will be** an instance of the `Promise` constructor, which makes identification of genuine promises easier:
|
||||
|
||||
```js
|
||||
var p = new Promise(..);
|
||||
|
||||
p instanceof Promise; // true
|
||||
```
|
||||
|
||||
However, these promise instances don't inherit (delegate to) a *meaningful* `Promise.prototype` object for their methods (there is one, it's just mostly empty).
|
||||
|
||||
Consider:
|
||||
|
||||
```js
|
||||
var p = new Promise(..);
|
||||
|
||||
Object.getOwnPropertyNames( p ); // [ then, catch ]
|
||||
Object.getOwnPropertyNames( Promise.prototype ); // [ constructor ]
|
||||
```
|
||||
|
||||
As such, these promises are not really "sub-classable" in the ES6 `class` / `extends` sense, though theoretically you should be able to do that in ES6 with the built-in Promises.
|
||||
|
||||
To read a full explanation of why, read [Part 3: The Trust Problem](http://blog.getify.com/promises-part-3/) of my blog post series on Promises.
|
||||
|
||||
Briefly, the reason for this deviation is that there's a choice between having delegated methods on the `.prototype` or having private state. Since **the spirit of promises was always to ensure trustability** -- that promises were immutable (from the outside) to everyone except the initial resolver/deferred -- private state is a critically important feature to preserve.
|
||||
|
||||
Many other ES6 promise shims/libs seem to have forgotten that important point, as many of them either expose the state publicly on the object instance or provide public accessor methods which can externally mutate a promise's state. Both of these deviations are **intolerable** in my opinion, so this library chose the opposite trade-off: *no ES6 sub-classing*.
|
||||
|
||||
Any trade-off is a shame, but this one is the least of a few evils, and probably won't prove to limit very many, as there are only a limited number of use-cases for `extend`ing `Promise` in the ES6 sub-class sense.
|
||||
|
||||
## Still Want More?
|
||||
|
||||
This project intentionally adheres pretty strictly to the narrow core of [Promises/A+](http://promisesaplus.com) as adopted/implemented by ES6 into the native `Promise()` mechanism.
|
||||
|
||||
But it's quite likely that you will [experience a variety of scenarios](http://blog.getify.com/promises-part-5/) in which using *only* native promises might be tedious, limiting, or more trouble than it's worth. There's good reason why most other **Promises/A+** "compliant" libs are actually superset extensions on the narrow core: **because async flow-control is often quite complex in the real world.**
|
||||
|
||||
*Native Promise Only* will **NOT** add any of these extra flourishes. Sorry.
|
||||
|
||||
**However, I have another project**: [asynquence](http://github.com/getify/asynquence) (async + sequence). It's an abstraction on top of the promises concept (promises are hidden inside), designed to drastically improve the readability and expressiveness of your async flow-control code.
|
||||
|
||||
You simply express your async flow-control and *asynquence* creates and chains all the promises for you underneath. **Super simple.**
|
||||
|
||||
*asynquence* has a custom implementation for the internal "promises" it uses, and as such does not need native `Promises`, nor does it need/include this polyfill.
|
||||
|
||||
Get your feet wet with native promises first, but then when you go looking for something more, consider [asynquence](http://github.com/getify/asynquence) (which is [vastly more powerful](http://davidwalsh.name/asynquence-part-1) and is still only ~2k!).
|
||||
|
||||
## Tests/Compliance
|
||||
|
||||
<a href="http://promisesaplus.com/" float="right">
|
||||
<img src="http://promisesaplus.com/assets/logo-small.png" alt="Promises/A+ logo"
|
||||
title="Promises/A+ 1.1 compliant" align="right" />
|
||||
</a>
|
||||
|
||||
*Native Promise Only* is "spec compliant" in the sense of passing all tests in the [Promises/A+ Test Suite](https://github.com/promises-aplus/promises-tests).
|
||||
|
||||
To run all tests:
|
||||
|
||||
1. Either git-clone this repo or run `npm install native-promise-only`, and then switch into that project root.
|
||||
2. Run `npm install` in the project root to install the dev-dependencies.
|
||||
3. If you didn't get *native-promise-only* from npm, then from the project root, run `./build.js` or `node build.js` or `npm run build` to generate the minified "npo.js" in the project root.
|
||||
4. Finally, run `npm test`.
|
||||
|
||||
**Note:** Other tests need to be added, such as testing the `Promise()` constructor's behavior, as well as the `Promise.*` static helpers (`resolve(..)`, `reject(..)`, `all(..)`, and `race(..)`), none of which are covered by the Promises/A+ test suite.
|
||||
|
||||
Developing a more comprehensive test-suite to augment the Promises/A+ test suite **is now another primary goal** of this project.
|
||||
|
||||
## License
|
||||
|
||||
The code and all the documentation are released under the MIT license.
|
||||
|
||||
http://getify.mit-license.org/
|
373
bower_components/emby-webcomponents/native-promise-only/lib/npo.src.js
vendored
Normal file
373
bower_components/emby-webcomponents/native-promise-only/lib/npo.src.js
vendored
Normal file
@ -0,0 +1,373 @@
|
||||
/*! Native Promise Only
|
||||
v0.8.0-a (c) Kyle Simpson
|
||||
MIT License: http://getify.mit-license.org
|
||||
*/
|
||||
|
||||
(function UMD(name,context,definition){
|
||||
// special form of UMD for polyfilling across evironments
|
||||
context[name] = definition();
|
||||
if (typeof module != "undefined" && module.exports) { module.exports = context[name]; }
|
||||
else if (typeof define == "function" && define.amd) { define(function $AMD$(){ return context[name]; }); }
|
||||
})("Promise",typeof global != "undefined" ? global : this,function DEF(){
|
||||
/*jshint validthis:true */
|
||||
"use strict";
|
||||
|
||||
var builtInProp, cycle, scheduling_queue,
|
||||
ToString = Object.prototype.toString,
|
||||
timer = (typeof setImmediate != "undefined") ?
|
||||
function timer(fn) { return setImmediate(fn); } :
|
||||
setTimeout
|
||||
;
|
||||
|
||||
// dammit, IE8.
|
||||
try {
|
||||
Object.defineProperty({},"x",{});
|
||||
builtInProp = function builtInProp(obj,name,val,config) {
|
||||
return Object.defineProperty(obj,name,{
|
||||
value: val,
|
||||
writable: true,
|
||||
configurable: config !== false
|
||||
});
|
||||
};
|
||||
}
|
||||
catch (err) {
|
||||
builtInProp = function builtInProp(obj,name,val) {
|
||||
obj[name] = val;
|
||||
return obj;
|
||||
};
|
||||
}
|
||||
|
||||
// Note: using a queue instead of array for efficiency
|
||||
scheduling_queue = (function Queue() {
|
||||
var first, last, item;
|
||||
|
||||
function Item(fn,self) {
|
||||
this.fn = fn;
|
||||
this.self = self;
|
||||
this.next = void 0;
|
||||
}
|
||||
|
||||
return {
|
||||
add: function add(fn,self) {
|
||||
item = new Item(fn,self);
|
||||
if (last) {
|
||||
last.next = item;
|
||||
}
|
||||
else {
|
||||
first = item;
|
||||
}
|
||||
last = item;
|
||||
item = void 0;
|
||||
},
|
||||
drain: function drain() {
|
||||
var f = first;
|
||||
first = last = cycle = void 0;
|
||||
|
||||
while (f) {
|
||||
f.fn.call(f.self);
|
||||
f = f.next;
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
function schedule(fn,self) {
|
||||
scheduling_queue.add(fn,self);
|
||||
if (!cycle) {
|
||||
cycle = timer(scheduling_queue.drain);
|
||||
}
|
||||
}
|
||||
|
||||
// promise duck typing
|
||||
function isThenable(o) {
|
||||
var _then, o_type = typeof o;
|
||||
|
||||
if (o != null &&
|
||||
(
|
||||
o_type == "object" || o_type == "function"
|
||||
)
|
||||
) {
|
||||
_then = o.then;
|
||||
}
|
||||
return typeof _then == "function" ? _then : false;
|
||||
}
|
||||
|
||||
function notify() {
|
||||
for (var i=0; i<this.chain.length; i++) {
|
||||
notifyIsolated(
|
||||
this,
|
||||
(this.state === 1) ? this.chain[i].success : this.chain[i].failure,
|
||||
this.chain[i]
|
||||
);
|
||||
}
|
||||
this.chain.length = 0;
|
||||
}
|
||||
|
||||
// NOTE: This is a separate function to isolate
|
||||
// the `try..catch` so that other code can be
|
||||
// optimized better
|
||||
function notifyIsolated(self,cb,chain) {
|
||||
var ret, _then;
|
||||
try {
|
||||
if (cb === false) {
|
||||
chain.reject(self.msg);
|
||||
}
|
||||
else {
|
||||
if (cb === true) {
|
||||
ret = self.msg;
|
||||
}
|
||||
else {
|
||||
ret = cb.call(void 0,self.msg);
|
||||
}
|
||||
|
||||
if (ret === chain.promise) {
|
||||
chain.reject(TypeError("Promise-chain cycle"));
|
||||
}
|
||||
else if (_then = isThenable(ret)) {
|
||||
_then.call(ret,chain.resolve,chain.reject);
|
||||
}
|
||||
else {
|
||||
chain.resolve(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
chain.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
function resolve(msg) {
|
||||
var _then, self = this;
|
||||
|
||||
// already triggered?
|
||||
if (self.triggered) { return; }
|
||||
|
||||
self.triggered = true;
|
||||
|
||||
// unwrap
|
||||
if (self.def) {
|
||||
self = self.def;
|
||||
}
|
||||
|
||||
try {
|
||||
if (_then = isThenable(msg)) {
|
||||
schedule(function(){
|
||||
var def_wrapper = new MakeDefWrapper(self);
|
||||
try {
|
||||
_then.call(msg,
|
||||
function $resolve$(){ resolve.apply(def_wrapper,arguments); },
|
||||
function $reject$(){ reject.apply(def_wrapper,arguments); }
|
||||
);
|
||||
}
|
||||
catch (err) {
|
||||
reject.call(def_wrapper,err);
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
self.msg = msg;
|
||||
self.state = 1;
|
||||
if (self.chain.length > 0) {
|
||||
schedule(notify,self);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
reject.call(new MakeDefWrapper(self),err);
|
||||
}
|
||||
}
|
||||
|
||||
function reject(msg) {
|
||||
var self = this;
|
||||
|
||||
// already triggered?
|
||||
if (self.triggered) { return; }
|
||||
|
||||
self.triggered = true;
|
||||
|
||||
// unwrap
|
||||
if (self.def) {
|
||||
self = self.def;
|
||||
}
|
||||
|
||||
self.msg = msg;
|
||||
self.state = 2;
|
||||
if (self.chain.length > 0) {
|
||||
schedule(notify,self);
|
||||
}
|
||||
}
|
||||
|
||||
function iteratePromises(Constructor,arr,resolver,rejecter) {
|
||||
for (var idx=0; idx<arr.length; idx++) {
|
||||
(function IIFE(idx){
|
||||
Constructor.resolve(arr[idx])
|
||||
.then(
|
||||
function $resolver$(msg){
|
||||
resolver(idx,msg);
|
||||
},
|
||||
rejecter
|
||||
);
|
||||
})(idx);
|
||||
}
|
||||
}
|
||||
|
||||
function MakeDefWrapper(self) {
|
||||
this.def = self;
|
||||
this.triggered = false;
|
||||
}
|
||||
|
||||
function MakeDef(self) {
|
||||
this.promise = self;
|
||||
this.state = 0;
|
||||
this.triggered = false;
|
||||
this.chain = [];
|
||||
this.msg = void 0;
|
||||
}
|
||||
|
||||
function Promise(executor) {
|
||||
if (typeof executor != "function") {
|
||||
throw TypeError("Not a function");
|
||||
}
|
||||
|
||||
if (this.__NPO__ !== 0) {
|
||||
throw TypeError("Not a promise");
|
||||
}
|
||||
|
||||
// instance shadowing the inherited "brand"
|
||||
// to signal an already "initialized" promise
|
||||
this.__NPO__ = 1;
|
||||
|
||||
var def = new MakeDef(this);
|
||||
|
||||
this["then"] = function then(success,failure) {
|
||||
var o = {
|
||||
success: typeof success == "function" ? success : true,
|
||||
failure: typeof failure == "function" ? failure : false
|
||||
};
|
||||
// Note: `then(..)` itself can be borrowed to be used against
|
||||
// a different promise constructor for making the chained promise,
|
||||
// by substituting a different `this` binding.
|
||||
o.promise = new this.constructor(function extractChain(resolve,reject) {
|
||||
if (typeof resolve != "function" || typeof reject != "function") {
|
||||
throw TypeError("Not a function");
|
||||
}
|
||||
|
||||
o.resolve = resolve;
|
||||
o.reject = reject;
|
||||
});
|
||||
def.chain.push(o);
|
||||
|
||||
if (def.state !== 0) {
|
||||
schedule(notify,def);
|
||||
}
|
||||
|
||||
return o.promise;
|
||||
};
|
||||
this["catch"] = function $catch$(failure) {
|
||||
return this.then(void 0,failure);
|
||||
};
|
||||
|
||||
try {
|
||||
executor.call(
|
||||
void 0,
|
||||
function publicResolve(msg){
|
||||
resolve.call(def,msg);
|
||||
},
|
||||
function publicReject(msg) {
|
||||
reject.call(def,msg);
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (err) {
|
||||
reject.call(def,err);
|
||||
}
|
||||
}
|
||||
|
||||
var PromisePrototype = builtInProp({},"constructor",Promise,
|
||||
/*configurable=*/false
|
||||
);
|
||||
|
||||
// Note: Android 4 cannot use `Object.defineProperty(..)` here
|
||||
Promise.prototype = PromisePrototype;
|
||||
|
||||
// built-in "brand" to signal an "uninitialized" promise
|
||||
builtInProp(PromisePrototype,"__NPO__",0,
|
||||
/*configurable=*/false
|
||||
);
|
||||
|
||||
builtInProp(Promise,"resolve",function Promise$resolve(msg) {
|
||||
var Constructor = this;
|
||||
|
||||
// spec mandated checks
|
||||
// note: best "isPromise" check that's practical for now
|
||||
if (msg && typeof msg == "object" && msg.__NPO__ === 1) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
return new Constructor(function executor(resolve,reject){
|
||||
if (typeof resolve != "function" || typeof reject != "function") {
|
||||
throw TypeError("Not a function");
|
||||
}
|
||||
|
||||
resolve(msg);
|
||||
});
|
||||
});
|
||||
|
||||
builtInProp(Promise,"reject",function Promise$reject(msg) {
|
||||
return new this(function executor(resolve,reject){
|
||||
if (typeof resolve != "function" || typeof reject != "function") {
|
||||
throw TypeError("Not a function");
|
||||
}
|
||||
|
||||
reject(msg);
|
||||
});
|
||||
});
|
||||
|
||||
builtInProp(Promise,"all",function Promise$all(arr) {
|
||||
var Constructor = this;
|
||||
|
||||
// spec mandated checks
|
||||
if (ToString.call(arr) != "[object Array]") {
|
||||
return Constructor.reject(TypeError("Not an array"));
|
||||
}
|
||||
if (arr.length === 0) {
|
||||
return Constructor.resolve([]);
|
||||
}
|
||||
|
||||
return new Constructor(function executor(resolve,reject){
|
||||
if (typeof resolve != "function" || typeof reject != "function") {
|
||||
throw TypeError("Not a function");
|
||||
}
|
||||
|
||||
var len = arr.length, msgs = Array(len), count = 0;
|
||||
|
||||
iteratePromises(Constructor,arr,function resolver(idx,msg) {
|
||||
msgs[idx] = msg;
|
||||
if (++count === len) {
|
||||
resolve(msgs);
|
||||
}
|
||||
},reject);
|
||||
});
|
||||
});
|
||||
|
||||
builtInProp(Promise,"race",function Promise$race(arr) {
|
||||
var Constructor = this;
|
||||
|
||||
// spec mandated checks
|
||||
if (ToString.call(arr) != "[object Array]") {
|
||||
return Constructor.reject(TypeError("Not an array"));
|
||||
}
|
||||
|
||||
return new Constructor(function executor(resolve,reject){
|
||||
if (typeof resolve != "function" || typeof reject != "function") {
|
||||
throw TypeError("Not a function");
|
||||
}
|
||||
|
||||
iteratePromises(Constructor,arr,function resolver(idx,msg){
|
||||
resolve(msg);
|
||||
},reject);
|
||||
});
|
||||
});
|
||||
|
||||
return Promise;
|
||||
});
|
21
bower_components/emby-webcomponents/native-promise-only/test_adapter.js
vendored
Normal file
21
bower_components/emby-webcomponents/native-promise-only/test_adapter.js
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
// Adapter for "promises-aplus-tests" test runner
|
||||
|
||||
var path = require("path");
|
||||
var Promise = require(path.join(__dirname,"lib","npo.src.js"));
|
||||
|
||||
module.exports.deferred = function __deferred__() {
|
||||
var o = {};
|
||||
o.promise = new Promise(function __Promise__(resolve,reject){
|
||||
o.resolve = resolve;
|
||||
o.reject = reject;
|
||||
});
|
||||
return o;
|
||||
};
|
||||
|
||||
module.exports.resolved = function __resolved__(val) {
|
||||
return Promise.resolve(val);
|
||||
};
|
||||
|
||||
module.exports.rejected = function __rejected__(reason) {
|
||||
return Promise.reject(reason);
|
||||
};
|
148
bower_components/emby-webcomponents/packagemanager.js
vendored
Normal file
148
bower_components/emby-webcomponents/packagemanager.js
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
define(['appSettings', 'pluginManager'], function (appSettings, pluginManager) {
|
||||
'use strict';
|
||||
|
||||
function packageManager() {
|
||||
|
||||
var self = this;
|
||||
var settingsKey = 'installedpackages1';
|
||||
|
||||
var packages = [];
|
||||
|
||||
self.packages = function () {
|
||||
return packages.slice(0);
|
||||
};
|
||||
|
||||
function addPackage(pkg) {
|
||||
|
||||
packages = packages.filter(function (p) {
|
||||
|
||||
return p.name !== pkg.name;
|
||||
});
|
||||
|
||||
packages.push(pkg);
|
||||
}
|
||||
|
||||
self.install = function (url) {
|
||||
|
||||
return loadPackage(url, true).then(function (pkg) {
|
||||
|
||||
var manifestUrls = JSON.parse(appSettings.get(settingsKey) || '[]');
|
||||
|
||||
if (manifestUrls.indexOf(url) === -1) {
|
||||
manifestUrls.push(url);
|
||||
appSettings.set(settingsKey, JSON.stringify(manifestUrls));
|
||||
}
|
||||
|
||||
return pkg;
|
||||
});
|
||||
};
|
||||
|
||||
self.uninstall = function (name) {
|
||||
|
||||
var pkg = packages.filter(function (p) {
|
||||
|
||||
return p.name === name;
|
||||
})[0];
|
||||
|
||||
if (pkg) {
|
||||
|
||||
packages = packages.filter(function (p) {
|
||||
|
||||
return p.name !== name;
|
||||
});
|
||||
|
||||
removeUrl(pkg.url);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
function removeUrl(url) {
|
||||
|
||||
var manifestUrls = JSON.parse(appSettings.get(settingsKey) || '[]');
|
||||
|
||||
manifestUrls = manifestUrls.filter(function (i) {
|
||||
return i !== url;
|
||||
});
|
||||
|
||||
appSettings.set(settingsKey, JSON.stringify(manifestUrls));
|
||||
}
|
||||
|
||||
self.init = function () {
|
||||
var manifestUrls = JSON.parse(appSettings.get(settingsKey) || '[]');
|
||||
|
||||
return Promise.all(manifestUrls.map(loadPackage)).then(function () {
|
||||
return Promise.resolve();
|
||||
}, function () {
|
||||
return Promise.resolve();
|
||||
});
|
||||
};
|
||||
|
||||
function loadPackage(url, throwError) {
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
var originalUrl = url;
|
||||
url += url.indexOf('?') === -1 ? '?' : '&';
|
||||
url += 't=' + new Date().getTime();
|
||||
|
||||
xhr.open('GET', url, true);
|
||||
|
||||
var onError = function () {
|
||||
|
||||
if (throwError === true) {
|
||||
reject();
|
||||
} else {
|
||||
removeUrl(originalUrl);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = function (e) {
|
||||
if (this.status < 400) {
|
||||
|
||||
var pkg = JSON.parse(this.response);
|
||||
pkg.url = originalUrl;
|
||||
|
||||
addPackage(pkg);
|
||||
|
||||
var plugins = pkg.plugins || [];
|
||||
if (pkg.plugin) {
|
||||
plugins.push(pkg.plugin);
|
||||
}
|
||||
var promises = plugins.map(function (pluginUrl) {
|
||||
return pluginManager.loadPlugin(self.mapPath(pkg, pluginUrl));
|
||||
});
|
||||
Promise.all(promises).then(resolve, resolve);
|
||||
|
||||
} else {
|
||||
onError();
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = onError;
|
||||
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
self.mapPath = function (pkg, pluginUrl) {
|
||||
|
||||
var urlLower = pluginUrl.toLowerCase();
|
||||
if (urlLower.indexOf('http:') === 0 || urlLower.indexOf('https:') === 0 || urlLower.indexOf('file:') === 0) {
|
||||
return pluginUrl;
|
||||
}
|
||||
|
||||
var packageUrl = pkg.url;
|
||||
packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf('/'));
|
||||
|
||||
packageUrl += '/';
|
||||
packageUrl += pluginUrl;
|
||||
|
||||
return packageUrl;
|
||||
};
|
||||
}
|
||||
|
||||
return new packageManager();
|
||||
});
|
63
bower_components/emby-webcomponents/playback/autoplaydetect.js
vendored
Normal file
63
bower_components/emby-webcomponents/playback/autoplaydetect.js
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
define([], function () {
|
||||
'use strict';
|
||||
|
||||
function supportsHtmlMediaAutoplay() {
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
var timeout;
|
||||
var elem = document.createElement('video');
|
||||
var elemStyle = elem.style;
|
||||
//skip the test if video itself, or the autoplay
|
||||
//element on it isn't supported
|
||||
if (!('autoplay' in elem)) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
elemStyle.position = 'absolute';
|
||||
elemStyle.height = 0;
|
||||
elemStyle.width = 0;
|
||||
|
||||
elem.setAttribute('autoplay', 'autoplay');
|
||||
elem.style.display = 'none';
|
||||
document.body.appendChild(elem);
|
||||
|
||||
var testAutoplay = function (arg) {
|
||||
clearTimeout(timeout);
|
||||
elem.removeEventListener('playing', testAutoplay);
|
||||
elem.removeEventListener('play', testAutoplay);
|
||||
var supported = (arg && arg.type === 'playing') || (arg && arg.type === 'play') || elem.currentTime !== 0;
|
||||
elem.parentNode.removeChild(elem);
|
||||
|
||||
if (supported) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
};
|
||||
|
||||
// play needed for firefox
|
||||
elem.addEventListener('play', testAutoplay);
|
||||
elem.addEventListener('playing', testAutoplay);
|
||||
|
||||
try {
|
||||
elem.src = 'data:video/mp4;base64,AAAAHGZ0eXBtcDQyAAAAAG1wNDJpc29tYXZjMQAAAz5tb292AAAAbG12aGQAAAAAzaNacc2jWnEAAV+QAAFfkAABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAGGlvZHMAAAAAEICAgAcAT////3//AAACQ3RyYWsAAABcdGtoZAAAAAHNo1pxzaNacQAAAAEAAAAAAAFfkAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAEAAAABAAAAAAAd9tZGlhAAAAIG1kaGQAAAAAzaNacc2jWnEAAV+QAAFfkFXEAAAAAAAhaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAAAAAAGWbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABVnN0YmwAAACpc3RzZAAAAAAAAAABAAAAmWF2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAEAAQAEgAAABIAAAAAAAAAAEOSlZUL0FWQyBDb2RpbmcAAAAAAAAAAAAAAAAAAAAAAAAY//8AAAAxYXZjQwH0AAr/4QAZZ/QACq609NQYBBkAAAMAAQAAAwAKjxImoAEABWjOAa8gAAAAEmNvbHJuY2xjAAYAAQAGAAAAGHN0dHMAAAAAAAAAAQAAAAUAAEZQAAAAKHN0c3oAAAAAAAAAAAAAAAUAAAIqAAAACAAAAAgAAAAIAAAACAAAAChzdHNjAAAAAAAAAAIAAAABAAAABAAAAAEAAAACAAAAAQAAAAEAAAAYc3RjbwAAAAAAAAACAAADYgAABaQAAAAUc3RzcwAAAAAAAAABAAAAAQAAABFzZHRwAAAAAAREREREAAAAb3VkdGEAAABnbWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcgAAAAAAAAAAAAAAAAAAAAA6aWxzdAAAADKpdG9vAAAAKmRhdGEAAAABAAAAAEhhbmRCcmFrZSAwLjkuOCAyMDEyMDcxODAwAAACUm1kYXQAAAHkBgX/4NxF6b3m2Ui3lizYINkj7u94MjY0IC0gY29yZSAxMjAgLSBILjI2NC9NUEVHLTQgQVZDIGNvZGVjIC0gQ29weWxlZnQgMjAwMy0yMDExIC0gaHR0cDovL3d3dy52aWRlb2xhbi5vcmcveDI2NC5odG1sIC0gb3B0aW9uczogY2FiYWM9MCByZWY9MSBkZWJsb2NrPTE6MDowIGFuYWx5c2U9MHgxOjAgbWU9ZXNhIHN1Ym1lPTkgcHN5PTAgbWl4ZWRfcmVmPTAgbWVfcmFuZ2U9NCBjaHJvbWFfbWU9MSB0cmVsbGlzPTAgOHg4ZGN0PTAgY3FtPTAgZGVhZHpvbmU9MjEsMTEgZmFzdF9wc2tpcD0wIGNocm9tYV9xcF9vZmZzZXQ9MCB0aHJlYWRzPTYgc2xpY2VkX3RocmVhZHM9MCBucj0wIGRlY2ltYXRlPTEgaW50ZXJsYWNlZD0wIGJsdXJheV9jb21wYXQ9MCBjb25zdHJhaW5lZF9pbnRyYT0wIGJmcmFtZXM9MCB3ZWlnaHRwPTAga2V5aW50PTUwIGtleWludF9taW49NSBzY2VuZWN1dD00MCBpbnRyYV9yZWZyZXNoPTAgcmM9Y3FwIG1idHJlZT0wIHFwPTAAgAAAAD5liISscR8A+E4ACAACFoAAITAAAgsAAPgYCoKgoC+L4vi+KAvi+L4YfAEAACMzgABF9AAEUGUgABDJiXnf4AAAAARBmiKUAAAABEGaQpQAAAAEQZpilAAAAARBmoKU';
|
||||
var promise = elem.play();
|
||||
if (promise && promise.catch) {
|
||||
promise.catch(reject);
|
||||
}
|
||||
|
||||
timeout = setTimeout(testAutoplay, 500);
|
||||
}
|
||||
|
||||
catch (e) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
supportsHtmlMediaAutoplay: supportsHtmlMediaAutoplay
|
||||
};
|
||||
});
|
87
bower_components/emby-webcomponents/playback/nowplayinghelper.js
vendored
Normal file
87
bower_components/emby-webcomponents/playback/nowplayinghelper.js
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
define([], function () {
|
||||
'use strict';
|
||||
|
||||
function getNowPlayingNames(nowPlayingItem, includeNonNameInfo) {
|
||||
|
||||
var topItem = nowPlayingItem;
|
||||
var bottomItem = null;
|
||||
var topText = nowPlayingItem.Name;
|
||||
|
||||
if (nowPlayingItem.AlbumId && nowPlayingItem.MediaType === 'Audio') {
|
||||
topItem = {
|
||||
Id: nowPlayingItem.AlbumId,
|
||||
Name: nowPlayingItem.Album,
|
||||
Type: 'MusicAlbum',
|
||||
IsFolder: true
|
||||
};
|
||||
}
|
||||
|
||||
if (nowPlayingItem.MediaType === 'Video') {
|
||||
if (nowPlayingItem.IndexNumber != null) {
|
||||
topText = nowPlayingItem.IndexNumber + " - " + topText;
|
||||
}
|
||||
if (nowPlayingItem.ParentIndexNumber != null) {
|
||||
topText = nowPlayingItem.ParentIndexNumber + "." + topText;
|
||||
}
|
||||
}
|
||||
|
||||
var bottomText = '';
|
||||
|
||||
if (nowPlayingItem.Artists && nowPlayingItem.Artists.length) {
|
||||
|
||||
if (nowPlayingItem.ArtistItems && nowPlayingItem.ArtistItems.length) {
|
||||
|
||||
bottomItem = {
|
||||
Id: nowPlayingItem.ArtistItems[0].Id,
|
||||
Name: nowPlayingItem.ArtistItems[0].Name,
|
||||
Type: 'MusicArtist',
|
||||
IsFolder: true
|
||||
};
|
||||
|
||||
bottomText = bottomItem.Name;
|
||||
} else {
|
||||
bottomText = nowPlayingItem.Artists[0];
|
||||
}
|
||||
}
|
||||
else if (nowPlayingItem.SeriesName || nowPlayingItem.Album) {
|
||||
bottomText = topText;
|
||||
topText = nowPlayingItem.SeriesName || nowPlayingItem.Album;
|
||||
|
||||
bottomItem = topItem;
|
||||
|
||||
if (nowPlayingItem.SeriesId) {
|
||||
topItem = {
|
||||
Id: nowPlayingItem.SeriesId,
|
||||
Name: nowPlayingItem.SeriesName,
|
||||
Type: 'Series',
|
||||
IsFolder: true
|
||||
};
|
||||
} else {
|
||||
topItem = null;
|
||||
}
|
||||
}
|
||||
else if (nowPlayingItem.ProductionYear && includeNonNameInfo !== false) {
|
||||
bottomText = nowPlayingItem.ProductionYear;
|
||||
}
|
||||
|
||||
var list = [];
|
||||
|
||||
list.push({
|
||||
text: topText,
|
||||
item: topItem
|
||||
});
|
||||
|
||||
if (bottomText) {
|
||||
list.push({
|
||||
text: bottomText,
|
||||
item: bottomItem
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
return {
|
||||
getNowPlayingNames: getNowPlayingNames
|
||||
};
|
||||
});
|
2934
bower_components/emby-webcomponents/playback/playbackmanager.js
vendored
Normal file
2934
bower_components/emby-webcomponents/playback/playbackmanager.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
60
bower_components/emby-webcomponents/playback/playbackvalidation.js
vendored
Normal file
60
bower_components/emby-webcomponents/playback/playbackvalidation.js
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
define(['playbackManager'], function (playbackManager) {
|
||||
"use strict";
|
||||
|
||||
return function () {
|
||||
|
||||
var self = this;
|
||||
|
||||
self.name = 'Playback validation';
|
||||
self.type = 'preplayintercept';
|
||||
self.id = 'playbackvalidation';
|
||||
self.order = -1;
|
||||
|
||||
self.intercept = function (options) {
|
||||
|
||||
// Don't care about video backdrops, or theme music or any kind of non-fullscreen playback
|
||||
if (!options.fullscreen) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return validatePlayback(options);
|
||||
};
|
||||
|
||||
function validatePlayback(options) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
require(["registrationServices"], function (registrationServices) {
|
||||
registrationServices.validateFeature('playback', options).then(function (result) {
|
||||
|
||||
if (result && result.enableTimeLimit) {
|
||||
startAutoStopTimer();
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var autoStopTimeout;
|
||||
var lockedTimeLimitMs = 63000;
|
||||
|
||||
function startAutoStopTimer() {
|
||||
stopAutoStopTimer();
|
||||
autoStopTimeout = setTimeout(onAutoStopTimeout, lockedTimeLimitMs);
|
||||
}
|
||||
|
||||
function onAutoStopTimeout() {
|
||||
stopAutoStopTimer();
|
||||
playbackManager.stop();
|
||||
}
|
||||
|
||||
function stopAutoStopTimer() {
|
||||
|
||||
var timeout = autoStopTimeout;
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
autoStopTimeout = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
215
bower_components/emby-webcomponents/playback/playerselection.js
vendored
Normal file
215
bower_components/emby-webcomponents/playback/playerselection.js
vendored
Normal file
@ -0,0 +1,215 @@
|
||||
define(['appSettings', 'events', 'browser', 'loading', 'playbackManager', 'embyRouter', 'globalize', 'apphost'], function (appSettings, events, browser, loading, playbackManager, embyRouter, globalize, appHost) {
|
||||
'use strict';
|
||||
|
||||
var currentDisplayInfo;
|
||||
|
||||
function mirrorItem(info, player) {
|
||||
|
||||
var item = info.item;
|
||||
|
||||
playbackManager.displayContent({
|
||||
|
||||
ItemName: item.Name,
|
||||
ItemId: item.Id,
|
||||
ItemType: item.Type,
|
||||
Context: info.context
|
||||
}, player);
|
||||
}
|
||||
|
||||
function mirrorIfEnabled(info) {
|
||||
|
||||
info = info || currentDisplayInfo;
|
||||
|
||||
if (info && playbackManager.enableDisplayMirroring()) {
|
||||
|
||||
var player = playbackManager.getPlayerInfo();
|
||||
|
||||
if (player) {
|
||||
if (!player.isLocalPlayer && player.supportedCommands.indexOf('DisplayContent') !== -1) {
|
||||
mirrorItem(info, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showPlayerSelection(button) {
|
||||
|
||||
var currentPlayerInfo = playbackManager.getPlayerInfo();
|
||||
|
||||
if (currentPlayerInfo) {
|
||||
if (!currentPlayerInfo.isLocalPlayer) {
|
||||
showActivePlayerMenu(currentPlayerInfo);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var currentPlayerId = currentPlayerInfo ? currentPlayerInfo.id : null;
|
||||
|
||||
loading.show();
|
||||
|
||||
playbackManager.getTargets().then(function (targets) {
|
||||
|
||||
var menuItems = targets.map(function (t) {
|
||||
|
||||
var name = t.name;
|
||||
|
||||
if (t.appName && t.appName !== t.name) {
|
||||
name += " - " + t.appName;
|
||||
}
|
||||
|
||||
return {
|
||||
name: name,
|
||||
id: t.id,
|
||||
selected: currentPlayerId === t.id
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
require(['actionsheet'], function (actionsheet) {
|
||||
|
||||
loading.hide();
|
||||
|
||||
var menuOptions = {
|
||||
title: globalize.translate('sharedcomponents#HeaderSelectPlayer'),
|
||||
items: menuItems,
|
||||
positionTo: button,
|
||||
|
||||
resolveOnClick: true
|
||||
};
|
||||
|
||||
// Unfortunately we can't allow the url to change or chromecast will throw a security error
|
||||
// Might be able to solve this in the future by moving the dialogs to hashbangs
|
||||
if (!(!browser.chrome || appHost.supports('castmenuhashchange'))) {
|
||||
menuOptions.enableHistory = false;
|
||||
}
|
||||
|
||||
actionsheet.show(menuOptions).then(function (id) {
|
||||
|
||||
var target = targets.filter(function (t) {
|
||||
return t.id === id;
|
||||
})[0];
|
||||
|
||||
playbackManager.trySetActivePlayer(target.playerName, target);
|
||||
|
||||
mirrorIfEnabled();
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showActivePlayerMenu(playerInfo) {
|
||||
|
||||
require(['dialogHelper', 'dialog', 'emby-checkbox', 'emby-button'], function (dialogHelper) {
|
||||
showActivePlayerMenuInternal(dialogHelper, playerInfo);
|
||||
});
|
||||
}
|
||||
|
||||
function showActivePlayerMenuInternal(dialogHelper, playerInfo) {
|
||||
|
||||
var html = '';
|
||||
|
||||
var dialogOptions = {
|
||||
removeOnClose: true
|
||||
};
|
||||
|
||||
dialogOptions.modal = false;
|
||||
dialogOptions.entryAnimationDuration = 160;
|
||||
dialogOptions.exitAnimationDuration = 160;
|
||||
dialogOptions.autoFocus = false;
|
||||
|
||||
var dlg = dialogHelper.createDialog(dialogOptions);
|
||||
|
||||
dlg.classList.add('promptDialog');
|
||||
|
||||
html += '<div class="promptDialogContent" style="padding:1.5em;">';
|
||||
html += '<h2 style="margin-top:.5em;">';
|
||||
html += (playerInfo.deviceName || playerInfo.name);
|
||||
html += '</h2>';
|
||||
|
||||
html += '<div>';
|
||||
|
||||
if (playerInfo.supportedCommands.indexOf('DisplayContent') !== -1) {
|
||||
|
||||
html += '<label class="checkboxContainer">';
|
||||
var checkedHtml = playbackManager.enableDisplayMirroring() ? ' checked' : '';
|
||||
html += '<input type="checkbox" is="emby-checkbox" class="chkMirror"' + checkedHtml + '/>';
|
||||
html += '<span>' + globalize.translate('sharedcomponents#EnableDisplayMirroring') + '</span>';
|
||||
html += '</label>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
html += '<div style="margin-top:1em;display:flex;justify-content: flex-end;">';
|
||||
|
||||
html += '<button is="emby-button" type="button" class="button-flat button-accent-flat btnRemoteControl promptDialogButton">' + globalize.translate('sharedcomponents#HeaderRemoteControl') + '</button>';
|
||||
html += '<button is="emby-button" type="button" class="button-flat button-accent-flat btnDisconnect promptDialogButton ">' + globalize.translate('sharedcomponents#Disconnect') + '</button>';
|
||||
html += '<button is="emby-button" type="button" class="button-flat button-accent-flat btnCancel promptDialogButton">' + globalize.translate('sharedcomponents#ButtonCancel') + '</button>';
|
||||
html += '</div>';
|
||||
|
||||
html += '</div>';
|
||||
dlg.innerHTML = html;
|
||||
|
||||
var chkMirror = dlg.querySelector('.chkMirror');
|
||||
|
||||
if (chkMirror) {
|
||||
chkMirror.addEventListener('change', onMirrorChange);
|
||||
}
|
||||
|
||||
var destination = '';
|
||||
|
||||
var btnRemoteControl = dlg.querySelector('.btnRemoteControl');
|
||||
if (btnRemoteControl) {
|
||||
btnRemoteControl.addEventListener('click', function () {
|
||||
destination = 'nowplaying.html';
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
}
|
||||
|
||||
dlg.querySelector('.btnDisconnect').addEventListener('click', function () {
|
||||
playbackManager.disconnectFromPlayer();
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
|
||||
dlg.querySelector('.btnCancel').addEventListener('click', function () {
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
|
||||
dialogHelper.open(dlg).then(function () {
|
||||
if (destination) {
|
||||
embyRouter.show(destination);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onMirrorChange() {
|
||||
playbackManager.enableDisplayMirroring(this.checked);
|
||||
}
|
||||
|
||||
document.addEventListener('viewbeforeshow', function () {
|
||||
currentDisplayInfo = null;
|
||||
});
|
||||
|
||||
document.addEventListener('viewshow', function (e) {
|
||||
|
||||
var state = e.detail.state || {};
|
||||
var item = state.item;
|
||||
|
||||
if (item && item.ServerId) {
|
||||
mirrorIfEnabled({
|
||||
item: item
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
events.on(appSettings, 'change', function (e, name) {
|
||||
if (name === 'displaymirror') {
|
||||
mirrorIfEnabled();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
show: showPlayerSelection
|
||||
};
|
||||
});
|
164
bower_components/emby-webcomponents/playback/playersettingsmenu.js
vendored
Normal file
164
bower_components/emby-webcomponents/playback/playersettingsmenu.js
vendored
Normal file
@ -0,0 +1,164 @@
|
||||
define(['actionsheet', 'datetime', 'playbackManager', 'globalize', 'appSettings', 'qualityoptions'], function (actionsheet, datetime, playbackManager, globalize, appSettings, qualityoptions) {
|
||||
'use strict';
|
||||
|
||||
function showQualityMenu(player, btn) {
|
||||
|
||||
var videoStream = playbackManager.currentMediaSource(player).MediaStreams.filter(function (stream) {
|
||||
return stream.Type === "Video";
|
||||
})[0];
|
||||
var videoWidth = videoStream ? videoStream.Width : null;
|
||||
|
||||
var options = qualityoptions.getVideoQualityOptions({
|
||||
currentMaxBitrate: playbackManager.getMaxStreamingBitrate(player),
|
||||
isAutomaticBitrateEnabled: playbackManager.enableAutomaticBitrateDetection(player),
|
||||
videoWidth: videoWidth,
|
||||
enableAuto: true
|
||||
});
|
||||
|
||||
var menuItems = options.map(function (o) {
|
||||
|
||||
var opt = {
|
||||
name: o.name,
|
||||
id: o.bitrate,
|
||||
secondaryText: o.secondaryText
|
||||
};
|
||||
|
||||
if (o.selected) {
|
||||
opt.selected = true;
|
||||
}
|
||||
|
||||
return opt;
|
||||
});
|
||||
|
||||
var selectedId = options.filter(function (o) {
|
||||
return o.selected;
|
||||
});
|
||||
|
||||
selectedId = selectedId.length ? selectedId[0].bitrate : null;
|
||||
|
||||
return actionsheet.show({
|
||||
items: menuItems,
|
||||
positionTo: btn
|
||||
|
||||
}).then(function (id) {
|
||||
var bitrate = parseInt(id);
|
||||
if (bitrate !== selectedId) {
|
||||
|
||||
playbackManager.setMaxStreamingBitrate({
|
||||
|
||||
enableAutomaticBitrateDetection: bitrate ? false : true,
|
||||
maxBitrate: bitrate
|
||||
|
||||
}, player);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showSettingsMenu(player, btn) {
|
||||
|
||||
}
|
||||
|
||||
function getQualitySecondaryText(player) {
|
||||
|
||||
return playbackManager.getPlayerState(player).then(function(state) {
|
||||
var isAutoEnabled = playbackManager.enableAutomaticBitrateDetection(player);
|
||||
var currentMaxBitrate = playbackManager.getMaxStreamingBitrate(player);
|
||||
|
||||
var videoStream = playbackManager.currentMediaSource(player).MediaStreams.filter(function (stream) {
|
||||
return stream.Type === "Video";
|
||||
})[0];
|
||||
var videoWidth = videoStream ? videoStream.Width : null;
|
||||
|
||||
var options = qualityoptions.getVideoQualityOptions({
|
||||
currentMaxBitrate: playbackManager.getMaxStreamingBitrate(player),
|
||||
isAutomaticBitrateEnabled: playbackManager.enableAutomaticBitrateDetection(player),
|
||||
videoWidth: videoWidth,
|
||||
enableAuto: true
|
||||
});
|
||||
|
||||
var menuItems = options.map(function (o) {
|
||||
|
||||
var opt = {
|
||||
name: o.name,
|
||||
id: o.bitrate,
|
||||
secondaryText: o.secondaryText
|
||||
};
|
||||
|
||||
if (o.selected) {
|
||||
opt.selected = true;
|
||||
}
|
||||
|
||||
return opt;
|
||||
});
|
||||
|
||||
var selectedOption = options.filter(function (o) {
|
||||
return o.selected;
|
||||
});
|
||||
|
||||
if (!selectedOption.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
selectedOption = selectedOption[0];
|
||||
|
||||
var text = selectedOption.name;
|
||||
|
||||
if (selectedOption.autoText) {
|
||||
if (state.PlayState && state.PlayState.PlayMethod !== 'Transcode') {
|
||||
text += ' - Direct';
|
||||
} else {
|
||||
text += ' ' + selectedOption.autoText;
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
});
|
||||
}
|
||||
|
||||
function show(options) {
|
||||
|
||||
var player = options.player;
|
||||
|
||||
return getQualitySecondaryText(player).then(function (secondaryQualityText) {
|
||||
var mediaType = options.mediaType;
|
||||
//return showQualityMenu(player, options.positionTo);
|
||||
|
||||
var menuItems = [];
|
||||
|
||||
menuItems.push({
|
||||
name: globalize.translate('sharedcomponents#Quality'),
|
||||
id: 'quality',
|
||||
secondaryText: secondaryQualityText
|
||||
});
|
||||
|
||||
//menuItems.push({
|
||||
// name: globalize.translate('sharedcomponents#Settings'),
|
||||
// id: 'settings'
|
||||
//});
|
||||
|
||||
return actionsheet.show({
|
||||
|
||||
items: menuItems,
|
||||
positionTo: options.positionTo
|
||||
|
||||
}).then(function (id) {
|
||||
|
||||
switch (id) {
|
||||
|
||||
case 'quality':
|
||||
return showQualityMenu(player, options.positionTo);
|
||||
case 'settings':
|
||||
return showSettingsMenu(player, options.positionTo);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return Promise.reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
show: show
|
||||
};
|
||||
});
|
48
bower_components/emby-webcomponents/playback/remotecontrolautoplay.js
vendored
Normal file
48
bower_components/emby-webcomponents/playback/remotecontrolautoplay.js
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
define(['events', 'playbackManager'], function (events, playbackManager) {
|
||||
'use strict';
|
||||
|
||||
function transferPlayback(oldPlayer, newPlayer) {
|
||||
|
||||
playbackManager.getPlayerState(oldPlayer).then(function (state) {
|
||||
|
||||
var item = state.NowPlayingItem;
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
var playState = state.PlayState || {};
|
||||
|
||||
playbackManager.stop(oldPlayer);
|
||||
|
||||
var itemId = item.Id;
|
||||
var resumePositionTicks = playState.PositionTicks || 0;
|
||||
|
||||
playbackManager.play({
|
||||
ids: [itemId],
|
||||
startPositionTicks: resumePositionTicks
|
||||
}, newPlayer);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
events.on(playbackManager, 'playerchange', function (e, newPlayer, newTarget, oldPlayer) {
|
||||
|
||||
if (!oldPlayer || !newPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!oldPlayer.isLocalPlayer) {
|
||||
console.log('Skipping remote control autoplay because oldPlayer is not a local player');
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPlayer.isLocalPlayer) {
|
||||
console.log('Skipping remote control autoplay because newPlayer is a local player');
|
||||
return;
|
||||
}
|
||||
|
||||
transferPlayback(oldPlayer, newPlayer);
|
||||
});
|
||||
|
||||
});
|
152
bower_components/emby-webcomponents/pluginmanager.js
vendored
Normal file
152
bower_components/emby-webcomponents/pluginmanager.js
vendored
Normal file
@ -0,0 +1,152 @@
|
||||
define(['events'], function (events) {
|
||||
'use strict';
|
||||
|
||||
function pluginManager() {
|
||||
|
||||
var self = this;
|
||||
var plugins = [];
|
||||
|
||||
// In lieu of automatic discovery, plugins will register dynamic objects
|
||||
// Each object will have the following properties:
|
||||
// name
|
||||
// type (skin, screensaver, etc)
|
||||
self.register = function (obj) {
|
||||
|
||||
plugins.push(obj);
|
||||
events.trigger(self, 'registered', [obj]);
|
||||
};
|
||||
|
||||
self.ofType = function (type) {
|
||||
|
||||
return plugins.filter(function (o) {
|
||||
return o.type === type;
|
||||
});
|
||||
};
|
||||
|
||||
self.plugins = function () {
|
||||
return plugins;
|
||||
};
|
||||
|
||||
self.mapRoute = function (plugin, route) {
|
||||
|
||||
if (typeof plugin === 'string') {
|
||||
plugin = plugins.filter(function (p) {
|
||||
return (p.id || p.packageName) === plugin;
|
||||
})[0];
|
||||
}
|
||||
|
||||
route = route.path || route;
|
||||
|
||||
if (route.toLowerCase().indexOf('http') === 0) {
|
||||
return route;
|
||||
}
|
||||
|
||||
return '/plugins/' + plugin.id + '/' + route;
|
||||
};
|
||||
|
||||
// TODO: replace with each plugin version
|
||||
var cacheParam = new Date().getTime();
|
||||
|
||||
self.mapPath = function (plugin, path, addCacheParam) {
|
||||
|
||||
if (typeof plugin === 'string') {
|
||||
plugin = plugins.filter(function (p) {
|
||||
return (p.id || p.packageName) === plugin;
|
||||
})[0];
|
||||
}
|
||||
|
||||
var url = plugin.baseUrl + '/' + path;
|
||||
|
||||
if (addCacheParam) {
|
||||
url += url.indexOf('?') === -1 ? '?' : '&';
|
||||
url += 'v=' + cacheParam;
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
function loadStrings(plugin, globalize) {
|
||||
var strings = plugin.getTranslations ? plugin.getTranslations() : [];
|
||||
return globalize.loadStrings({
|
||||
name: plugin.id || plugin.packageName,
|
||||
strings: strings
|
||||
});
|
||||
}
|
||||
|
||||
function definePluginRoute(route, plugin) {
|
||||
|
||||
route.contentPath = self.mapPath(plugin, route.path);
|
||||
route.path = self.mapRoute(plugin, route);
|
||||
|
||||
Emby.App.defineRoute(route, plugin.id);
|
||||
}
|
||||
|
||||
self.loadPlugin = function (url) {
|
||||
|
||||
console.log('Loading plugin: ' + url);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
require([url, 'globalize', 'embyRouter'], function (pluginFactory, globalize, embyRouter) {
|
||||
|
||||
var plugin = new pluginFactory();
|
||||
|
||||
// See if it's already installed
|
||||
var existing = plugins.filter(function (p) {
|
||||
return p.id === plugin.id;
|
||||
})[0];
|
||||
|
||||
if (existing) {
|
||||
resolve(url);
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.installUrl = url;
|
||||
|
||||
var urlLower = url.toLowerCase();
|
||||
if (urlLower.indexOf('http:') === -1 && urlLower.indexOf('https:') === -1 && urlLower.indexOf('file:') === -1) {
|
||||
if (url.indexOf(embyRouter.baseUrl()) !== 0) {
|
||||
|
||||
url = embyRouter.baseUrl() + '/' + url;
|
||||
}
|
||||
}
|
||||
|
||||
var separatorIndex = Math.max(url.lastIndexOf('/'), url.lastIndexOf('\\'));
|
||||
plugin.baseUrl = url.substring(0, separatorIndex);
|
||||
|
||||
var paths = {};
|
||||
paths[plugin.id] = plugin.baseUrl;
|
||||
|
||||
requirejs.config({
|
||||
waitSeconds: 0,
|
||||
paths: paths
|
||||
});
|
||||
|
||||
self.register(plugin);
|
||||
|
||||
if (plugin.getRoutes) {
|
||||
plugin.getRoutes().forEach(function (route) {
|
||||
definePluginRoute(route, plugin);
|
||||
});
|
||||
}
|
||||
|
||||
if (plugin.type === 'skin') {
|
||||
|
||||
// translations won't be loaded for skins until needed
|
||||
resolve(plugin);
|
||||
} else {
|
||||
|
||||
loadStrings(plugin, globalize).then(function () {
|
||||
resolve(plugin);
|
||||
}, reject);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
var instance = new pluginManager();
|
||||
window.Emby = window.Emby || {};
|
||||
window.Emby.PluginManager = instance;
|
||||
return instance;
|
||||
});
|
44
bower_components/emby-webcomponents/recordingcreator/recordingfields.css
vendored
Normal file
44
bower_components/emby-webcomponents/recordingcreator/recordingfields.css
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
.recordingButton {
|
||||
margin-left: 0;
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
.recordingIcon {
|
||||
font-size: 1.3em !important;
|
||||
}
|
||||
|
||||
.recordingIcon-active {
|
||||
color: #cc3333;
|
||||
}
|
||||
|
||||
.manageButtonText {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.recordSeriesContainer {
|
||||
margin-bottom: .8em;
|
||||
}
|
||||
|
||||
.recordingFields-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media all and (max-width: 440px) {
|
||||
|
||||
.manageButtonText {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.recordingButton {
|
||||
width: auto;
|
||||
margin-right: 1.5em !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 440px) {
|
||||
|
||||
.manageButtonIcon {
|
||||
font-size: 90% !important;
|
||||
}
|
||||
}
|
20
bower_components/emby-webcomponents/resourcelocks/nullresourcelock.js
vendored
Normal file
20
bower_components/emby-webcomponents/resourcelocks/nullresourcelock.js
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
define([], function () {
|
||||
'use strict';
|
||||
|
||||
function ResourceLockInstance() {
|
||||
}
|
||||
|
||||
ResourceLockInstance.prototype.acquire = function () {
|
||||
this._isHeld = true;
|
||||
};
|
||||
|
||||
ResourceLockInstance.prototype.isHeld = function () {
|
||||
return this._isHeld === true;
|
||||
};
|
||||
|
||||
ResourceLockInstance.prototype.release = function () {
|
||||
this._isHeld = false;
|
||||
};
|
||||
|
||||
return ResourceLockInstance;
|
||||
});
|
37
bower_components/emby-webcomponents/resourcelocks/resourcelockmanager.js
vendored
Normal file
37
bower_components/emby-webcomponents/resourcelocks/resourcelockmanager.js
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
define([], function () {
|
||||
'use strict';
|
||||
|
||||
function getRequirePromise(deps) {
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
require(deps, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
function requestResourceLock(resource) {
|
||||
return getRequirePromise([resource]).then(function (factory) {
|
||||
return new factory();
|
||||
});
|
||||
}
|
||||
|
||||
function request(type) {
|
||||
|
||||
switch (type) {
|
||||
|
||||
case 'wake':
|
||||
return requestResourceLock('wakeLock');
|
||||
case 'screen':
|
||||
return requestResourceLock('screenLock');
|
||||
case 'network':
|
||||
return requestResourceLock('networkLock');
|
||||
default:
|
||||
return Promise.reject();
|
||||
}
|
||||
return Promise.resolve(new ResourceLockInstance(type));
|
||||
}
|
||||
|
||||
return {
|
||||
request: request
|
||||
};
|
||||
});
|
100
bower_components/emby-webcomponents/sanitizefilename.js
vendored
Normal file
100
bower_components/emby-webcomponents/sanitizefilename.js
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
// From https://github.com/parshap/node-sanitize-filename
|
||||
|
||||
define([], function () {
|
||||
'use strict';
|
||||
|
||||
var illegalRe = /[\/\?<>\\:\*\|":]/g;
|
||||
var controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
||||
var reservedRe = /^\.+$/;
|
||||
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
var windowsTrailingRe = /[\. ]+$/;
|
||||
|
||||
function isHighSurrogate(codePoint) {
|
||||
return codePoint >= 0xd800 && codePoint <= 0xdbff;
|
||||
}
|
||||
|
||||
function isLowSurrogate(codePoint) {
|
||||
return codePoint >= 0xdc00 && codePoint <= 0xdfff;
|
||||
}
|
||||
|
||||
function getByteLength(string) {
|
||||
if (typeof string !== "string") {
|
||||
throw new Error("Input must be string");
|
||||
}
|
||||
|
||||
var charLength = string.length;
|
||||
var byteLength = 0;
|
||||
var codePoint = null;
|
||||
var prevCodePoint = null;
|
||||
for (var i = 0; i < charLength; i++) {
|
||||
codePoint = string.charCodeAt(i);
|
||||
// handle 4-byte non-BMP chars
|
||||
// low surrogate
|
||||
if (isLowSurrogate(codePoint)) {
|
||||
// when parsing previous hi-surrogate, 3 is added to byteLength
|
||||
if (prevCodePoint != null && isHighSurrogate(prevCodePoint)) {
|
||||
byteLength += 1;
|
||||
}
|
||||
else {
|
||||
byteLength += 3;
|
||||
}
|
||||
}
|
||||
else if (codePoint <= 0x7f) {
|
||||
byteLength += 1;
|
||||
}
|
||||
else if (codePoint >= 0x80 && codePoint <= 0x7ff) {
|
||||
byteLength += 2;
|
||||
}
|
||||
else if (codePoint >= 0x800 && codePoint <= 0xffff) {
|
||||
byteLength += 3;
|
||||
}
|
||||
prevCodePoint = codePoint;
|
||||
}
|
||||
|
||||
return byteLength;
|
||||
}
|
||||
|
||||
function truncate(string, byteLength) {
|
||||
if (typeof string !== "string") {
|
||||
throw new Error("Input must be string");
|
||||
}
|
||||
|
||||
var charLength = string.length;
|
||||
var curByteLength = 0;
|
||||
var codePoint;
|
||||
var segment;
|
||||
|
||||
for (var i = 0; i < charLength; i += 1) {
|
||||
codePoint = string.charCodeAt(i);
|
||||
segment = string[i];
|
||||
|
||||
if (isHighSurrogate(codePoint) && isLowSurrogate(string.charCodeAt(i + 1))) {
|
||||
i += 1;
|
||||
segment += string[i];
|
||||
}
|
||||
|
||||
curByteLength += getByteLength(segment);
|
||||
|
||||
if (curByteLength === byteLength) {
|
||||
return string.slice(0, i + 1);
|
||||
}
|
||||
else if (curByteLength > byteLength) {
|
||||
return string.slice(0, i - segment.length + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
return {
|
||||
sanitize: function (input, replacement) {
|
||||
var sanitized = input
|
||||
.replace(illegalRe, replacement)
|
||||
.replace(controlRe, replacement)
|
||||
.replace(reservedRe, replacement)
|
||||
.replace(windowsReservedRe, replacement)
|
||||
.replace(windowsTrailingRe, replacement);
|
||||
return truncate(sanitized, 255);
|
||||
}
|
||||
};
|
||||
});
|
548
bower_components/emby-webcomponents/sessionplayer.js
vendored
Normal file
548
bower_components/emby-webcomponents/sessionplayer.js
vendored
Normal file
@ -0,0 +1,548 @@
|
||||
define(['playbackManager', 'events', 'serverNotifications', 'connectionManager'], function (playbackManager, events, serverNotifications, connectionManager) {
|
||||
'use strict';
|
||||
|
||||
function getActivePlayerId() {
|
||||
var info = playbackManager.getPlayerInfo();
|
||||
return info ? info.id : null;
|
||||
}
|
||||
|
||||
function sendPlayCommand(apiClient, options, playType) {
|
||||
|
||||
var sessionId = getActivePlayerId();
|
||||
|
||||
var ids = options.ids || options.items.map(function (i) {
|
||||
return i.Id;
|
||||
});
|
||||
|
||||
var remoteOptions = {
|
||||
ItemIds: ids.join(','),
|
||||
|
||||
PlayCommand: playType
|
||||
};
|
||||
|
||||
if (options.startPositionTicks) {
|
||||
remoteOptions.startPositionTicks = options.startPositionTicks;
|
||||
}
|
||||
|
||||
return apiClient.sendPlayCommand(sessionId, remoteOptions);
|
||||
}
|
||||
|
||||
function sendPlayStateCommand(apiClient, command, options) {
|
||||
|
||||
var sessionId = getActivePlayerId();
|
||||
|
||||
apiClient.sendPlayStateCommand(sessionId, command, options);
|
||||
}
|
||||
|
||||
return function () {
|
||||
|
||||
var self = this;
|
||||
|
||||
self.name = 'Remote Control';
|
||||
self.type = 'mediaplayer';
|
||||
self.isLocalPlayer = false;
|
||||
self.id = 'remoteplayer';
|
||||
|
||||
var currentServerId;
|
||||
|
||||
function getCurrentApiClient() {
|
||||
|
||||
if (currentServerId) {
|
||||
return connectionManager.getApiClient(currentServerId);
|
||||
}
|
||||
|
||||
return connectionManager.currentApiClient();
|
||||
}
|
||||
|
||||
function sendCommandByName(name, options) {
|
||||
|
||||
var command = {
|
||||
Name: name
|
||||
};
|
||||
|
||||
if (options) {
|
||||
command.Arguments = options;
|
||||
}
|
||||
|
||||
self.sendCommand(command);
|
||||
}
|
||||
|
||||
self.sendCommand = function (command) {
|
||||
|
||||
var sessionId = getActivePlayerId();
|
||||
|
||||
var apiClient = getCurrentApiClient();
|
||||
apiClient.sendCommand(sessionId, command);
|
||||
};
|
||||
|
||||
self.play = function (options) {
|
||||
|
||||
var playOptions = {};
|
||||
playOptions.ids = options.ids || options.items.map(function (i) {
|
||||
return i.Id;
|
||||
});
|
||||
|
||||
if (options.startPositionTicks) {
|
||||
playOptions.startPositionTicks = options.startPositionTicks;
|
||||
}
|
||||
|
||||
return sendPlayCommand(getCurrentApiClient(), playOptions, 'PlayNow');
|
||||
};
|
||||
|
||||
self.shuffle = function (item) {
|
||||
|
||||
sendPlayCommand(getCurrentApiClient(), { ids: [item.Id] }, 'PlayShuffle');
|
||||
};
|
||||
|
||||
self.instantMix = function (item) {
|
||||
|
||||
sendPlayCommand(getCurrentApiClient(), { ids: [item.Id] }, 'PlayInstantMix');
|
||||
};
|
||||
|
||||
self.queue = function (options) {
|
||||
|
||||
sendPlayCommand(getCurrentApiClient(), options, 'PlayNext');
|
||||
};
|
||||
|
||||
self.queueNext = function (options) {
|
||||
|
||||
sendPlayCommand(getCurrentApiClient(), options, 'PlayLast');
|
||||
};
|
||||
|
||||
self.canPlayMediaType = function (mediaType) {
|
||||
|
||||
mediaType = (mediaType || '').toLowerCase();
|
||||
return mediaType === 'audio' || mediaType === 'video';
|
||||
};
|
||||
|
||||
self.canQueueMediaType = function (mediaType) {
|
||||
return self.canPlayMediaType(mediaType);
|
||||
};
|
||||
|
||||
self.stop = function () {
|
||||
sendPlayStateCommand(getCurrentApiClient(), 'stop');
|
||||
};
|
||||
|
||||
self.nextTrack = function () {
|
||||
sendPlayStateCommand(getCurrentApiClient(), 'nextTrack');
|
||||
};
|
||||
|
||||
self.previousTrack = function () {
|
||||
sendPlayStateCommand(getCurrentApiClient(), 'previousTrack');
|
||||
};
|
||||
|
||||
self.seek = function (positionTicks) {
|
||||
sendPlayStateCommand(getCurrentApiClient(), 'seek',
|
||||
{
|
||||
SeekPositionTicks: positionTicks
|
||||
});
|
||||
};
|
||||
|
||||
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.getVolume = function () {
|
||||
var state = self.lastPlayerData || {};
|
||||
state = state.PlayState || {};
|
||||
return state.VolumeLevel;
|
||||
};
|
||||
|
||||
self.pause = function () {
|
||||
sendPlayStateCommand(getCurrentApiClient(), 'Pause');
|
||||
};
|
||||
|
||||
self.unpause = function () {
|
||||
sendPlayStateCommand(getCurrentApiClient(), 'Unpause');
|
||||
};
|
||||
|
||||
self.setMute = function (isMuted) {
|
||||
|
||||
if (isMuted) {
|
||||
sendCommandByName('Mute');
|
||||
} else {
|
||||
sendCommandByName('Unmute');
|
||||
}
|
||||
};
|
||||
|
||||
self.toggleMute = function () {
|
||||
sendCommandByName('ToggleMute');
|
||||
};
|
||||
|
||||
self.setVolume = function (vol) {
|
||||
sendCommandByName('SetVolume', {
|
||||
Volume: vol
|
||||
});
|
||||
};
|
||||
|
||||
self.volumeUp = function () {
|
||||
sendCommandByName('VolumeUp');
|
||||
};
|
||||
|
||||
self.volumeDown = function () {
|
||||
sendCommandByName('VolumeDown');
|
||||
};
|
||||
|
||||
self.toggleFullscreen = function () {
|
||||
sendCommandByName('ToggleFullscreen');
|
||||
};
|
||||
|
||||
self.audioTracks = function () {
|
||||
var state = self.lastPlayerData || {};
|
||||
state = state.NowPlayingItem || {};
|
||||
var streams = state.MediaStreams || [];
|
||||
return streams.filter(function (s) {
|
||||
return s.Type === 'Audio';
|
||||
});
|
||||
};
|
||||
|
||||
self.getAudioStreamIndex = function () {
|
||||
var state = self.lastPlayerData || {};
|
||||
state = state.PlayState || {};
|
||||
return state.AudioStreamIndex;
|
||||
};
|
||||
|
||||
self.setAudioStreamIndex = function (index) {
|
||||
sendCommandByName('SetAudioStreamIndex', {
|
||||
Index: index
|
||||
});
|
||||
};
|
||||
|
||||
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) {
|
||||
sendCommandByName('SetSubtitleStreamIndex', {
|
||||
Index: index
|
||||
});
|
||||
};
|
||||
|
||||
self.getMaxStreamingBitrate = function () {
|
||||
|
||||
};
|
||||
|
||||
self.setMaxStreamingBitrate = function (options) {
|
||||
|
||||
};
|
||||
|
||||
self.isFullscreen = function () {
|
||||
|
||||
};
|
||||
|
||||
self.toggleFullscreen = function () {
|
||||
|
||||
};
|
||||
|
||||
self.getRepeatMode = function () {
|
||||
|
||||
};
|
||||
|
||||
self.setRepeatMode = function (mode) {
|
||||
|
||||
sendCommandByName('SetRepeatMode', {
|
||||
RepeatMode: mode
|
||||
});
|
||||
};
|
||||
|
||||
self.displayContent = function (options) {
|
||||
|
||||
sendCommandByName('DisplayContent', options);
|
||||
};
|
||||
|
||||
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.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 () {
|
||||
|
||||
var apiClient = getCurrentApiClient();
|
||||
|
||||
if (apiClient) {
|
||||
return apiClient.getSessions().then(function (sessions) {
|
||||
|
||||
var currentTargetId = getActivePlayerId();
|
||||
|
||||
// Update existing data
|
||||
//updateSessionInfo(popup, msg.Data);
|
||||
var session = sessions.filter(function (s) {
|
||||
return s.Id === currentTargetId;
|
||||
})[0];
|
||||
|
||||
if (session) {
|
||||
session = getPlayerState(session);
|
||||
}
|
||||
|
||||
return session;
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
};
|
||||
|
||||
var pollInterval;
|
||||
|
||||
function onPollIntervalFired() {
|
||||
|
||||
var apiClient = getCurrentApiClient();
|
||||
if (!apiClient.isWebSocketOpen()) {
|
||||
|
||||
if (apiClient) {
|
||||
apiClient.getSessions().then(function (sessions) {
|
||||
processUpdatedSessions(sessions, apiClient);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.subscribeToPlayerUpdates = function () {
|
||||
|
||||
self.isUpdating = true;
|
||||
|
||||
var apiClient = getCurrentApiClient();
|
||||
if (apiClient.isWebSocketOpen()) {
|
||||
|
||||
apiClient.sendWebSocketMessage("SessionsStart", "100,800");
|
||||
}
|
||||
if (pollInterval) {
|
||||
clearInterval(pollInterval);
|
||||
pollInterval = null;
|
||||
}
|
||||
pollInterval = setInterval(onPollIntervalFired, 5000);
|
||||
};
|
||||
|
||||
function unsubscribeFromPlayerUpdates() {
|
||||
|
||||
self.isUpdating = true;
|
||||
|
||||
var apiClient = getCurrentApiClient();
|
||||
if (apiClient.isWebSocketOpen()) {
|
||||
|
||||
apiClient.sendWebSocketMessage("SessionsStop");
|
||||
}
|
||||
if (pollInterval) {
|
||||
clearInterval(pollInterval);
|
||||
pollInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
var playerListenerCount = 0;
|
||||
self.beginPlayerUpdates = function () {
|
||||
|
||||
if (playerListenerCount <= 0) {
|
||||
|
||||
playerListenerCount = 0;
|
||||
|
||||
self.subscribeToPlayerUpdates();
|
||||
}
|
||||
|
||||
playerListenerCount++;
|
||||
};
|
||||
|
||||
self.endPlayerUpdates = function () {
|
||||
|
||||
playerListenerCount--;
|
||||
|
||||
if (playerListenerCount <= 0) {
|
||||
|
||||
unsubscribeFromPlayerUpdates();
|
||||
playerListenerCount = 0;
|
||||
}
|
||||
};
|
||||
|
||||
self.getTargets = function () {
|
||||
|
||||
var apiClient = getCurrentApiClient();
|
||||
|
||||
var sessionQuery = {
|
||||
ControllableByUserId: apiClient.getCurrentUserId()
|
||||
};
|
||||
|
||||
if (apiClient) {
|
||||
return apiClient.getSessions(sessionQuery).then(function (sessions) {
|
||||
|
||||
return sessions.filter(function (s) {
|
||||
return s.DeviceId !== apiClient.deviceId();
|
||||
|
||||
}).map(function (s) {
|
||||
return {
|
||||
name: s.DeviceName,
|
||||
deviceName: s.DeviceName,
|
||||
id: s.Id,
|
||||
playerName: self.name,
|
||||
appName: s.Client,
|
||||
playableMediaTypes: s.PlayableMediaTypes,
|
||||
isLocalPlayer: false,
|
||||
supportedCommands: s.SupportedCommands
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
} else {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
};
|
||||
|
||||
self.tryPair = function (target) {
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
function getPlayerState(session) {
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function firePlaybackEvent(name, session) {
|
||||
|
||||
var state = getPlayerState(session);
|
||||
|
||||
normalizeImages(state);
|
||||
|
||||
self.lastPlayerData = state;
|
||||
|
||||
events.trigger(self, name, [state]);
|
||||
}
|
||||
|
||||
function onWebSocketConnectionChange() {
|
||||
|
||||
// Reconnect
|
||||
if (self.isUpdating) {
|
||||
self.subscribeToPlayerUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
function processUpdatedSessions(sessions, apiClient) {
|
||||
|
||||
var serverId = apiClient.serverId();
|
||||
|
||||
sessions.map(function (s) {
|
||||
if (s.NowPlayingItem) {
|
||||
s.NowPlayingItem.ServerId = serverId;
|
||||
}
|
||||
});
|
||||
|
||||
var currentTargetId = getActivePlayerId();
|
||||
// Update existing data
|
||||
//updateSessionInfo(popup, msg.Data);
|
||||
var session = sessions.filter(function (s) {
|
||||
return s.Id === currentTargetId;
|
||||
})[0];
|
||||
|
||||
if (session) {
|
||||
firePlaybackEvent('statechange', session);
|
||||
firePlaybackEvent('timeupdate', session);
|
||||
firePlaybackEvent('pause', session);
|
||||
}
|
||||
}
|
||||
|
||||
events.on(serverNotifications, 'Sessions', function (e, apiClient, data) {
|
||||
processUpdatedSessions(data, apiClient);
|
||||
});
|
||||
|
||||
events.on(serverNotifications, 'SessionEnded', function (e, apiClient, data) {
|
||||
console.log("Server reports another session ended");
|
||||
|
||||
if (getActivePlayerId() === data.Id) {
|
||||
playbackManager.setDefaultPlayerActive();
|
||||
}
|
||||
});
|
||||
|
||||
events.on(serverNotifications, 'PlaybackStart', function (e, apiClient, data) {
|
||||
if (data.DeviceId !== apiClient.deviceId()) {
|
||||
if (getActivePlayerId() === data.Id) {
|
||||
firePlaybackEvent('playbackstart', data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
events.on(serverNotifications, 'PlaybackStopped', function (e, apiClient, data) {
|
||||
if (data.DeviceId !== apiClient.deviceId()) {
|
||||
if (getActivePlayerId() === data.Id) {
|
||||
firePlaybackEvent('playbackstop', data);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
506
bower_components/emby-webcomponents/sync/syncjobeditor.js
vendored
Normal file
506
bower_components/emby-webcomponents/sync/syncjobeditor.js
vendored
Normal file
@ -0,0 +1,506 @@
|
||||
define(['connectionManager', 'serverNotifications', 'events', 'datetime', 'dom', 'imageLoader', 'loading', 'globalize', 'apphost', 'layoutManager', 'scrollHelper', 'dialogHelper', 'shell', 'listViewStyle', 'paper-icon-button-light', 'emby-button', 'formDialogStyle'], function (connectionManager, serverNotifications, events, datetime, dom, imageLoader, loading, globalize, appHost, layoutManager, scrollHelper, dialogHelper, shell) {
|
||||
'use strict';
|
||||
|
||||
function renderJob(context, job, dialogOptions) {
|
||||
|
||||
require(['syncDialog'], function (syncDialog) {
|
||||
syncDialog.renderForm({
|
||||
elem: context.querySelector('.syncJobFormContent'),
|
||||
dialogOptions: dialogOptions,
|
||||
dialogOptionsFn: getTargetDialogOptionsFn(dialogOptions),
|
||||
showName: true,
|
||||
readOnlySyncTarget: true
|
||||
}).then(function () {
|
||||
fillJobValues(context, job, dialogOptions);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getTargetDialogOptionsFn(dialogOptions) {
|
||||
|
||||
return function (targetId) {
|
||||
|
||||
return Promise.resolve(dialogOptions);
|
||||
};
|
||||
}
|
||||
|
||||
function getJobItemHtml(jobItem, apiClient, index) {
|
||||
|
||||
var html = '';
|
||||
|
||||
html += '<div class="listItem" data-itemid="' + jobItem.Id + '" data-status="' + jobItem.Status + '" data-remove="' + jobItem.IsMarkedForRemoval + '">';
|
||||
|
||||
var hasActions = ['Queued', 'Cancelled', 'Failed', 'ReadyToTransfer', 'Transferring', 'Converting', 'Synced'].indexOf(jobItem.Status) !== -1;
|
||||
|
||||
var imgUrl;
|
||||
|
||||
if (jobItem.PrimaryImageItemId) {
|
||||
|
||||
imgUrl = apiClient.getImageUrl(jobItem.PrimaryImageItemId, {
|
||||
type: "Primary",
|
||||
width: 80,
|
||||
tag: jobItem.PrimaryImageTag,
|
||||
minScale: 1.5
|
||||
});
|
||||
}
|
||||
|
||||
if (imgUrl) {
|
||||
html += '<button type="button" is="emby-button" class="blue mini fab autoSize" icon="sync" style="background-image:url(\'' + imgUrl + '\');background-repeat:no-repeat;background-position:center center;background-size: cover;"><i style="visibility:hidden;" class="md-icon">sync</i></button>';
|
||||
}
|
||||
else {
|
||||
html += '<button type="button" is="emby-button" class="blue mini fab autoSize" icon="sync"><i class="md-icon">sync</i></button>';
|
||||
}
|
||||
|
||||
html += '<div class="listItemBody three-line">';
|
||||
|
||||
html += '<h3 class="listItemBodyText">';
|
||||
html += jobItem.ItemName;
|
||||
html += '</h3>';
|
||||
|
||||
if (jobItem.Status === 'Failed') {
|
||||
html += '<div class="secondary listItemBodyText" style="color:red;">';
|
||||
} else {
|
||||
html += '<div class="secondary listItemBodyText">';
|
||||
}
|
||||
html += globalize.translate('sharedcomponents#SyncJobItemStatus' + jobItem.Status);
|
||||
if (jobItem.Status === 'Synced' && jobItem.IsMarkedForRemoval) {
|
||||
html += '<br/>';
|
||||
html += globalize.translate('sharedcomponents#RemovingFromDevice');
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="secondary listItemBodyText" style="padding-top:5px;">';
|
||||
html += '<div style="background:#e0e0e0;height:4px;"><div style="background:#52B54B;width:' + (jobItem.Progress || 0) + '%;height:100%;"></div></div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '</div>';
|
||||
|
||||
var moreIcon = appHost.moreIcon === 'dots-horiz' ? '' : '';
|
||||
|
||||
if (hasActions) {
|
||||
|
||||
html += '<button type="button" is="paper-icon-button-light" class="btnJobItemMenu autoSize"><i class="md-icon">' + moreIcon + '</i></button>';
|
||||
} else {
|
||||
html += '<button type="button" is="paper-icon-button-light" class="btnJobItemMenu autoSize" disabled><i class="md-icon">' + moreIcon + '</i></button>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderJobItems(context, items, apiClient) {
|
||||
|
||||
var html = '';
|
||||
|
||||
html += '<h1>' + globalize.translate('sharedcomponents#Items') + '</h1>';
|
||||
|
||||
html += '<div class="paperList">';
|
||||
|
||||
var index = 0;
|
||||
html += items.map(function (i) {
|
||||
|
||||
return getJobItemHtml(i, apiClient, index++);
|
||||
|
||||
}).join('');
|
||||
|
||||
html += '</div>';
|
||||
|
||||
var elem = context.querySelector('.jobItems');
|
||||
elem.innerHTML = html;
|
||||
imageLoader.lazyChildren(elem);
|
||||
}
|
||||
|
||||
function parentWithClass(elem, className) {
|
||||
|
||||
while (!elem.classList || !elem.classList.contains(className)) {
|
||||
elem = elem.parentNode;
|
||||
|
||||
if (!elem) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
function showJobItemMenu(elem, jobId, apiClient) {
|
||||
|
||||
var context = parentWithClass(elem, 'page');
|
||||
var listItem = parentWithClass(elem, 'listItem');
|
||||
var jobItemId = listItem.getAttribute('data-itemid');
|
||||
var status = listItem.getAttribute('data-status');
|
||||
var remove = listItem.getAttribute('data-remove').toLowerCase() === 'true';
|
||||
|
||||
var menuItems = [];
|
||||
|
||||
if (status === 'Failed' || status === 'Cancelled') {
|
||||
menuItems.push({
|
||||
name: globalize.translate('sharedcomponents#Retry'),
|
||||
id: 'retry'
|
||||
});
|
||||
}
|
||||
else if (status === 'Queued' || status === 'Transferring' || status === 'Converting' || status === 'ReadyToTransfer') {
|
||||
menuItems.push({
|
||||
name: globalize.translate('sharedcomponents#CancelDownload'),
|
||||
id: 'cancel'
|
||||
});
|
||||
}
|
||||
else if (status === 'Synced' && remove) {
|
||||
menuItems.push({
|
||||
name: globalize.translate('sharedcomponents#KeepOnDevice'),
|
||||
id: 'unmarkforremoval'
|
||||
});
|
||||
}
|
||||
else if (status === 'Synced') {
|
||||
menuItems.push({
|
||||
name: globalize.translate('sharedcomponents#RemoveFromDevice'),
|
||||
id: 'markforremoval'
|
||||
});
|
||||
}
|
||||
|
||||
require(['actionsheet'], function (actionsheet) {
|
||||
|
||||
actionsheet.show({
|
||||
items: menuItems,
|
||||
positionTo: elem,
|
||||
callback: function (id) {
|
||||
|
||||
switch (id) {
|
||||
|
||||
case 'cancel':
|
||||
cancelJobItem(context, jobId, jobItemId, apiClient);
|
||||
break;
|
||||
case 'retry':
|
||||
retryJobItem(context, jobId, jobItemId, apiClient);
|
||||
break;
|
||||
case 'markforremoval':
|
||||
markForRemoval(context, jobId, jobItemId, apiClient);
|
||||
break;
|
||||
case 'unmarkforremoval':
|
||||
unMarkForRemoval(context, jobId, jobItemId, apiClient);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function cancelJobItem(context, jobId, jobItemId, apiClient) {
|
||||
|
||||
// Need a timeout because jquery mobile will not show a popup while another is in the act of closing
|
||||
|
||||
loading.show();
|
||||
|
||||
apiClient.ajax({
|
||||
|
||||
type: "DELETE",
|
||||
url: apiClient.getUrl('Sync/JobItems/' + jobItemId)
|
||||
|
||||
}).then(function () {
|
||||
|
||||
loadJob(context, jobId, apiClient);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function markForRemoval(context, jobId, jobItemId, apiClient) {
|
||||
|
||||
apiClient.ajax({
|
||||
|
||||
type: "POST",
|
||||
url: apiClient.getUrl('Sync/JobItems/' + jobItemId + '/MarkForRemoval')
|
||||
|
||||
}).then(function () {
|
||||
|
||||
loadJob(context, jobId, apiClient);
|
||||
});
|
||||
}
|
||||
|
||||
function unMarkForRemoval(context, jobId, jobItemId, apiClient) {
|
||||
|
||||
apiClient.ajax({
|
||||
|
||||
type: "POST",
|
||||
url: apiClient.getUrl('Sync/JobItems/' + jobItemId + '/UnmarkForRemoval')
|
||||
|
||||
}).then(function () {
|
||||
|
||||
loadJob(context, jobId, apiClient);
|
||||
});
|
||||
}
|
||||
|
||||
function retryJobItem(context, jobId, jobItemId, apiClient) {
|
||||
|
||||
apiClient.ajax({
|
||||
|
||||
type: "POST",
|
||||
url: apiClient.getUrl('Sync/JobItems/' + jobItemId + '/Enable')
|
||||
|
||||
}).then(function () {
|
||||
|
||||
loadJob(context, jobId, apiClient);
|
||||
});
|
||||
}
|
||||
|
||||
function fillJobValues(context, job, editOptions) {
|
||||
|
||||
var txtSyncJobName = context.querySelector('#txtSyncJobName');
|
||||
if (txtSyncJobName) {
|
||||
txtSyncJobName.value = job.Name;
|
||||
}
|
||||
|
||||
var selectProfile = context.querySelector('#selectProfile');
|
||||
if (selectProfile) {
|
||||
selectProfile.value = job.Profile || '';
|
||||
}
|
||||
|
||||
var selectQuality = context.querySelector('#selectQuality');
|
||||
if (selectQuality) {
|
||||
selectQuality.value = job.Quality || '';
|
||||
}
|
||||
|
||||
var chkUnwatchedOnly = context.querySelector('#chkUnwatchedOnly');
|
||||
if (chkUnwatchedOnly) {
|
||||
chkUnwatchedOnly.checked = job.UnwatchedOnly;
|
||||
}
|
||||
|
||||
var chkSyncNewContent = context.querySelector('#chkSyncNewContent');
|
||||
if (chkSyncNewContent) {
|
||||
chkSyncNewContent.checked = job.SyncNewContent;
|
||||
}
|
||||
|
||||
var txtItemLimit = context.querySelector('#txtItemLimit');
|
||||
if (txtItemLimit) {
|
||||
txtItemLimit.value = job.ItemLimit;
|
||||
}
|
||||
|
||||
var txtBitrate = context.querySelector('#txtBitrate');
|
||||
if (job.Bitrate) {
|
||||
txtBitrate.value = job.Bitrate / 1000000;
|
||||
} else {
|
||||
txtBitrate.value = '';
|
||||
}
|
||||
|
||||
var target = editOptions.Targets.filter(function (t) {
|
||||
return t.Id === job.TargetId;
|
||||
})[0];
|
||||
var targetName = target ? target.Name : '';
|
||||
|
||||
var selectSyncTarget = context.querySelector('#selectSyncTarget');
|
||||
if (selectSyncTarget) {
|
||||
selectSyncTarget.value = targetName;
|
||||
}
|
||||
}
|
||||
|
||||
var _jobOptions;
|
||||
function loadJob(context, id, apiClient) {
|
||||
|
||||
loading.show();
|
||||
|
||||
apiClient.getJSON(apiClient.getUrl('Sync/Jobs/' + id)).then(function (job) {
|
||||
|
||||
apiClient.getJSON(apiClient.getUrl('Sync/Options', {
|
||||
|
||||
UserId: job.UserId,
|
||||
ItemIds: (job.RequestedItemIds && job.RequestedItemIds.length ? job.RequestedItemIds.join('') : null),
|
||||
|
||||
ParentId: job.ParentId,
|
||||
Category: job.Category,
|
||||
TargetId: job.TargetId
|
||||
|
||||
})).then(function (options) {
|
||||
|
||||
_jobOptions = options;
|
||||
renderJob(context, job, options);
|
||||
loading.hide();
|
||||
});
|
||||
});
|
||||
|
||||
apiClient.getJSON(apiClient.getUrl('Sync/JobItems', {
|
||||
|
||||
JobId: id,
|
||||
AddMetadata: true
|
||||
|
||||
})).then(function (result) {
|
||||
|
||||
renderJobItems(context, result.Items, apiClient);
|
||||
loading.hide();
|
||||
});
|
||||
}
|
||||
|
||||
function loadJobInfo(context, job, jobItems, apiClient) {
|
||||
|
||||
//renderJob(page, job, _jobOptions);
|
||||
renderJobItems(context, jobItems, apiClient);
|
||||
loading.hide();
|
||||
}
|
||||
|
||||
function saveJob(context, id, apiClient) {
|
||||
|
||||
loading.show();
|
||||
|
||||
apiClient.getJSON(apiClient.getUrl('Sync/Jobs/' + id)).then(function (job) {
|
||||
|
||||
require(['syncDialog'], function (syncDialog) {
|
||||
syncDialog.setJobValues(job, context);
|
||||
|
||||
apiClient.ajax({
|
||||
|
||||
url: apiClient.getUrl('Sync/Jobs/' + id),
|
||||
type: 'POST',
|
||||
data: JSON.stringify(job),
|
||||
contentType: "application/json"
|
||||
|
||||
}).then(function () {
|
||||
|
||||
loading.hide();
|
||||
dialogHelper.close(context);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function onHelpLinkClick(e) {
|
||||
|
||||
shell.openUrl(this.href);
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
function startListening(apiClient, jobId) {
|
||||
|
||||
var startParams = "0,1500";
|
||||
|
||||
startParams += "," + jobId;
|
||||
|
||||
if (apiClient.isWebSocketOpen()) {
|
||||
apiClient.sendWebSocketMessage("SyncJobStart", startParams);
|
||||
}
|
||||
}
|
||||
|
||||
function stopListening(apiClient) {
|
||||
|
||||
if (apiClient.isWebSocketOpen()) {
|
||||
apiClient.sendWebSocketMessage("SyncJobStop", "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function bindEvents(context, jobId, apiClient) {
|
||||
context.querySelector('.jobItems').addEventListener('click', function (e) {
|
||||
var btnJobItemMenu = dom.parentWithClass(e.target, 'btnJobItemMenu');
|
||||
if (btnJobItemMenu) {
|
||||
showJobItemMenu(btnJobItemMenu, jobId, apiClient);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showEditor(options) {
|
||||
|
||||
var apiClient = connectionManager.getApiClient(options.serverId);
|
||||
var id = options.jobId;
|
||||
|
||||
var dlgElementOptions = {
|
||||
removeOnClose: true,
|
||||
scrollY: false,
|
||||
autoFocus: false
|
||||
};
|
||||
|
||||
if (layoutManager.tv) {
|
||||
dlgElementOptions.size = 'fullscreen';
|
||||
} else {
|
||||
dlgElementOptions.size = 'medium';
|
||||
}
|
||||
|
||||
var dlg = dialogHelper.createDialog(dlgElementOptions);
|
||||
|
||||
dlg.classList.add('formDialog');
|
||||
|
||||
var html = '';
|
||||
html += '<div class="formDialogHeader">';
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><i class="md-icon"></i></button>';
|
||||
html += '<h3 class="formDialogHeaderTitle">';
|
||||
html += globalize.translate('sharedcomponents#Sync');
|
||||
html += '</h3>';
|
||||
|
||||
html += '<a href="https://github.com/MediaBrowser/Wiki/wiki/Sync" target="_blank" class="clearLink lnkHelp" style="margin-top:0;display:inline-block;vertical-align:middle;margin-left:auto;"><button is="emby-button" type="button" class="button-accent-flat button-flat"><i class="md-icon">info</i><span>' + globalize.translate('sharedcomponents#Help') + '</span></button></a>';
|
||||
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">';
|
||||
html += '<div class="dialogContentInner dialog-content-centered">';
|
||||
|
||||
html += '<form class="syncJobForm" style="margin: auto;">';
|
||||
|
||||
html += '<div class="syncJobFormContent"></div>';
|
||||
|
||||
html += '<div class="jobItems"></div>';
|
||||
|
||||
html += '<div class="formDialogFooter">';
|
||||
html += '<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem"><span>' + globalize.translate('sharedcomponents#Save') + '</span></button>';
|
||||
html += '</div>';
|
||||
|
||||
html += '</form>';
|
||||
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
dlg.innerHTML = html;
|
||||
|
||||
dlg.querySelector('.lnkHelp').addEventListener('click', onHelpLinkClick);
|
||||
|
||||
var submitted = false;
|
||||
|
||||
dlg.querySelector('form').addEventListener('submit', function (e) {
|
||||
|
||||
saveJob(dlg, id, apiClient);
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
dlg.querySelector('.btnCancel').addEventListener('click', function () {
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
|
||||
if (layoutManager.tv) {
|
||||
scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false);
|
||||
}
|
||||
|
||||
function onSyncJobMessage(e, apiClient, msg) {
|
||||
loadJobInfo(dlg, msg.Job, msg.JobItems, apiClient);
|
||||
}
|
||||
|
||||
loadJob(dlg, id, apiClient);
|
||||
bindEvents(dlg, id, apiClient);
|
||||
|
||||
var promise = dialogHelper.open(dlg);
|
||||
|
||||
startListening(apiClient, id);
|
||||
events.on(serverNotifications, "SyncJob", onSyncJobMessage);
|
||||
|
||||
return promise.then(function () {
|
||||
|
||||
stopListening(apiClient);
|
||||
events.off(serverNotifications, "SyncJob", onSyncJobMessage);
|
||||
|
||||
if (layoutManager.tv) {
|
||||
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
|
||||
}
|
||||
|
||||
if (submitted) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
show: showEditor
|
||||
};
|
||||
|
||||
});
|
420
bower_components/emby-webcomponents/sync/syncjoblist.js
vendored
Normal file
420
bower_components/emby-webcomponents/sync/syncjoblist.js
vendored
Normal file
@ -0,0 +1,420 @@
|
||||
define(['serverNotifications', 'events', 'loading', 'connectionManager', 'imageLoader', 'dom', 'globalize', 'registrationServices', 'layoutManager', 'listViewStyle'], function (serverNotifications, events, loading, connectionManager, imageLoader, dom, globalize, registrationServices, layoutManager) {
|
||||
'use strict';
|
||||
|
||||
function onSyncJobsUpdated(e, apiClient, data) {
|
||||
|
||||
var listInstance = this;
|
||||
renderList(listInstance, data, apiClient);
|
||||
}
|
||||
|
||||
function refreshList(listInstance, jobs) {
|
||||
for (var i = 0, length = jobs.length; i < length; i++) {
|
||||
|
||||
var job = jobs[i];
|
||||
refreshJob(listInstance, job);
|
||||
}
|
||||
}
|
||||
|
||||
function cancelJob(listInstance, id) {
|
||||
|
||||
require(['confirm'], function (confirm) {
|
||||
|
||||
var msg = listInstance.options.isLocalSync ?
|
||||
globalize.translate('sharedcomponents#ConfirmRemoveDownload') :
|
||||
globalize.translate('sharedcomponents#CancelSyncJobConfirmation');
|
||||
|
||||
confirm({
|
||||
|
||||
text: msg,
|
||||
primary: 'cancel'
|
||||
|
||||
}).then(function () {
|
||||
|
||||
loading.show();
|
||||
var apiClient = getApiClient(listInstance);
|
||||
|
||||
apiClient.ajax({
|
||||
|
||||
url: apiClient.getUrl('Sync/Jobs/' + id),
|
||||
type: 'DELETE'
|
||||
|
||||
}).then(function () {
|
||||
|
||||
fetchData(listInstance);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function refreshJob(listInstance, job) {
|
||||
|
||||
var listItem = listInstance.options.element.querySelector('.listItem[data-id=\'' + job.Id + '\']');
|
||||
|
||||
if (!listItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
listItem.querySelector('.jobStatus').innerHTML = getProgressText(job);
|
||||
}
|
||||
|
||||
function getProgressText(job) {
|
||||
|
||||
var status = job.Status;
|
||||
|
||||
if (status === 'Completed') {
|
||||
status = 'Synced';
|
||||
}
|
||||
|
||||
var html = globalize.translate('sharedcomponents#SyncJobItemStatus' + status);
|
||||
|
||||
if (job.Status === 'Transferring' || job.Status === 'Converting' || job.Status === 'Completed') {
|
||||
html += ' ';
|
||||
html += (job.Progress || 0) + '%';
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function getSyncJobHtml(listInstance, job, apiClient) {
|
||||
|
||||
var html = '';
|
||||
|
||||
var tagName = layoutManager.tv ? 'button' : 'div';
|
||||
var typeAttribute = tagName === 'button' ? ' type="button"' : '';
|
||||
|
||||
var listItemClass = 'listItem';
|
||||
|
||||
if (layoutManager.tv) {
|
||||
listItemClass += ' listItem-button listItem-focusscale';
|
||||
}
|
||||
|
||||
html += '<' + tagName + typeAttribute + ' class="' + listItemClass + '" data-id="' + job.Id + '" data-status="' + job.Status + '">';
|
||||
|
||||
var imgUrl;
|
||||
|
||||
if (job.PrimaryImageItemId) {
|
||||
|
||||
imgUrl = apiClient.getImageUrl(job.PrimaryImageItemId, {
|
||||
type: "Primary",
|
||||
width: 80,
|
||||
tag: job.PrimaryImageTag,
|
||||
minScale: 1.5
|
||||
});
|
||||
}
|
||||
|
||||
if (imgUrl) {
|
||||
html += '<div class="listItemImage lazy" data-src="' + imgUrl + '" item-icon>';
|
||||
html += '</div>';
|
||||
}
|
||||
else {
|
||||
html += '<i class="md-icon listItemIcon">file_download</i>';
|
||||
}
|
||||
|
||||
var textLines = [];
|
||||
|
||||
if (job.ParentName) {
|
||||
textLines.push(job.ParentName);
|
||||
}
|
||||
|
||||
textLines.push(job.Name);
|
||||
|
||||
if (job.ItemCount === 1) {
|
||||
textLines.push(globalize.translate('sharedcomponents#ValueOneItem'));
|
||||
} else {
|
||||
textLines.push(globalize.translate('sharedcomponents#ItemCount', job.ItemCount));
|
||||
}
|
||||
|
||||
html += '<div class="listItemBody three-line">';
|
||||
|
||||
for (var i = 0, length = textLines.length; i < length; i++) {
|
||||
|
||||
if (i === 0) {
|
||||
html += '<h3 class="listItemBodyText">';
|
||||
html += textLines[i];
|
||||
html += '</h3>';
|
||||
} else {
|
||||
html += '<div class="listItemBodyText secondary">';
|
||||
html += textLines[i];
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
html += '<div class="secondary listItemBodyText jobStatus" style="color:green;">';
|
||||
html += getProgressText(job);
|
||||
html += '</div>';
|
||||
|
||||
html += '</div>';
|
||||
|
||||
if (!layoutManager.tv) {
|
||||
html += '<button type="button" is="paper-icon-button-light" class="btnJobMenu listItemButton"><i class="md-icon">more_vert</i></button>';
|
||||
}
|
||||
|
||||
html += '</' + tagName + '>';
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderList(listInstance, jobs, apiClient) {
|
||||
|
||||
if ((new Date().getTime() - listInstance.lastDataLoad) < 60000) {
|
||||
refreshList(listInstance, jobs);
|
||||
return;
|
||||
}
|
||||
|
||||
listInstance.lastDataLoad = new Date().getTime();
|
||||
|
||||
var html = '';
|
||||
var lastTargetName = '';
|
||||
|
||||
var isLocalSync = listInstance.options.isLocalSync;
|
||||
var showTargetName = !isLocalSync;
|
||||
|
||||
var hasOpenSection = false;
|
||||
|
||||
for (var i = 0, length = jobs.length; i < length; i++) {
|
||||
|
||||
var job = jobs[i];
|
||||
if (showTargetName) {
|
||||
var targetName = job.TargetName || 'Unknown';
|
||||
|
||||
if (targetName !== lastTargetName) {
|
||||
|
||||
if (lastTargetName) {
|
||||
html += '</div>';
|
||||
html += '<br/>';
|
||||
hasOpenSection = false;
|
||||
}
|
||||
|
||||
lastTargetName = targetName;
|
||||
|
||||
html += '<div class="detailSectionHeader">';
|
||||
|
||||
html += '<h1>' + targetName + '</h1>';
|
||||
|
||||
html += '</div>';
|
||||
html += '<div class="itemsContainer vertical-list paperList">';
|
||||
hasOpenSection = true;
|
||||
}
|
||||
}
|
||||
|
||||
html += getSyncJobHtml(listInstance, job, apiClient);
|
||||
}
|
||||
|
||||
if (hasOpenSection) {
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
var elem = listInstance.options.element.querySelector('.syncJobListContent');
|
||||
|
||||
if (!html) {
|
||||
if (isLocalSync) {
|
||||
html = '<div style="padding:1em .25em;">' + globalize.translate('sharedcomponents#MessageNoDownloadsFound') + '</div>';
|
||||
} else {
|
||||
html = '<div style="padding:1em .25em;">' + globalize.translate('sharedcomponents#MessageNoSyncJobsFound') + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
elem.innerHTML = html;
|
||||
|
||||
imageLoader.lazyChildren(elem);
|
||||
}
|
||||
|
||||
function fetchData(listInstance) {
|
||||
|
||||
listInstance.lastDataLoad = 0;
|
||||
loading.show();
|
||||
|
||||
var options = {};
|
||||
var apiClient = getApiClient(listInstance);
|
||||
|
||||
if (listInstance.options.userId) {
|
||||
options.UserId = listInstance.options.userId;
|
||||
}
|
||||
|
||||
if (listInstance.options.isLocalSync) {
|
||||
options.TargetId = apiClient.deviceId();
|
||||
} else {
|
||||
options.ExcludeTargetIds = apiClient.deviceId();
|
||||
}
|
||||
|
||||
return apiClient.getJSON(apiClient.getUrl('Sync/Jobs', options)).then(function (response) {
|
||||
|
||||
renderList(listInstance, response.Items, apiClient);
|
||||
loading.hide();
|
||||
});
|
||||
}
|
||||
|
||||
function startListening(listInstance) {
|
||||
|
||||
var startParams = "0,1500";
|
||||
|
||||
var apiClient = getApiClient(listInstance);
|
||||
|
||||
if (listInstance.options.userId) {
|
||||
startParams += "," + listInstance.options.userId;
|
||||
}
|
||||
if (listInstance.options.isLocalSync) {
|
||||
startParams += "," + apiClient.deviceId();
|
||||
}
|
||||
|
||||
if (apiClient.isWebSocketOpen()) {
|
||||
apiClient.sendWebSocketMessage("SyncJobsStart", startParams);
|
||||
}
|
||||
}
|
||||
|
||||
function stopListening(listInstance) {
|
||||
|
||||
var apiClient = getApiClient(listInstance);
|
||||
if (apiClient.isWebSocketOpen()) {
|
||||
apiClient.sendWebSocketMessage("SyncJobsStop", "");
|
||||
}
|
||||
}
|
||||
|
||||
function getApiClient(listInstance) {
|
||||
return connectionManager.getApiClient(listInstance.options.serverId);
|
||||
}
|
||||
|
||||
function showJobMenu(listInstance, elem) {
|
||||
|
||||
var item = dom.parentWithClass(elem, 'listItem');
|
||||
var jobId = item.getAttribute('data-id');
|
||||
var status = item.getAttribute('data-status');
|
||||
|
||||
var menuItems = [];
|
||||
|
||||
if (status === 'Cancelled') {
|
||||
menuItems.push({
|
||||
name: globalize.translate('sharedcomponents#Delete'),
|
||||
id: 'delete'
|
||||
});
|
||||
} else {
|
||||
var txt = listInstance.options.isLocalSync ?
|
||||
globalize.translate('sharedcomponents#RemoveDownload') :
|
||||
globalize.translate('sharedcomponents#ButtonCancelSyncJob');
|
||||
|
||||
menuItems.push({
|
||||
name: txt,
|
||||
id: 'cancel'
|
||||
});
|
||||
}
|
||||
|
||||
require(['actionsheet'], function (actionsheet) {
|
||||
|
||||
actionsheet.show({
|
||||
items: menuItems,
|
||||
positionTo: elem,
|
||||
callback: function (id) {
|
||||
|
||||
switch (id) {
|
||||
|
||||
case 'delete':
|
||||
cancelJob(listInstance, jobId);
|
||||
break;
|
||||
case 'cancel':
|
||||
cancelJob(listInstance, jobId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function onElementClick(e) {
|
||||
|
||||
var listInstance = this;
|
||||
|
||||
var btnJobMenu = dom.parentWithClass(e.target, 'btnJobMenu');
|
||||
if (btnJobMenu) {
|
||||
showJobMenu(this, btnJobMenu);
|
||||
return;
|
||||
}
|
||||
|
||||
var listItem = dom.parentWithClass(e.target, 'listItem');
|
||||
if (listItem) {
|
||||
var jobId = listItem.getAttribute('data-id');
|
||||
// edit job
|
||||
require(['syncJobEditor'], function (syncJobEditor) {
|
||||
syncJobEditor.show({
|
||||
serverId: listInstance.options.serverId,
|
||||
jobId: jobId
|
||||
}).then(function () {
|
||||
fetchData(listInstance);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function syncJobList(options) {
|
||||
this.options = options;
|
||||
|
||||
var onSyncJobsUpdatedHandler = onSyncJobsUpdated.bind(this);
|
||||
this.onSyncJobsUpdatedHandler = onSyncJobsUpdatedHandler;
|
||||
events.on(serverNotifications, 'SyncJobs', onSyncJobsUpdatedHandler);
|
||||
|
||||
var onClickHandler = onElementClick.bind(this);
|
||||
options.element.addEventListener('click', onClickHandler);
|
||||
this.onClickHandler = onClickHandler;
|
||||
|
||||
options.element.innerHTML = '<div class="syncJobListContent"></div>';
|
||||
|
||||
fetchData(this);
|
||||
startListening(this);
|
||||
|
||||
initSupporterInfo(options.element, getApiClient(this));
|
||||
}
|
||||
|
||||
function showSupporterInfo(context) {
|
||||
|
||||
var html = '<button is="emby-button" class="raised button-accent block btnSyncSupporter" style="margin:1em 0;">';
|
||||
|
||||
html += '<div>';
|
||||
html += globalize.translate('sharedcomponents#HeaderSyncRequiresSub');
|
||||
html += '</div>';
|
||||
html += '<div style="margin-top:.5em;">';
|
||||
html += globalize.translate('sharedcomponents#LearnMore');
|
||||
html += '</div>';
|
||||
|
||||
html += '</button';
|
||||
|
||||
context.insertAdjacentHTML('afterbegin', html);
|
||||
|
||||
context.querySelector('.btnSyncSupporter').addEventListener('click', function () {
|
||||
|
||||
registrationServices.validateFeature('sync');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function initSupporterInfo(context, apiClient) {
|
||||
|
||||
apiClient.getPluginSecurityInfo().then(function (regInfo) {
|
||||
|
||||
if (!regInfo.IsMBSupporter) {
|
||||
showSupporterInfo(context, apiClient);
|
||||
}
|
||||
|
||||
}, function () {
|
||||
showSupporterInfo(context, apiClient);
|
||||
});
|
||||
}
|
||||
|
||||
syncJobList.prototype.destroy = function () {
|
||||
|
||||
stopListening(this);
|
||||
|
||||
var onSyncJobsUpdatedHandler = this.onSyncJobsUpdatedHandler;
|
||||
this.onSyncJobsUpdatedHandler = null;
|
||||
events.off(serverNotifications, 'SyncJobs', onSyncJobsUpdatedHandler);
|
||||
|
||||
var onClickHandler = this.onClickHandler;
|
||||
this.onClickHandler = null;
|
||||
this.options.element.removeEventListener('click', onClickHandler);
|
||||
|
||||
this.options = null;
|
||||
};
|
||||
|
||||
return syncJobList;
|
||||
});
|
407
bower_components/emby-webcomponents/youtubeplayer/plugin.js
vendored
Normal file
407
bower_components/emby-webcomponents/youtubeplayer/plugin.js
vendored
Normal file
@ -0,0 +1,407 @@
|
||||
define(['pluginManager', 'events', 'browser', 'embyRouter'], function (pluginManager, Events, browser, embyRouter) {
|
||||
"use strict";
|
||||
|
||||
return function () {
|
||||
|
||||
var self = this;
|
||||
|
||||
self.name = 'Youtube Player';
|
||||
self.type = 'mediaplayer';
|
||||
self.id = 'youtubeplayer';
|
||||
|
||||
// Let any players created by plugins take priority
|
||||
self.priority = 1;
|
||||
|
||||
var videoDialog;
|
||||
var currentSrc;
|
||||
var started = false;
|
||||
|
||||
var currentYoutubePlayer;
|
||||
var timeUpdateInterval;
|
||||
|
||||
self.canPlayMediaType = function (mediaType) {
|
||||
|
||||
mediaType = (mediaType || '').toLowerCase();
|
||||
|
||||
return mediaType === 'audio' || mediaType === 'video';
|
||||
};
|
||||
|
||||
self.canPlayItem = function (item) {
|
||||
|
||||
// Does not play server items
|
||||
return false;
|
||||
};
|
||||
|
||||
self.canPlayUrl = function (url) {
|
||||
|
||||
return url.toLowerCase().indexOf('youtube.com') !== -1;
|
||||
};
|
||||
|
||||
self.getDeviceProfile = function () {
|
||||
|
||||
return Promise.resolve({});
|
||||
};
|
||||
|
||||
self.currentSrc = function () {
|
||||
return currentSrc;
|
||||
};
|
||||
|
||||
self.play = function (options) {
|
||||
|
||||
started = false;
|
||||
|
||||
return createMediaElement(options).then(function (elem) {
|
||||
|
||||
return setCurrentSrc(elem, options);
|
||||
});
|
||||
};
|
||||
|
||||
function setCurrentSrc(elem, options) {
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
require(['queryString'], function (queryString) {
|
||||
|
||||
|
||||
currentSrc = options.url;
|
||||
var params = queryString.parse(options.url.split('?')[1]);
|
||||
// 3. This function creates an <iframe> (and YouTube player)
|
||||
// after the API code downloads.
|
||||
window.onYouTubeIframeAPIReady = function () {
|
||||
currentYoutubePlayer = new YT.Player('player', {
|
||||
height: videoDialog.offsetHeight,
|
||||
width: videoDialog.offsetWidth,
|
||||
videoId: params.v,
|
||||
events: {
|
||||
'onReady': onPlayerReady,
|
||||
'onStateChange': function (event) {
|
||||
if (event.data === YT.PlayerState.PLAYING) {
|
||||
onPlaying(options, resolve);
|
||||
} else if (event.data === YT.PlayerState.ENDED) {
|
||||
onEnded();
|
||||
} else if (event.data === YT.PlayerState.PAUSED) {
|
||||
onPause();
|
||||
}
|
||||
}
|
||||
},
|
||||
playerVars: {
|
||||
controls: 0,
|
||||
enablejsapi: 1,
|
||||
modestbranding: 1,
|
||||
rel: 0,
|
||||
showinfo: 0,
|
||||
fs: 0,
|
||||
playsinline: 1
|
||||
}
|
||||
});
|
||||
|
||||
window.removeEventListener('resize', onVideoResize);
|
||||
window.addEventListener('resize', onVideoResize);
|
||||
window.removeEventListener('orientationChange', onVideoResize);
|
||||
window.addEventListener('orientationChange', onVideoResize);
|
||||
};
|
||||
|
||||
if (!window.YT) {
|
||||
var tag = document.createElement('script');
|
||||
tag.src = "https://www.youtube.com/iframe_api";
|
||||
var firstScriptTag = document.getElementsByTagName('script')[0];
|
||||
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
|
||||
} else {
|
||||
window.onYouTubeIframeAPIReady();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function onVideoResize() {
|
||||
var player = currentYoutubePlayer;
|
||||
var dlg = videoDialog;
|
||||
if (player && dlg) {
|
||||
player.setSize(dlg.offsetWidth, dlg.offsetHeight);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. The API will call this function when the video player is ready.
|
||||
function onPlayerReady(event) {
|
||||
event.target.playVideo();
|
||||
}
|
||||
|
||||
self.setSubtitleStreamIndex = function (index) {
|
||||
};
|
||||
|
||||
self.canSetAudioStreamIndex = function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
self.setAudioStreamIndex = function (index) {
|
||||
|
||||
};
|
||||
|
||||
// Save this for when playback stops, because querying the time at that point might return 0
|
||||
self.currentTime = function (val) {
|
||||
|
||||
if (currentYoutubePlayer) {
|
||||
if (val != null) {
|
||||
currentYoutubePlayer.seekTo(val / 1000, true);
|
||||
return;
|
||||
}
|
||||
|
||||
return currentYoutubePlayer.getCurrentTime() * 1000;
|
||||
}
|
||||
};
|
||||
|
||||
self.duration = function (val) {
|
||||
|
||||
if (currentYoutubePlayer) {
|
||||
return currentYoutubePlayer.getDuration() * 1000;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
self.stop = function (destroyPlayer, reportEnded) {
|
||||
|
||||
var src = currentSrc;
|
||||
|
||||
if (src) {
|
||||
|
||||
if (currentYoutubePlayer) {
|
||||
currentYoutubePlayer.stopVideo();
|
||||
}
|
||||
onEndedInternal(reportEnded);
|
||||
|
||||
if (destroyPlayer) {
|
||||
self.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
self.destroy = function () {
|
||||
|
||||
embyRouter.setTransparency('none');
|
||||
|
||||
var dlg = videoDialog;
|
||||
if (dlg) {
|
||||
|
||||
videoDialog = null;
|
||||
|
||||
dlg.parentNode.removeChild(dlg);
|
||||
}
|
||||
};
|
||||
|
||||
self.pause = function () {
|
||||
if (currentYoutubePlayer) {
|
||||
currentYoutubePlayer.pauseVideo();
|
||||
|
||||
// This needs a delay before the youtube player will report the correct player state
|
||||
setTimeout(onPause, 200);
|
||||
}
|
||||
};
|
||||
|
||||
self.unpause = function () {
|
||||
if (currentYoutubePlayer) {
|
||||
currentYoutubePlayer.playVideo();
|
||||
|
||||
// This needs a delay before the youtube player will report the correct player state
|
||||
setTimeout(onPlaying, 200);
|
||||
}
|
||||
};
|
||||
|
||||
self.paused = function () {
|
||||
|
||||
if (currentYoutubePlayer) {
|
||||
console.log(currentYoutubePlayer.getPlayerState());
|
||||
return currentYoutubePlayer.getPlayerState() === 2;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
self.volume = function (val) {
|
||||
if (val != null) {
|
||||
return self.setVolume(val);
|
||||
}
|
||||
|
||||
return self.getVolume();
|
||||
};
|
||||
|
||||
self.setVolume = function (val) {
|
||||
if (currentYoutubePlayer) {
|
||||
if (val != null) {
|
||||
currentYoutubePlayer.setVolume(val);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.getVolume = function () {
|
||||
if (currentYoutubePlayer) {
|
||||
return currentYoutubePlayer.getVolume();
|
||||
}
|
||||
};
|
||||
|
||||
self.setMute = function (mute) {
|
||||
|
||||
if (mute) {
|
||||
if (currentYoutubePlayer) {
|
||||
currentYoutubePlayer.mute();
|
||||
}
|
||||
} else {
|
||||
|
||||
if (currentYoutubePlayer) {
|
||||
currentYoutubePlayer.unMute();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.isMuted = function () {
|
||||
if (currentYoutubePlayer) {
|
||||
currentYoutubePlayer.isMuted();
|
||||
}
|
||||
};
|
||||
|
||||
function onEnded() {
|
||||
|
||||
onEndedInternal(true);
|
||||
}
|
||||
|
||||
function clearTimeUpdateInterval() {
|
||||
if (timeUpdateInterval) {
|
||||
clearInterval(timeUpdateInterval);
|
||||
}
|
||||
timeUpdateInterval = null;
|
||||
}
|
||||
|
||||
function onEndedInternal(triggerEnded) {
|
||||
|
||||
clearTimeUpdateInterval();
|
||||
window.removeEventListener('resize', onVideoResize);
|
||||
window.removeEventListener('orientationChange', onVideoResize);
|
||||
|
||||
if (triggerEnded) {
|
||||
var stopInfo = {
|
||||
src: currentSrc
|
||||
};
|
||||
|
||||
Events.trigger(self, 'stopped', [stopInfo]);
|
||||
}
|
||||
|
||||
currentSrc = null;
|
||||
if (currentYoutubePlayer) {
|
||||
currentYoutubePlayer.destroy();
|
||||
}
|
||||
currentYoutubePlayer = null;
|
||||
}
|
||||
|
||||
function onTimeUpdate(e) {
|
||||
|
||||
Events.trigger(self, 'timeupdate');
|
||||
}
|
||||
|
||||
function onVolumeChange() {
|
||||
|
||||
Events.trigger(self, 'volumechange');
|
||||
}
|
||||
|
||||
function onPlaying(playOptions, resolve) {
|
||||
|
||||
if (!started) {
|
||||
|
||||
started = true;
|
||||
resolve();
|
||||
clearTimeUpdateInterval();
|
||||
timeUpdateInterval = setInterval(onTimeUpdate, 500);
|
||||
|
||||
if (playOptions.fullscreen) {
|
||||
|
||||
embyRouter.showVideoOsd().then(function () {
|
||||
videoDialog.classList.remove('onTop');
|
||||
});
|
||||
|
||||
} else {
|
||||
embyRouter.setTransparency('backdrop');
|
||||
videoDialog.classList.remove('onTop');
|
||||
}
|
||||
|
||||
require(['loading'], function (loading) {
|
||||
|
||||
loading.hide();
|
||||
});
|
||||
}
|
||||
Events.trigger(self, 'playing');
|
||||
}
|
||||
|
||||
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 : '';
|
||||
console.log('Media element error code: ' + errorCode);
|
||||
|
||||
Events.trigger(self, 'error');
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
var dlg = document.querySelector('.youtubePlayerContainer');
|
||||
|
||||
if (!dlg) {
|
||||
|
||||
require(['loading', 'css!' + pluginManager.mapPath(self, 'style.css')], function (loading) {
|
||||
|
||||
loading.show();
|
||||
|
||||
var dlg = document.createElement('div');
|
||||
|
||||
dlg.classList.add('youtubePlayerContainer');
|
||||
|
||||
if (options.fullscreen) {
|
||||
dlg.classList.add('onTop');
|
||||
}
|
||||
|
||||
dlg.innerHTML = '<div id="player"></div>';
|
||||
var videoElement = dlg.querySelector('#player');
|
||||
|
||||
document.body.insertBefore(dlg, document.body.firstChild);
|
||||
videoDialog = dlg;
|
||||
|
||||
if (options.fullscreen && dlg.animate && !browser.slow) {
|
||||
zoomIn(dlg, 1).onfinish = function () {
|
||||
resolve(videoElement);
|
||||
};
|
||||
} else {
|
||||
resolve(videoElement);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
} else {
|
||||
resolve(dlg.querySelector('#player'));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
21
bower_components/emby-webcomponents/youtubeplayer/style.css
vendored
Normal file
21
bower_components/emby-webcomponents/youtubeplayer/style.css
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
.youtubePlayerContainer {
|
||||
background: #000 !important;
|
||||
position: fixed !important;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.youtubePlayerContainer.onTop {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.youtubePlayerContainer video {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
Loading…
Reference in New Issue
Block a user