mirror of
https://github.com/jellyfin/jellyfin-web.git
synced 2024-11-18 03:18:19 -07:00
Merge branch 'dev'
This commit is contained in:
commit
63260ab4b0
@ -16,15 +16,14 @@
|
||||
},
|
||||
"devDependencies": {},
|
||||
"ignore": [],
|
||||
"version": "1.1.51",
|
||||
"_release": "1.1.51",
|
||||
"version": "1.1.54",
|
||||
"_release": "1.1.54",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "1.1.51",
|
||||
"commit": "b118029cf5077fc940b3a651054357cbd4f7d04e"
|
||||
"tag": "1.1.54",
|
||||
"commit": "80b29ca19f79c58f1b9e7f105c5b637761a9c5f9"
|
||||
},
|
||||
"_source": "https://github.com/MediaBrowser/Emby.ApiClient.Javascript.git",
|
||||
"_target": "^1.1.51",
|
||||
"_originalSource": "emby-apiclient",
|
||||
"_direct": true
|
||||
"_originalSource": "emby-apiclient"
|
||||
}
|
@ -141,7 +141,7 @@
|
||||
{
|
||||
url: url,
|
||||
status: response.status,
|
||||
errorCode: response.headers ? response.headers["X-Application-Error-Code"] : null
|
||||
errorCode: response.headers ? response.headers.get('X-Application-Error-Code') : null
|
||||
}]);
|
||||
}
|
||||
|
||||
|
@ -215,7 +215,7 @@
|
||||
return connectUser;
|
||||
};
|
||||
|
||||
var minServerVersion = '3.0.5781';
|
||||
var minServerVersion = '3.0.5785';
|
||||
self.minServerVersion = function (val) {
|
||||
|
||||
if (val) {
|
||||
|
47
dashboard-ui/bower_components/emby-lazyload-image/lazyload-image.html
vendored
Normal file
47
dashboard-ui/bower_components/emby-lazyload-image/lazyload-image.html
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
<script>
|
||||
|
||||
require(['imageFetcher'], function (imageFetcher) {
|
||||
window.ImageFetcherLazyloadImage = imageFetcher;
|
||||
});
|
||||
|
||||
/**
|
||||
* <lazyload-image>
|
||||
* HTMLImageElement extension for lazy loading.
|
||||
* http://github.com/1000ch/lazyload-image
|
||||
*
|
||||
* Copyright 1000ch
|
||||
* licensed under the MIT license.
|
||||
*/
|
||||
window.LazyloadImage = (function () {
|
||||
'use strict';
|
||||
|
||||
var FALLBACK_IMAGE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg==';
|
||||
|
||||
// create prototype from HTMLImageElement
|
||||
var LazyloadImagePrototype = Object.create(HTMLImageElement.prototype);
|
||||
|
||||
// lifecycle callbacks
|
||||
//LazyloadImagePrototype.createdCallback = function () {
|
||||
|
||||
//};
|
||||
|
||||
//LazyloadImagePrototype.attachedCallback = function () {
|
||||
|
||||
// var original = this.currentSrc || this.src;
|
||||
|
||||
// if (original && window.ImageFetcherLazyloadImage) {
|
||||
// //console.log(original);
|
||||
// this.src = FALLBACK_IMAGE;
|
||||
// console.log('loading ' + original);
|
||||
// ImageFetcherLazyloadImage.loadImage(this, original);
|
||||
// }
|
||||
//};
|
||||
|
||||
return document.registerElement('lazyload-image', {
|
||||
prototype: LazyloadImagePrototype,
|
||||
extends: 'img'
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
</script>
|
@ -16,12 +16,12 @@
|
||||
},
|
||||
"devDependencies": {},
|
||||
"ignore": [],
|
||||
"version": "1.2.26",
|
||||
"_release": "1.2.26",
|
||||
"version": "1.2.58",
|
||||
"_release": "1.2.58",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "1.2.26",
|
||||
"commit": "7e13c8d9c17a4946681b3485c5fbf3e62f39cd2f"
|
||||
"tag": "1.2.58",
|
||||
"commit": "523cb074208c7350bb68554c6fcde613142117f4"
|
||||
},
|
||||
"_source": "https://github.com/MediaBrowser/emby-webcomponents.git",
|
||||
"_target": "^1.2.0",
|
||||
|
@ -25,12 +25,17 @@
|
||||
transition: transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1);
|
||||
transition: transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1),-webkit-transform .3s cubic-bezier(.4,0,.2,1);
|
||||
z-index: -1;*/
|
||||
max-height: 84%;
|
||||
}
|
||||
|
||||
.actionSheet.centered .actionSheetContent {
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
.layout-tv .actionSheet {
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.actionSheet.centered .actionSheetContent {
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.actionSheetContent {
|
||||
margin: 0 !important;
|
||||
|
@ -15,11 +15,13 @@ define(['dialogHelper', 'layoutManager', 'dialogText', 'html!./../prompt/icons.h
|
||||
|
||||
var backButton = false;
|
||||
var raisedButtons = false;
|
||||
var isFullscreen = false;
|
||||
|
||||
if (layoutManager.tv) {
|
||||
dialogOptions.size = 'fullscreen';
|
||||
backButton = true;
|
||||
raisedButtons = true;
|
||||
isFullscreen = true;
|
||||
} else {
|
||||
|
||||
dialogOptions.modal = false;
|
||||
@ -42,6 +44,9 @@ define(['dialogHelper', 'layoutManager', 'dialogText', 'html!./../prompt/icons.h
|
||||
html += '<h2>';
|
||||
html += options.title;
|
||||
html += '</h2>';
|
||||
} else if (!isFullscreen) {
|
||||
// Add a little space so it's not hugging the border
|
||||
html += '<br/>';
|
||||
}
|
||||
|
||||
var text = options.html || options.text;
|
||||
@ -62,7 +67,7 @@ define(['dialogHelper', 'layoutManager', 'dialogText', 'html!./../prompt/icons.h
|
||||
if (raisedButtons) {
|
||||
html += '<paper-button raised class="btnSubmit"><iron-icon icon="dialog:check"></iron-icon><span>' + dialogText.get(buttonText) + '</span></paper-button>';
|
||||
} else {
|
||||
html += '<div style="text-align:right;">';
|
||||
html += '<div class="buttons" style="text-align:right;">';
|
||||
html += '<paper-button class="btnSubmit">' + dialogText.get(buttonText) + '</paper-button>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
@ -77,11 +77,18 @@ define(['browser'], function (browser) {
|
||||
// Unfortunately there's no real way to detect mkv support
|
||||
if (browser.chrome) {
|
||||
|
||||
var userAgent = navigator.userAgent.toLowerCase();
|
||||
|
||||
// Not supported on opera tv
|
||||
if (browser.operaTv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter out browsers based on chromium that don't support mkv
|
||||
if (userAgent.indexOf('vivaldi') != -1 || userAgent.indexOf('opera') != -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -318,9 +325,7 @@ define(['browser'], function (browser) {
|
||||
AudioCodec: hlsVideoAudioCodecs.join(','),
|
||||
VideoCodec: 'h264',
|
||||
Context: 'Streaming',
|
||||
Protocol: 'hls',
|
||||
// Can't use this when autoplay is not supported
|
||||
ForceLiveStream: options.supportsCustomSeeking ? true : false
|
||||
Protocol: 'hls'
|
||||
});
|
||||
}
|
||||
|
||||
@ -411,6 +416,12 @@ define(['browser'], function (browser) {
|
||||
]
|
||||
});
|
||||
|
||||
var maxLevel = '41';
|
||||
|
||||
if (browser.chrome && !browser.mobile) {
|
||||
maxLevel = '51';
|
||||
}
|
||||
|
||||
profile.CodecProfiles.push({
|
||||
Type: 'Video',
|
||||
Codec: 'h264',
|
||||
@ -429,7 +440,7 @@ define(['browser'], function (browser) {
|
||||
{
|
||||
Condition: 'LessThanEqual',
|
||||
Property: 'VideoLevel',
|
||||
Value: '41'
|
||||
Value: maxLevel
|
||||
}]
|
||||
});
|
||||
|
||||
|
12
dashboard-ui/bower_components/emby-webcomponents/clearbutton.css
vendored
Normal file
12
dashboard-ui/bower_components/emby-webcomponents/clearbutton.css
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
.clearButton {
|
||||
background: transparent;
|
||||
border: 0 !important;
|
||||
padding: 0 !important;
|
||||
cursor: pointer;
|
||||
outline: none !important;
|
||||
color: inherit;
|
||||
width: 100%;
|
||||
vertical-align: middle;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
define([], function () {
|
||||
define(['globalize'], function (globalize) {
|
||||
|
||||
function parseISO8601Date(s, toLocal) {
|
||||
|
||||
@ -94,11 +94,28 @@ define([], function () {
|
||||
return parts.join(':');
|
||||
}
|
||||
|
||||
var toLocaleTimeStringSupportsLocales = function toLocaleTimeStringSupportsLocales() {
|
||||
try {
|
||||
new Date().toLocaleTimeString('i');
|
||||
} catch (e) {
|
||||
return e.name === 'RangeError';
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
|
||||
function getDisplayTime(date) {
|
||||
var time = date.toLocaleTimeString().toLowerCase();
|
||||
|
||||
if (time.indexOf('am') != -1 || time.indexOf('pm') != -1) {
|
||||
var currentLocale = globalize.getCurrentLocale();
|
||||
|
||||
var time = currentLocale && toLocaleTimeStringSupportsLocales ?
|
||||
date.toLocaleTimeString(currentLocale) :
|
||||
date.toLocaleTimeString();
|
||||
|
||||
var timeLower = time.toLowerCase();
|
||||
|
||||
if (timeLower.indexOf('am') != -1 || timeLower.indexOf('pm') != -1) {
|
||||
|
||||
time = timeLower;
|
||||
var hour = date.getHours() % 12;
|
||||
var suffix = date.getHours() > 11 ? 'pm' : 'am';
|
||||
if (!hour) {
|
||||
|
@ -127,6 +127,10 @@
|
||||
transition: opacity ease-out 0.2s;
|
||||
}
|
||||
|
||||
.mouseIdle .dialog::backdrop {
|
||||
cursor: none !important;
|
||||
}
|
||||
|
||||
.dialog.opened::backdrop {
|
||||
opacity: .6;
|
||||
}
|
||||
|
@ -24,8 +24,6 @@
|
||||
function onBackCommand(e) {
|
||||
|
||||
if (e.detail.command == 'back') {
|
||||
inputManager.off(dlg, onBackCommand);
|
||||
|
||||
self.closedByBack = true;
|
||||
closeDialog(dlg);
|
||||
e.preventDefault();
|
||||
@ -34,6 +32,9 @@
|
||||
|
||||
function onDialogClosed() {
|
||||
|
||||
inputManager.off(dlg, onBackCommand);
|
||||
window.removeEventListener('popstate', onHashChange);
|
||||
|
||||
removeBackdrop(dlg);
|
||||
dlg.classList.remove('opened');
|
||||
|
||||
@ -41,9 +42,6 @@
|
||||
document.body.classList.remove('noScroll');
|
||||
}
|
||||
|
||||
window.removeEventListener('popstate', onHashChange);
|
||||
inputManager.off(dlg, onBackCommand);
|
||||
|
||||
if (!self.closedByBack && isHistoryEnabled(dlg)) {
|
||||
var state = history.state || {};
|
||||
if (state.dialogId == hash) {
|
||||
|
11
dashboard-ui/bower_components/emby-webcomponents/filedownloader.js
vendored
Normal file
11
dashboard-ui/bower_components/emby-webcomponents/filedownloader.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
define(['multi-download'], function (multiDownload) {
|
||||
|
||||
return {
|
||||
download: function (items) {
|
||||
|
||||
multiDownload(items.map(function (item) {
|
||||
return item.url;
|
||||
}));
|
||||
}
|
||||
};
|
||||
});
|
408
dashboard-ui/bower_components/emby-webcomponents/guide/guide.css
vendored
Normal file
408
dashboard-ui/bower_components/emby-webcomponents/guide/guide.css
vendored
Normal file
@ -0,0 +1,408 @@
|
||||
.tvguide {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: initial;
|
||||
}
|
||||
|
||||
.tvGuideHeader {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tvProgramSectionHeader {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tvProgram {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tvProgramTimeSlotInner {
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
.tvProgramInfo {
|
||||
vertical-align: middle;
|
||||
padding: .5em .5em;
|
||||
border-bottom: .65vh solid #121212;
|
||||
}
|
||||
|
||||
.tvProgramCurrentTimeSlot {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.tvProgramName {
|
||||
color: #fff;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.tvProgramTime {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.newTvProgram {
|
||||
color: yellow;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.liveTvProgram {
|
||||
color: #64A239;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.premiereTvProgram {
|
||||
color: orange;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.programAccent {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
.sportsProgramInfo .programAccent {
|
||||
background-color: #0A7C33;
|
||||
}
|
||||
|
||||
.newsProgramInfo .programAccent {
|
||||
background-color: #523378;
|
||||
}
|
||||
|
||||
.movieProgramInfo .programAccent {
|
||||
background-color: #A43913;
|
||||
}
|
||||
|
||||
.childProgramInfo .programAccent {
|
||||
background-color: #0B487D;
|
||||
}
|
||||
|
||||
.timerCircle {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border-radius: 50%;
|
||||
background: #cc0000;
|
||||
border: 1px solid #cc3333;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.seriesTimerCircle {
|
||||
position: relative;
|
||||
margin-left: 0;
|
||||
left: 21px;
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
.itemMiscInfo .seriesTimerCircle:first-child {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.seriesTimerCircle + .seriesTimerCircle {
|
||||
opacity: .65;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.seriesTimerCircle + .seriesTimerCircle + .seriesTimerCircle {
|
||||
opacity: 1;
|
||||
left: -21px;
|
||||
}
|
||||
|
||||
.channelTimeslotHeader {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.timeslotHeaders {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mobileGuide .timeslotHeaders {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.programContainer {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.programGridContainer {
|
||||
margin-left: 12.2vw;
|
||||
}
|
||||
|
||||
.channelPrograms {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.channelPrograms, .timeslotHeadersInner {
|
||||
width: 1800vw;
|
||||
}
|
||||
|
||||
@media all and (min-width: 600px) {
|
||||
|
||||
.channelPrograms, .timeslotHeadersInner {
|
||||
width: 1400vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 800px) {
|
||||
|
||||
.channelPrograms, .timeslotHeadersInner {
|
||||
width: 1200vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 1280px) {
|
||||
|
||||
.channelPrograms, .timeslotHeadersInner {
|
||||
width: 660vw;
|
||||
}
|
||||
}
|
||||
|
||||
.timeslotHeader {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
text-indent: .35em;
|
||||
}
|
||||
|
||||
.channelHeaderCell, .channelTimeslotHeader {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
border-right: 1px solid #121212;
|
||||
width: 24vw;
|
||||
background: rgba(40, 40, 40, .9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #fff !important;
|
||||
text-decoration: none;
|
||||
/* Needed in firefox */
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@media all and (min-width: 500px) {
|
||||
|
||||
.channelHeaderCell, .channelTimeslotHeader {
|
||||
width: 16vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 600px) {
|
||||
|
||||
.channelHeaderCell, .channelTimeslotHeader {
|
||||
width: 16vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 800px) {
|
||||
|
||||
.channelHeaderCell, .channelTimeslotHeader {
|
||||
width: 14vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 1280px) {
|
||||
|
||||
.channelHeaderCell, .channelTimeslotHeader {
|
||||
width: 12vw;
|
||||
}
|
||||
}
|
||||
|
||||
.btnSelectDate {
|
||||
color: #fff;
|
||||
padding-left: .5em;
|
||||
}
|
||||
|
||||
.channelHeaderCell {
|
||||
border-bottom: .65vh solid #121212 !important;
|
||||
background-size: auto 65.7%;
|
||||
background-position: 90% center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
@media all and (max-width: 800px) {
|
||||
|
||||
.guideCurrentDay {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 1280px) {
|
||||
|
||||
.channelHeaderCell.withImage .guideChannelNumber {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.channelPrograms, .channelHeaderCell {
|
||||
height: 15vh;
|
||||
}
|
||||
|
||||
@media all and (min-height: 500px) {
|
||||
|
||||
.channelPrograms, .channelHeaderCell {
|
||||
height: 10.5vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-height: 600px) {
|
||||
|
||||
.channelPrograms, .channelHeaderCell {
|
||||
height: 9vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-height: 720px) {
|
||||
|
||||
.channelPrograms, .channelHeaderCell {
|
||||
height: 6vh;
|
||||
}
|
||||
|
||||
.layout-tv .channelPrograms, .layout-tv .channelHeaderCell {
|
||||
height: 7.6vh;
|
||||
}
|
||||
}
|
||||
|
||||
.channelTimeslotHeader {
|
||||
border-right-color: transparent;
|
||||
}
|
||||
|
||||
.channelTimeslotHeader, .timeslotHeader {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.timeslotHeader, .channelTimeslotHeader {
|
||||
height: 14vh;
|
||||
}
|
||||
|
||||
@media all and (min-height: 500px) {
|
||||
|
||||
.timeslotHeader, .channelTimeslotHeader {
|
||||
height: 10vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-height: 600px) {
|
||||
|
||||
.timeslotHeader, .channelTimeslotHeader {
|
||||
height: 8vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-height: 720px) {
|
||||
|
||||
.timeslotHeader, .channelTimeslotHeader {
|
||||
height: 5.75vh;
|
||||
}
|
||||
}
|
||||
|
||||
.pointerInput .channelHeaderCell:hover {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
.channelList {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.programGrid {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.timeslotHeader {
|
||||
width: 2.0833333333333333333333333333333%;
|
||||
}
|
||||
|
||||
.programCell {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
/* Unfortunately the borders using vh get rounded while the bottom property doesn't. So this is a little hack to try and make them even*/
|
||||
bottom: .59vh;
|
||||
border-left: .65vh solid #121212 !important;
|
||||
background-color: rgba(32, 32, 32, .95);
|
||||
display: flex;
|
||||
color: #fff !important;
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
/* Needed for Firefox */
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.timeslotCellInner {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.guideProgramName {
|
||||
padding: 0 .5em 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.guideProgramTime {
|
||||
padding: 0 .5em .35em;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.programCell iron-icon {
|
||||
margin-left: auto;
|
||||
margin-right: .5em;
|
||||
height: 3.5vh;
|
||||
width: 3.5vh;
|
||||
color: #ddd;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.programCell iron-icon + iron-icon {
|
||||
margin-left: .25em;
|
||||
}
|
||||
|
||||
.guideChannelNumber {
|
||||
padding-left: 1em;
|
||||
max-width: 30%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.guideChannelName {
|
||||
margin-left: auto;
|
||||
margin-right: 1em;
|
||||
max-width: 40%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media all and (max-width: 1000px) {
|
||||
|
||||
.guideChannelName {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.channelList, .programGrid {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.programCell:focus, .channelHeaderCell:focus, .btnSelectDate:focus {
|
||||
background-color: #52B54B;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.programCell:focus .programAccent {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.timerIcon, .seriesTimerIcon {
|
||||
color: #cc3333 !important;
|
||||
}
|
648
dashboard-ui/bower_components/emby-webcomponents/guide/guide.js
vendored
Normal file
648
dashboard-ui/bower_components/emby-webcomponents/guide/guide.js
vendored
Normal file
@ -0,0 +1,648 @@
|
||||
define(['globalize', 'connectionManager', 'loading', 'scrollHelper', 'datetime', 'focusManager', 'imageLoader', 'events', 'layoutManager', 'itemShortcuts', 'registrationservices', 'clearButtonStyle', 'css!./guide.css', 'html!./icons.html', 'scrollStyles'], function (globalize, connectionManager, loading, scrollHelper, datetime, focusManager, imageLoader, events, layoutManager, itemShortcuts, registrationServices) {
|
||||
|
||||
var baseUrl;
|
||||
|
||||
function Guide(options) {
|
||||
|
||||
var self = this;
|
||||
var items = {};
|
||||
|
||||
self.refresh = function () {
|
||||
reloadPage(options.element);
|
||||
};
|
||||
|
||||
self.destroy = function () {
|
||||
itemShortcuts.off(options.element);
|
||||
items = {};
|
||||
};
|
||||
|
||||
self.options = options;
|
||||
|
||||
// 30 mins
|
||||
var cellCurationMinutes = 30;
|
||||
var cellDurationMs = cellCurationMinutes * 60 * 1000;
|
||||
var msPerDay = 86400000;
|
||||
|
||||
var currentDate;
|
||||
|
||||
var channelQuery = {
|
||||
|
||||
StartIndex: 0,
|
||||
EnableFavoriteSorting: true
|
||||
};
|
||||
|
||||
var channelsPromise;
|
||||
|
||||
function normalizeDateToTimeslot(date) {
|
||||
|
||||
var minutesOffset = date.getMinutes() - cellCurationMinutes;
|
||||
|
||||
if (minutesOffset >= 0) {
|
||||
|
||||
date.setHours(date.getHours(), cellCurationMinutes, 0, 0);
|
||||
|
||||
} else {
|
||||
|
||||
date.setHours(date.getHours(), 0, 0, 0);
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
loading.show();
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
loading.hide();
|
||||
}
|
||||
|
||||
function getChannelLimit(context) {
|
||||
|
||||
return registrationServices.validateFeature('livetv').then(function () {
|
||||
|
||||
var limit = 400;
|
||||
|
||||
context.querySelector('.guideRequiresUnlock').classList.add('hide');
|
||||
|
||||
return limit;
|
||||
|
||||
}, function () {
|
||||
|
||||
var limit = 5;
|
||||
|
||||
context.querySelector('.guideRequiresUnlock').classList.remove('hide');
|
||||
context.querySelector('.unlockText').innerHTML = globalize.translate('MessageLiveTvGuideRequiresUnlock', limit);
|
||||
|
||||
return limit;
|
||||
});
|
||||
}
|
||||
|
||||
function reloadGuide(context, newStartDate) {
|
||||
|
||||
var apiClient = connectionManager.currentApiClient();
|
||||
|
||||
channelQuery.UserId = apiClient.getCurrentUserId();
|
||||
|
||||
getChannelLimit(context).then(function (channelLimit) {
|
||||
|
||||
showLoading();
|
||||
|
||||
channelQuery.Limit = channelLimit;
|
||||
channelQuery.AddCurrentProgram = false;
|
||||
|
||||
channelsPromise = channelsPromise || apiClient.getLiveTvChannels(channelQuery);
|
||||
|
||||
var date = newStartDate;
|
||||
// Add one second to avoid getting programs that are just ending
|
||||
date = new Date(date.getTime() + 1000);
|
||||
|
||||
// Subtract to avoid getting programs that are starting when the grid ends
|
||||
var nextDay = new Date(date.getTime() + msPerDay - 2000);
|
||||
|
||||
console.log(nextDay);
|
||||
channelsPromise.then(function (channelsResult) {
|
||||
|
||||
apiClient.getLiveTvPrograms({
|
||||
UserId: apiClient.getCurrentUserId(),
|
||||
MaxStartDate: nextDay.toISOString(),
|
||||
MinEndDate: date.toISOString(),
|
||||
channelIds: channelsResult.Items.map(function (c) {
|
||||
return c.Id;
|
||||
}).join(','),
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: "Primary,Backdrop",
|
||||
SortBy: "StartDate"
|
||||
|
||||
}).then(function (programsResult) {
|
||||
|
||||
renderGuide(context, date, channelsResult.Items, programsResult.Items, apiClient);
|
||||
|
||||
hideLoading();
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getDisplayTime(date) {
|
||||
|
||||
if ((typeof date).toString().toLowerCase() === 'string') {
|
||||
try {
|
||||
|
||||
date = datetime.parseISO8601Date(date, { toLocal: true });
|
||||
|
||||
} catch (err) {
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
return datetime.getDisplayTime(date).toLowerCase();
|
||||
}
|
||||
|
||||
function getTimeslotHeadersHtml(startDate, endDateTime) {
|
||||
|
||||
var html = '';
|
||||
|
||||
// clone
|
||||
startDate = new Date(startDate.getTime());
|
||||
|
||||
html += '<div class="timeslotHeadersInner">';
|
||||
|
||||
while (startDate.getTime() < endDateTime) {
|
||||
|
||||
html += '<div class="timeslotHeader">';
|
||||
|
||||
html += getDisplayTime(startDate);
|
||||
html += '</div>';
|
||||
|
||||
// Add 30 mins
|
||||
startDate.setTime(startDate.getTime() + cellDurationMs);
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function parseDates(program) {
|
||||
|
||||
if (!program.StartDateLocal) {
|
||||
try {
|
||||
|
||||
program.StartDateLocal = datetime.parseISO8601Date(program.StartDate, { toLocal: true });
|
||||
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!program.EndDateLocal) {
|
||||
try {
|
||||
|
||||
program.EndDateLocal = datetime.parseISO8601Date(program.EndDate, { toLocal: true });
|
||||
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getChannelProgramsHtml(context, date, channel, programs) {
|
||||
|
||||
var html = '';
|
||||
|
||||
var startMs = date.getTime();
|
||||
var endMs = startMs + msPerDay - 1;
|
||||
|
||||
programs = programs.filter(function (curr) {
|
||||
return curr.ChannelId == channel.Id;
|
||||
});
|
||||
|
||||
html += '<div class="channelPrograms">';
|
||||
|
||||
for (var i = 0, length = programs.length; i < length; i++) {
|
||||
|
||||
var program = programs[i];
|
||||
|
||||
if (program.ChannelId != channel.Id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
parseDates(program);
|
||||
|
||||
if (program.EndDateLocal.getTime() < startMs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (program.StartDateLocal.getTime() > endMs) {
|
||||
break;
|
||||
}
|
||||
|
||||
items[program.Id] = program;
|
||||
|
||||
var renderStartMs = Math.max(program.StartDateLocal.getTime(), startMs);
|
||||
var startPercent = (program.StartDateLocal.getTime() - startMs) / msPerDay;
|
||||
startPercent *= 100;
|
||||
startPercent = Math.max(startPercent, 0);
|
||||
|
||||
var renderEndMs = Math.min(program.EndDateLocal.getTime(), endMs);
|
||||
var endPercent = (renderEndMs - renderStartMs) / msPerDay;
|
||||
endPercent *= 100;
|
||||
|
||||
var cssClass = "programCell clearButton itemAction";
|
||||
var addAccent = true;
|
||||
|
||||
if (program.IsKids) {
|
||||
cssClass += " childProgramInfo";
|
||||
} else if (program.IsSports) {
|
||||
cssClass += " sportsProgramInfo";
|
||||
} else if (program.IsNews) {
|
||||
cssClass += " newsProgramInfo";
|
||||
} else if (program.IsMovie) {
|
||||
cssClass += " movieProgramInfo";
|
||||
}
|
||||
else {
|
||||
cssClass += " plainProgramInfo";
|
||||
addAccent = false;
|
||||
}
|
||||
|
||||
html += '<button data-action="link" data-isfolder="' + program.IsFolder + '" data-id="' + program.Id + '" data-serverid="' + program.ServerId + '" data-type="' + program.Type + '" class="' + cssClass + '" style="left:' + startPercent + '%;width:' + endPercent + '%;">';
|
||||
|
||||
var guideProgramNameClass = "guideProgramName";
|
||||
|
||||
html += '<div class="' + guideProgramNameClass + '">';
|
||||
|
||||
if (program.IsLive) {
|
||||
html += '<span class="liveTvProgram">' + globalize.translate('core#AttributeLive') + ' </span>';
|
||||
}
|
||||
else if (program.IsPremiere) {
|
||||
html += '<span class="premiereTvProgram">' + globalize.translate('core#AttributePremiere') + ' </span>';
|
||||
}
|
||||
else if (program.IsSeries && !program.IsRepeat) {
|
||||
html += '<span class="newTvProgram">' + globalize.translate('core#AttributeNew') + ' </span>';
|
||||
}
|
||||
|
||||
html += program.Name;
|
||||
html += '</div>';
|
||||
|
||||
if (program.IsHD) {
|
||||
html += '<iron-icon icon="guide:hd"></iron-icon>';
|
||||
}
|
||||
|
||||
if (program.SeriesTimerId) {
|
||||
html += '<iron-icon class="seriesTimerIcon" icon="guide:fiber-smart-record"></iron-icon>';
|
||||
}
|
||||
else if (program.TimerId) {
|
||||
html += '<iron-icon class="timerIcon" icon="guide:fiber-manual-record"></iron-icon>';
|
||||
}
|
||||
|
||||
if (addAccent) {
|
||||
html += '<div class="programAccent"></div>';
|
||||
}
|
||||
|
||||
html += '</button>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderPrograms(context, date, channels, programs) {
|
||||
|
||||
var html = [];
|
||||
|
||||
for (var i = 0, length = channels.length; i < length; i++) {
|
||||
|
||||
html.push(getChannelProgramsHtml(context, date, channels[i], programs));
|
||||
}
|
||||
|
||||
var programGrid = context.querySelector('.programGrid');
|
||||
programGrid.innerHTML = html.join('');
|
||||
|
||||
programGrid.scrollTop = 0;
|
||||
programGrid.scrollLeft = 0;
|
||||
}
|
||||
|
||||
function renderChannelHeaders(context, channels, apiClient) {
|
||||
|
||||
var html = '';
|
||||
|
||||
for (var i = 0, length = channels.length; i < length; i++) {
|
||||
|
||||
var channel = channels[i];
|
||||
var hasChannelImage = channel.ImageTags.Primary;
|
||||
var dataSrc = '';
|
||||
if (hasChannelImage) {
|
||||
|
||||
var url = apiClient.getScaledImageUrl(channel.Id, {
|
||||
maxHeight: 200,
|
||||
tag: channel.ImageTags.Primary,
|
||||
type: "Primary"
|
||||
});
|
||||
|
||||
dataSrc = ' data-src="' + url + '"';
|
||||
}
|
||||
|
||||
var cssClass = 'channelHeaderCell clearButton itemAction lazy';
|
||||
if (hasChannelImage) {
|
||||
cssClass += ' withImage';
|
||||
}
|
||||
|
||||
html += '<button type="button" class="' + cssClass + '"' + dataSrc + ' data-action="link" data-isfolder="' + channel.IsFolder + '" data-id="' + channel.Id + '" data-serverid="' + channel.ServerId + '" data-type="' + channel.Type + '">';
|
||||
|
||||
html += '<div class="guideChannelNumber">' + channel.Number + '</div>';
|
||||
|
||||
if (!hasChannelImage) {
|
||||
html += '<div class="guideChannelName">' + channel.Name + '</div>';
|
||||
}
|
||||
|
||||
html += '</button>';
|
||||
}
|
||||
|
||||
var channelList = context.querySelector('.channelList');
|
||||
channelList.innerHTML = html;
|
||||
imageLoader.lazyChildren(channelList);
|
||||
}
|
||||
|
||||
function renderGuide(context, date, channels, programs, apiClient) {
|
||||
|
||||
//var list = [];
|
||||
//channels.forEach(function(i) {
|
||||
// list.push(i);
|
||||
//});
|
||||
//channels.forEach(function (i) {
|
||||
// list.push(i);
|
||||
//});
|
||||
//channels.forEach(function (i) {
|
||||
// list.push(i);
|
||||
//});
|
||||
//channels.forEach(function (i) {
|
||||
// list.push(i);
|
||||
//});
|
||||
//channels.forEach(function (i) {
|
||||
// list.push(i);
|
||||
//});
|
||||
//channels.forEach(function (i) {
|
||||
// list.push(i);
|
||||
//});
|
||||
//channels.forEach(function (i) {
|
||||
// list.push(i);
|
||||
//});
|
||||
//channels.forEach(function (i) {
|
||||
// list.push(i);
|
||||
//});
|
||||
//channels.forEach(function (i) {
|
||||
// list.push(i);
|
||||
//});
|
||||
//channels.forEach(function (i) {
|
||||
// list.push(i);
|
||||
//});
|
||||
//channels.forEach(function (i) {
|
||||
// list.push(i);
|
||||
//});
|
||||
//channels.forEach(function (i) {
|
||||
// list.push(i);
|
||||
//});
|
||||
//channels = list;
|
||||
renderChannelHeaders(context, channels, apiClient);
|
||||
|
||||
var startDate = date;
|
||||
var endDate = new Date(startDate.getTime() + msPerDay);
|
||||
context.querySelector('.timeslotHeaders').innerHTML = getTimeslotHeadersHtml(startDate, endDate);
|
||||
items = {};
|
||||
renderPrograms(context, date, channels, programs);
|
||||
|
||||
if (layoutManager.tv) {
|
||||
focusManager.autoFocus(context.querySelector('.programGrid'), true);
|
||||
}
|
||||
}
|
||||
|
||||
function nativeScrollTo(container, pos, horizontal) {
|
||||
|
||||
if (container.scrollTo) {
|
||||
if (horizontal) {
|
||||
container.scrollTo(pos, 0);
|
||||
} else {
|
||||
container.scrollTo(0, pos);
|
||||
}
|
||||
} else {
|
||||
if (horizontal) {
|
||||
container.scrollLeft = Math.round(pos);
|
||||
} else {
|
||||
container.scrollTop = Math.round(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var lastGridScroll = 0;
|
||||
var lastHeaderScroll = 0;
|
||||
function onProgramGridScroll(context, elem, timeslotHeaders) {
|
||||
|
||||
if ((new Date().getTime() - lastHeaderScroll) >= 1000) {
|
||||
lastGridScroll = new Date().getTime();
|
||||
nativeScrollTo(timeslotHeaders, elem.scrollLeft, true);
|
||||
}
|
||||
}
|
||||
|
||||
function onTimeslotHeadersScroll(context, elem, programGrid) {
|
||||
|
||||
if ((new Date().getTime() - lastGridScroll) >= 1000) {
|
||||
lastHeaderScroll = new Date().getTime();
|
||||
nativeScrollTo(programGrid, elem.scrollLeft, true);
|
||||
}
|
||||
}
|
||||
|
||||
function getFutureDateText(date) {
|
||||
|
||||
var weekday = [];
|
||||
weekday[0] = globalize.translate('core#OptionSundayShort');
|
||||
weekday[1] = globalize.translate('core#OptionMondayShort');
|
||||
weekday[2] = globalize.translate('core#OptionTuesdayShort');
|
||||
weekday[3] = globalize.translate('core#OptionWednesdayShort');
|
||||
weekday[4] = globalize.translate('core#OptionThursdayShort');
|
||||
weekday[5] = globalize.translate('core#OptionFridayShort');
|
||||
weekday[6] = globalize.translate('core#OptionSaturdayShort');
|
||||
|
||||
var day = weekday[date.getDay()];
|
||||
date = date.toLocaleDateString();
|
||||
|
||||
if (date.toLowerCase().indexOf(day.toLowerCase()) == -1) {
|
||||
return day + " " + date;
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
function changeDate(page, date) {
|
||||
|
||||
var newStartDate = normalizeDateToTimeslot(date);
|
||||
currentDate = newStartDate;
|
||||
|
||||
reloadGuide(page, newStartDate);
|
||||
|
||||
var text = getFutureDateText(date);
|
||||
text = '<span class="guideCurrentDay">' + text.replace(' ', ' </span>');
|
||||
page.querySelector('.btnSelectDate').innerHTML = text;
|
||||
}
|
||||
|
||||
var dateOptions = [];
|
||||
|
||||
function setDateRange(page, guideInfo) {
|
||||
|
||||
var today = new Date();
|
||||
today.setHours(today.getHours(), 0, 0, 0);
|
||||
|
||||
var start = datetime.parseISO8601Date(guideInfo.StartDate, { toLocal: true });
|
||||
var end = datetime.parseISO8601Date(guideInfo.EndDate, { toLocal: true });
|
||||
|
||||
start.setHours(0, 0, 0, 0);
|
||||
end.setHours(0, 0, 0, 0);
|
||||
|
||||
if (start.getTime() >= end.getTime()) {
|
||||
end.setDate(start.getDate() + 1);
|
||||
}
|
||||
|
||||
start = new Date(Math.max(today, start));
|
||||
|
||||
dateOptions = [];
|
||||
|
||||
while (start <= end) {
|
||||
|
||||
dateOptions.push({
|
||||
name: getFutureDateText(start),
|
||||
id: start.getTime()
|
||||
});
|
||||
|
||||
start.setDate(start.getDate() + 1);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
var date = new Date();
|
||||
|
||||
if (currentDate) {
|
||||
date.setTime(currentDate.getTime());
|
||||
}
|
||||
|
||||
changeDate(page, date);
|
||||
}
|
||||
|
||||
function reloadPage(page) {
|
||||
|
||||
showLoading();
|
||||
|
||||
var apiClient = connectionManager.currentApiClient();
|
||||
|
||||
apiClient.getLiveTvGuideInfo().then(function (guideInfo) {
|
||||
|
||||
setDateRange(page, guideInfo);
|
||||
});
|
||||
}
|
||||
|
||||
function selectDate(page) {
|
||||
|
||||
require(['actionsheet'], function (actionsheet) {
|
||||
|
||||
actionsheet.show({
|
||||
items: dateOptions,
|
||||
title: globalize.translate('core#HeaderSelectDate'),
|
||||
callback: function (id) {
|
||||
|
||||
var date = new Date();
|
||||
date.setTime(parseInt(id));
|
||||
changeDate(page, date);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function createVerticalScroller(view, pageInstance) {
|
||||
|
||||
if (layoutManager.tv) {
|
||||
scrollHelper.centerFocus.on(view.querySelector('.smoothScrollY'), false);
|
||||
|
||||
var programGrid = view.querySelector('.programGrid');
|
||||
|
||||
scrollHelper.centerFocus.on(programGrid, true);
|
||||
}
|
||||
}
|
||||
|
||||
function parentWithClass(elem, className) {
|
||||
|
||||
while (!elem.classList || !elem.classList.contains(className)) {
|
||||
elem = elem.parentNode;
|
||||
|
||||
if (!elem) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
var selectedMediaInfoTimeout;
|
||||
var focusedElement;
|
||||
function onProgramGridFocus(e) {
|
||||
|
||||
var programCell = parentWithClass(e.target, 'programCell');
|
||||
|
||||
if (!programCell) {
|
||||
return;
|
||||
}
|
||||
|
||||
focusedElement = e.target;
|
||||
if (selectedMediaInfoTimeout) {
|
||||
clearTimeout(selectedMediaInfoTimeout);
|
||||
}
|
||||
selectedMediaInfoTimeout = setTimeout(onSelectedMediaInfoTimeout, 700);
|
||||
}
|
||||
|
||||
function onSelectedMediaInfoTimeout() {
|
||||
var focused = focusedElement
|
||||
if (focused && document.activeElement == focused) {
|
||||
var id = focused.getAttribute('data-id');
|
||||
var item = items[id];
|
||||
|
||||
if (item) {
|
||||
events.trigger(self, 'focus', [
|
||||
{
|
||||
item: item
|
||||
}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', baseUrl + '/tvguide.template.html', true);
|
||||
|
||||
xhr.onload = function (e) {
|
||||
|
||||
var template = this.response;
|
||||
var context = options.element;
|
||||
context.innerHTML = globalize.translateDocument(template, 'core');
|
||||
|
||||
var programGrid = context.querySelector('.programGrid');
|
||||
var timeslotHeaders = context.querySelector('.timeslotHeaders');
|
||||
|
||||
programGrid.addEventListener('focus', onProgramGridFocus, true);
|
||||
programGrid.addEventListener('scroll', function () {
|
||||
|
||||
onProgramGridScroll(context, this, timeslotHeaders);
|
||||
});
|
||||
|
||||
timeslotHeaders.addEventListener('scroll', function () {
|
||||
onTimeslotHeadersScroll(context, this, programGrid);
|
||||
});
|
||||
|
||||
context.querySelector('.btnSelectDate').addEventListener('click', function () {
|
||||
selectDate(context);
|
||||
});
|
||||
|
||||
context.querySelector('.btnUnlockGuide').addEventListener('click', function () {
|
||||
reloadPage(context);
|
||||
});
|
||||
|
||||
context.classList.add('tvguide');
|
||||
|
||||
createVerticalScroller(context, self);
|
||||
itemShortcuts.on(context);
|
||||
|
||||
events.trigger(self, 'load');
|
||||
|
||||
self.refresh();
|
||||
}
|
||||
|
||||
xhr.send();
|
||||
};
|
||||
|
||||
Guide.setBaseUrl = function (url) {
|
||||
baseUrl = url;
|
||||
};
|
||||
|
||||
return Guide;
|
||||
});
|
42
dashboard-ui/bower_components/emby-webcomponents/guide/icons.html
vendored
Normal file
42
dashboard-ui/bower_components/emby-webcomponents/guide/icons.html
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
<!--
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
-->
|
||||
<!--
|
||||
`iron-icons` is a utility import that includes the definition for the `iron-icon` element, `iron-iconset-svg` element, as well as an import for the default icon set.
|
||||
|
||||
The `iron-icons` directory also includes imports for additional icon sets that can be loaded into your project.
|
||||
|
||||
Example loading icon set:
|
||||
|
||||
<link rel="import" href="../iron-icons/maps-icons.html">
|
||||
|
||||
To use an icon from one of these sets, first prefix your `iron-icon` with the icon set name, followed by a colon, ":", and then the icon id.
|
||||
|
||||
Example using the directions-bus icon from the maps icon set:
|
||||
|
||||
<iron-icon icon="maps:directions-bus"></iron-icon>
|
||||
|
||||
|
||||
See [iron-icon](#iron-icon) for more information about working with icons.
|
||||
|
||||
See [iron-iconset](#iron-iconset) and [iron-iconset-svg](#iron-iconset-svg) for more information about how to create a custom iconset.
|
||||
|
||||
@group Iron Elements
|
||||
@element iron-icons
|
||||
@demo demo/index.html
|
||||
-->
|
||||
|
||||
<iron-iconset-svg name="guide" size="24">
|
||||
<svg>
|
||||
<defs>
|
||||
<g id="fiber-manual-record"><circle cx="12" cy="12" r="8" /></g>
|
||||
<g id="fiber-smart-record"><g><circle cx="9" cy="12" r="8" /><path d="M17 4.26v2.09c2.33.82 4 3.04 4 5.65s-1.67 4.83-4 5.65v2.09c3.45-.89 6-4.01 6-7.74s-2.55-6.85-6-7.74z" /></g></g>
|
||||
<g id="hd"><path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-8 12H9.5v-2h-2v2H6V9h1.5v2.5h2V9H11v6zm2-6h4c.55 0 1 .45 1 1v4c0 .55-.45 1-1 1h-4V9zm1.5 4.5h2v-3h-2v3z" /></g>
|
||||
</defs>
|
||||
</svg>
|
||||
</iron-iconset-svg>
|
22
dashboard-ui/bower_components/emby-webcomponents/guide/tvguide.template.html
vendored
Normal file
22
dashboard-ui/bower_components/emby-webcomponents/guide/tvguide.template.html
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<div class="tvGuideHeader">
|
||||
<div class="channelTimeslotHeader">
|
||||
<button class="btnSelectDate clearButton" style="display:block;"></button>
|
||||
</div>
|
||||
<div class="timeslotHeaders smoothScrollX" style="scroll-behavior: auto;"></div>
|
||||
</div>
|
||||
|
||||
<div class="smoothScrollY guideVerticalScroller" style="flex-grow: 1;">
|
||||
|
||||
<div class="programContainer">
|
||||
<div class="channelList"></div>
|
||||
|
||||
<div class="programGridContainer programGrid smoothScrollX" style="white-space: nowrap;">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="guideRequiresUnlock readOnlyContent hide" style="margin:1em auto;text-align:center;padding:1em;flex-shrink:0;">
|
||||
<p class="unlockText"></p>
|
||||
<paper-button raised class="secondary block btnUnlockGuide"><iron-icon icon="check"></iron-icon><span>${ButtonUnlockGuide}</span></paper-button>
|
||||
</div>
|
@ -4,16 +4,8 @@ define([], function () {
|
||||
|
||||
if (elem.tagName !== "IMG") {
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
var tmp = new Image();
|
||||
|
||||
tmp.onload = function () {
|
||||
elem.style.backgroundImage = "url('" + url + "')";
|
||||
resolve(elem);
|
||||
};
|
||||
tmp.src = url;
|
||||
});
|
||||
elem.style.backgroundImage = "url('" + url + "')";
|
||||
return Promise.resolve(elem);
|
||||
|
||||
} else {
|
||||
elem.setAttribute("src", url);
|
||||
|
@ -4,16 +4,8 @@ define(['cryptojs-md5'], function () {
|
||||
|
||||
if (elem.tagName !== "IMG") {
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
var tmp = new Image();
|
||||
|
||||
tmp.onload = function () {
|
||||
elem.style.backgroundImage = "url('" + url + "')";
|
||||
resolve(elem);
|
||||
};
|
||||
tmp.src = url;
|
||||
});
|
||||
elem.style.backgroundImage = "url('" + url + "')";
|
||||
return Promise.resolve(elem);
|
||||
|
||||
} else {
|
||||
elem.setAttribute("src", url);
|
||||
|
@ -1,4 +1,4 @@
|
||||
define(['layoutManager', 'MaterialSpinner', 'css!./loading'], function (layoutManager) {
|
||||
define(['MaterialSpinner', 'css!./loading'], function () {
|
||||
|
||||
return {
|
||||
show: function () {
|
||||
@ -11,10 +11,6 @@ define(['layoutManager', 'MaterialSpinner', 'css!./loading'], function (layoutMa
|
||||
elem.classList.add('mdl-spinner');
|
||||
elem.classList.add('mdl-js-spinner');
|
||||
|
||||
if (layoutManager.tv) {
|
||||
elem.classList.add('tv');
|
||||
}
|
||||
|
||||
document.body.appendChild(elem);
|
||||
componentHandler.upgradeElement(elem, 'MaterialSpinner');
|
||||
}
|
||||
|
@ -1,21 +1,14 @@
|
||||
.docspinner {
|
||||
margin-top: -4vh;
|
||||
margin-left: -4vh;
|
||||
width: 7vh;
|
||||
height: 7vh;
|
||||
margin-top: -5vh;
|
||||
margin-left: -5vh;
|
||||
width: 10vh;
|
||||
height: 10vh;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: 9999999;
|
||||
}
|
||||
|
||||
.docspinner.tv {
|
||||
margin-top: -5vh;
|
||||
margin-left: -5vh;
|
||||
width: 10vh;
|
||||
height: 10vh;
|
||||
}
|
||||
|
||||
.loadingHide {
|
||||
display: none !important;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
define(['layoutManager', 'paper-spinner', 'css!./loading'], function (layoutManager) {
|
||||
define(['paper-spinner', 'css!./loading'], function () {
|
||||
|
||||
return {
|
||||
show: function () {
|
||||
@ -9,10 +9,6 @@ define(['layoutManager', 'paper-spinner', 'css!./loading'], function (layoutMana
|
||||
elem = document.createElement("paper-spinner");
|
||||
elem.classList.add('docspinner');
|
||||
|
||||
if (layoutManager.tv) {
|
||||
elem.classList.add('tv');
|
||||
}
|
||||
|
||||
document.body.appendChild(elem);
|
||||
}
|
||||
|
||||
|
73
dashboard-ui/bower_components/emby-webcomponents/multidownload.js
vendored
Normal file
73
dashboard-ui/bower_components/emby-webcomponents/multidownload.js
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.multiDownload = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
'use strict';
|
||||
|
||||
function fallback(urls) {
|
||||
var i = 0;
|
||||
|
||||
(function createIframe() {
|
||||
var frame = document.createElement('iframe');
|
||||
frame.style.display = 'none';
|
||||
frame.src = urls[i++];
|
||||
document.documentElement.appendChild(frame);
|
||||
|
||||
// the download init has to be sequential otherwise IE only use the first
|
||||
var interval = setInterval(function () {
|
||||
if (frame.contentWindow.document.readyState === 'complete') {
|
||||
clearInterval(interval);
|
||||
|
||||
// Safari needs a timeout
|
||||
setTimeout(function () {
|
||||
frame.parentNode.removeChild(frame);
|
||||
}, 1000);
|
||||
|
||||
if (i < urls.length) {
|
||||
createIframe();
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
})();
|
||||
}
|
||||
|
||||
function isFirefox() {
|
||||
// sad panda :(
|
||||
return /Firefox\//i.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
function sameDomain(url) {
|
||||
var a = document.createElement('a');
|
||||
a.href = url;
|
||||
|
||||
return location.hostname === a.hostname && location.protocol === a.protocol;
|
||||
}
|
||||
|
||||
function download(url) {
|
||||
var a = document.createElement('a');
|
||||
a.download = '';
|
||||
a.href = url;
|
||||
// firefox doesn't support `a.click()`...
|
||||
a.dispatchEvent(new MouseEvent('click'));
|
||||
}
|
||||
|
||||
module.exports = function (urls) {
|
||||
if (!urls) {
|
||||
throw new Error('`urls` required');
|
||||
}
|
||||
|
||||
if (typeof document.createElement('a').download === 'undefined') {
|
||||
return fallback(urls);
|
||||
}
|
||||
|
||||
var delay = 0;
|
||||
|
||||
urls.forEach(function (url) {
|
||||
// the download init has to be sequential for firefox if the urls are not on the same domain
|
||||
if (isFirefox() && !sameDomain(url)) {
|
||||
return setTimeout(download.bind(null, url), 100 * ++delay);
|
||||
}
|
||||
|
||||
download(url);
|
||||
});
|
||||
}
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
@ -12,7 +12,7 @@
|
||||
|
||||
.promptDialogContent {
|
||||
text-align: left;
|
||||
padding: 2em;
|
||||
padding: 1em 2em;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
|
@ -271,17 +271,17 @@ define(['loading', 'viewManager', 'skinManager', 'pluginManager', 'backdrop', 'b
|
||||
var apiClient = connectionManager.currentApiClient();
|
||||
var pathname = ctx.pathname.toLowerCase();
|
||||
|
||||
console.log('Emby.Page - processing path request ' + pathname);
|
||||
console.log('embyRouter - processing path request ' + pathname);
|
||||
|
||||
if ((!apiClient || !apiClient.isLoggedIn()) && !route.anonymous) {
|
||||
console.log('Emby.Page - route does not allow anonymous access, redirecting to login');
|
||||
console.log('embyRouter - route does not allow anonymous access, redirecting to login');
|
||||
beginConnectionWizard();
|
||||
return;
|
||||
}
|
||||
|
||||
if (apiClient && apiClient.isLoggedIn()) {
|
||||
|
||||
console.log('Emby.Page - user is authenticated');
|
||||
console.log('embyRouter - user is authenticated');
|
||||
|
||||
var isCurrentRouteStartup = currentRouteInfo ? currentRouteInfo.route.startup : true;
|
||||
if (ctx.isBack && (route.isDefaultRoute || route.startup) && !isCurrentRouteStartup) {
|
||||
@ -289,7 +289,7 @@ define(['loading', 'viewManager', 'skinManager', 'pluginManager', 'backdrop', 'b
|
||||
return;
|
||||
}
|
||||
else if (route.isDefaultRoute) {
|
||||
console.log('Emby.Page - loading skin home page');
|
||||
console.log('embyRouter - loading skin home page');
|
||||
skinManager.loadUserSkin();
|
||||
return;
|
||||
} else if (route.roles) {
|
||||
@ -303,7 +303,7 @@ define(['loading', 'viewManager', 'skinManager', 'pluginManager', 'backdrop', 'b
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Emby.Page - proceeding to ' + pathname);
|
||||
console.log('embyRouter - proceeding to ' + pathname);
|
||||
callback();
|
||||
}
|
||||
|
||||
@ -484,8 +484,10 @@ define(['loading', 'viewManager', 'skinManager', 'pluginManager', 'backdrop', 'b
|
||||
function showItem(item) {
|
||||
|
||||
if (typeof (item) === 'string') {
|
||||
Emby.Models.item(item).then(showItem);
|
||||
|
||||
require(['connectionManager'], function (connectionManager) {
|
||||
var apiClient = connectionManager.currentApiClient();
|
||||
apiClient.getItem(apiClient.getCurrentUserId(), item).then(showItem);
|
||||
});
|
||||
} else {
|
||||
skinManager.getCurrentSkin().showItem(item);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
.smoothScrollX {
|
||||
.smoothScrollX, .hiddenScrollX {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overflow-y: hidden;
|
||||
@ -6,45 +6,30 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hiddenScrollX {
|
||||
overflow-x: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.hiddenScrollX, .layout-tv .smoothScrollX, .layout-mobile .smoothScrollX {
|
||||
.hiddenScrollX, .layout-tv .smoothScrollX {
|
||||
-ms-overflow-style: none;
|
||||
overflow: -moz-scrollbars-none;
|
||||
}
|
||||
|
||||
.hiddenScrollX::-webkit-scrollbar, .layout-tv .smoothScrollX::-webkit-scrollbar, .layout-mobile .smoothScrollX::-webkit-scrollbar {
|
||||
.hiddenScrollX::-webkit-scrollbar, .layout-tv .smoothScrollX::-webkit-scrollbar {
|
||||
height: 0 !important;
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.smoothScrollY {
|
||||
.smoothScrollY, .hiddenScrollY {
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overflow-x: hidden;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.hiddenScrollY, .layout-tv .smoothScrollY, .layout-mobile .smoothScrollY {
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overflow-x: hidden;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.hiddenScrollY {
|
||||
.hiddenScrollY, .layout-tv .smoothScrollY {
|
||||
-ms-overflow-style: none;
|
||||
overflow: -moz-scrollbars-none;
|
||||
}
|
||||
|
||||
.hiddenScrollY::-webkit-scrollbar, .layout-tv .smoothScrollY::-webkit-scrollbar, .layout-mobile .smoothScrollY::-webkit-scrollbar {
|
||||
.hiddenScrollY::-webkit-scrollbar, .layout-tv .smoothScrollY::-webkit-scrollbar {
|
||||
width: 0 !important;
|
||||
display: none;
|
||||
}
|
||||
|
60
dashboard-ui/bower_components/emby-webcomponents/sharing/sharingmanager.js
vendored
Normal file
60
dashboard-ui/bower_components/emby-webcomponents/sharing/sharingmanager.js
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
define(['connectionManager', 'sharingMenu', 'loading'], function (connectionManager, sharingMenu, loading) {
|
||||
|
||||
function onSharingCancel(options, apiClient) {
|
||||
|
||||
var shareId = options.share.Id;
|
||||
|
||||
console.log('share cancelled. shareId: ' + shareId);
|
||||
|
||||
// Delete the share since it was cancelled
|
||||
apiClient.ajax({
|
||||
|
||||
type: 'DELETE',
|
||||
url: apiClient.getUrl('Social/Shares/' + shareId)
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function showMenu(options) {
|
||||
|
||||
loading.show();
|
||||
var itemId = options.itemId;
|
||||
var apiClient = connectionManager.getApiClient(options.serverId);
|
||||
var userId = apiClient.getCurrentUserId();
|
||||
|
||||
return apiClient.getItem(userId, itemId).then(function () {
|
||||
|
||||
return apiClient.ajax({
|
||||
type: 'POST',
|
||||
url: apiClient.getUrl('Social/Shares', {
|
||||
|
||||
ItemId: itemId,
|
||||
UserId: userId
|
||||
}),
|
||||
dataType: "json"
|
||||
|
||||
}).then(function (share) {
|
||||
|
||||
var options = {
|
||||
share: share
|
||||
};
|
||||
|
||||
loading.hide();
|
||||
|
||||
sharingMenu.showMenu(options).then(function() {
|
||||
console.log('share success. shareId: ' + options.share.Id);
|
||||
}, function () {
|
||||
onSharingCancel(options, apiClient);
|
||||
});
|
||||
|
||||
}, function () {
|
||||
|
||||
loading.hide();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
showMenu: showMenu
|
||||
};
|
||||
});
|
@ -1,34 +1,28 @@
|
||||
define(['dialogHelper', 'jQuery', 'thirdparty/social-share-kit-1.0.4/dist/js/social-share-kit.min', 'css!thirdparty/social-share-kit-1.0.4/dist/css/social-share-kit.css'], function (dialogHelper, $) {
|
||||
define(['dialogHelper', 'layoutManager', 'dialogText', './social-share-kit-1.0.4/dist/js/social-share-kit.min', 'css!./social-share-kit-1.0.4/dist/css/social-share-kit.css'], function (dialogHelper, layoutManager, dialogText) {
|
||||
|
||||
function showMenu(options, successCallback, cancelCallback) {
|
||||
function showMenu(options) {
|
||||
|
||||
var dlg = dialogHelper.createDialog({
|
||||
removeOnClose: true,
|
||||
autoFocus: false
|
||||
autoFocus: layoutManager.tv
|
||||
});
|
||||
|
||||
dlg.id = 'dlg' + new Date().getTime();
|
||||
var html = '';
|
||||
|
||||
html += '<h2>' + Globalize.translate('HeaderShare') + '</h2>';
|
||||
html += '<h2>' + Globalize.translate('Share') + '</h2>';
|
||||
|
||||
html += '<div>';
|
||||
html += '<div class="ssk-group ssk-round ssk-lg">';
|
||||
|
||||
// We can only do facebook if we can guarantee that the current page is available over the internet, since FB will try to probe it.
|
||||
if (Dashboard.isConnectMode()) {
|
||||
html += '<a href="" class="ssk ssk-facebook"></a>';
|
||||
}
|
||||
html += '<a href="" class="ssk ssk-facebook"></a>';
|
||||
|
||||
html += '<a href="" class="ssk ssk-twitter"></a><a href="" class="ssk ssk-google-plus"></a><a href="" class="ssk ssk-pinterest"></a><a href="" class="ssk ssk-tumblr"></a></div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div style="max-width:240px;">';
|
||||
html += Globalize.translate('ButtonShareHelp');
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="buttons">';
|
||||
html += '<paper-button class="btnCancel" dialog-dismiss>' + Globalize.translate('ButtonCancel') + '</paper-button>';
|
||||
html += '<paper-button class="btnCancel">' + dialogText.get('Cancel') + '</paper-button>';
|
||||
html += '</div>';
|
||||
|
||||
dlg.innerHTML = html;
|
||||
@ -48,28 +42,36 @@
|
||||
via: 'Emby'
|
||||
});
|
||||
|
||||
// Has to be assigned a z-index after the call to .open()
|
||||
dlg.addEventListener('close', function () {
|
||||
|
||||
if (isShared) {
|
||||
successCallback(options);
|
||||
} else {
|
||||
cancelCallback(options);
|
||||
}
|
||||
});
|
||||
|
||||
// Has to be assigned a z-index after the call to .open()
|
||||
$('.ssk', dlg).on('click', function () {
|
||||
function onSskButtonClick(e) {
|
||||
isShared = true;
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
}
|
||||
|
||||
// Has to be assigned a z-index after the call to .open()
|
||||
var sskButtons = dlg.querySelectorAll('.ssk');
|
||||
for (var i = 0, length = sskButtons.length; i < length; i++) {
|
||||
sskButtons[i].addEventListener('click', onSskButtonClick);
|
||||
}
|
||||
|
||||
// Has to be assigned a z-index after the call to .open()
|
||||
dlg.querySelector('.btnCancel').addEventListener('click', function () {
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
|
||||
var promise = new Promise(function (resolve, reject) {
|
||||
|
||||
dlg.addEventListener('close', function () {
|
||||
if (isShared) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
dialogHelper.open(dlg);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
return {
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
168
dashboard-ui/bower_components/emby-webcomponents/shortcuts.js
vendored
Normal file
168
dashboard-ui/bower_components/emby-webcomponents/shortcuts.js
vendored
Normal file
@ -0,0 +1,168 @@
|
||||
define(['playbackManager', 'inputManager', 'connectionManager', 'embyRouter'], function (playbackManager, inputManager, connectionManager, embyRouter) {
|
||||
|
||||
function playAllFromHere(card, serverId) {
|
||||
var cards = card.parentNode.querySelectorAll('.itemAction[data-id]');
|
||||
var ids = [];
|
||||
|
||||
var foundCard = false;
|
||||
for (var i = 0, length = cards.length; i < length; i++) {
|
||||
if (cards[i] == card) {
|
||||
foundCard = true;
|
||||
}
|
||||
if (foundCard) {
|
||||
ids.push(cards[i].getAttribute('data-id'));
|
||||
}
|
||||
}
|
||||
playbackManager.play({
|
||||
ids: ids,
|
||||
serverId: serverId
|
||||
});
|
||||
}
|
||||
|
||||
function showSlideshow(startItemId, serverId) {
|
||||
|
||||
var apiClient = connectionManager.getApiClient(serverId);
|
||||
var userId = apiClient.getCurrentUserId();
|
||||
|
||||
return apiClient.getItem(userId, startItemId).then(function (item) {
|
||||
|
||||
return apiClient.getItems(userId, {
|
||||
|
||||
MediaTypes: 'Photo',
|
||||
Filters: 'IsNotFolder',
|
||||
ParentId: item.ParentId
|
||||
|
||||
}).then(function (result) {
|
||||
|
||||
var items = result.Items;
|
||||
|
||||
var index = items.map(function (i) {
|
||||
return i.Id;
|
||||
|
||||
}).indexOf(startItemId);
|
||||
|
||||
if (index == -1) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
require(['slideshow'], function (slideshow) {
|
||||
|
||||
var newSlideShow = new slideshow({
|
||||
showTitle: false,
|
||||
cover: false,
|
||||
items: items,
|
||||
startIndex: index,
|
||||
interval: 8000,
|
||||
interactive: true
|
||||
});
|
||||
|
||||
newSlideShow.show();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showItem(options) {
|
||||
|
||||
if (options.Type == 'Photo') {
|
||||
|
||||
showSlideshow(options.Id, options.ServerId);
|
||||
return;
|
||||
}
|
||||
|
||||
embyRouter.showItem(options);
|
||||
}
|
||||
|
||||
function executeAction(card, action) {
|
||||
var id = card.getAttribute('data-id');
|
||||
var serverId = card.getAttribute('data-serverid');
|
||||
var type = card.getAttribute('data-type');
|
||||
var isfolder = card.getAttribute('data-isfolder') == 'true';
|
||||
|
||||
if (action == 'link') {
|
||||
showItem({
|
||||
Id: id,
|
||||
Type: type,
|
||||
IsFolder: isfolder,
|
||||
ServerId: serverId
|
||||
});
|
||||
}
|
||||
|
||||
else if (action == 'instantmix') {
|
||||
playbackManager.instantMix(id, serverId);
|
||||
}
|
||||
|
||||
else if (action == 'play') {
|
||||
|
||||
var startPositionTicks = parseInt(card.getAttribute('data-startpositionticks') || '0');
|
||||
|
||||
playbackManager.play({
|
||||
ids: [id],
|
||||
startPositionTicks: startPositionTicks,
|
||||
serverId: serverId
|
||||
});
|
||||
}
|
||||
|
||||
else if (action == 'playallfromhere') {
|
||||
playAllFromHere(card, serverId);
|
||||
}
|
||||
|
||||
else if (action == 'setplaylistindex') {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function onClick(e) {
|
||||
var card = parentWithClass(e.target, 'itemAction');
|
||||
|
||||
if (card) {
|
||||
var action = card.getAttribute('data-action');
|
||||
|
||||
if (action) {
|
||||
executeAction(card, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parentWithClass(elem, className) {
|
||||
|
||||
while (!elem.classList || !elem.classList.contains(className)) {
|
||||
elem = elem.parentNode;
|
||||
|
||||
if (!elem) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
function onCommand(e) {
|
||||
var cmd = e.detail.command;
|
||||
|
||||
if (cmd == 'play') {
|
||||
var card = parentWithClass(e.target, 'itemAction');
|
||||
|
||||
if (card) {
|
||||
executeAction(card, cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function on(context) {
|
||||
context.addEventListener('click', onClick);
|
||||
inputManager.on(context, onCommand);
|
||||
}
|
||||
|
||||
function off(context) {
|
||||
context.removeEventListener('click', onClick);
|
||||
inputManager.off(context, onCommand);
|
||||
}
|
||||
|
||||
return {
|
||||
on: on,
|
||||
off: off
|
||||
};
|
||||
|
||||
});
|
@ -34,11 +34,13 @@ See [iron-iconset](#iron-iconset) and [iron-iconset-svg](#iron-iconset-svg) for
|
||||
<iron-iconset-svg name="slideshow" size="24">
|
||||
<svg>
|
||||
<defs>
|
||||
<g id="arrow-back"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" /></g>
|
||||
<g id="close"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" /></g>
|
||||
<g id="pause"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" /></g>
|
||||
<g id="skip-next"><path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z" /></g>
|
||||
<g id="skip-previous"><path d="M6 6h2v12H6zm3.5 6l8.5 6V6z" /></g>
|
||||
<g id="keyboard-arrow-left"><path d="M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z" /></g>
|
||||
<g id="keyboard-arrow-right"><path d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z" /></g>
|
||||
<g id="play-arrow"><path d="M8 5v14l11-7z" /></g>
|
||||
<g id="file-download"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" /></g>
|
||||
<g id="share"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z" /></g>
|
||||
</defs>
|
||||
</svg>
|
||||
</iron-iconset-svg>
|
||||
|
@ -1,16 +1,117 @@
|
||||
define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'css!./style', 'html!./icons', 'iron-icon-set', 'paper-fab', 'paper-icon-button', 'paper-spinner'], function (dialogHelper, inputmanager, connectionManager, layoutManager) {
|
||||
define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'apphost', 'css!./style', 'html!./icons', 'iron-icon-set', 'paper-icon-button', 'paper-spinner'], function (dialogHelper, inputmanager, connectionManager, layoutManager, focusManager, appHost) {
|
||||
|
||||
function getImageUrl(item, options, apiClient) {
|
||||
|
||||
options = options || {};
|
||||
options.type = options.type || "Primary";
|
||||
|
||||
if (typeof (item) === 'string') {
|
||||
return apiClient.getScaledImageUrl(item, options);
|
||||
}
|
||||
|
||||
if (item.ImageTags && item.ImageTags[options.type]) {
|
||||
|
||||
options.tag = item.ImageTags[options.type];
|
||||
return apiClient.getScaledImageUrl(item.Id, options);
|
||||
}
|
||||
|
||||
if (options.type == 'Primary') {
|
||||
if (item.AlbumId && item.AlbumPrimaryImageTag) {
|
||||
|
||||
options.tag = item.AlbumPrimaryImageTag;
|
||||
return apiClient.getScaledImageUrl(item.AlbumId, options);
|
||||
}
|
||||
|
||||
//else if (item.AlbumId && item.SeriesPrimaryImageTag) {
|
||||
|
||||
// imgUrl = ApiClient.getScaledImageUrl(item.SeriesId, {
|
||||
// type: "Primary",
|
||||
// width: downloadWidth,
|
||||
// tag: item.SeriesPrimaryImageTag,
|
||||
// minScale: minScale
|
||||
// });
|
||||
|
||||
//}
|
||||
//else if (item.ParentPrimaryImageTag) {
|
||||
|
||||
// imgUrl = ApiClient.getImageUrl(item.ParentPrimaryImageItemId, {
|
||||
// type: "Primary",
|
||||
// width: downloadWidth,
|
||||
// tag: item.ParentPrimaryImageTag,
|
||||
// minScale: minScale
|
||||
// });
|
||||
//}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBackdropImageUrl(item, options, apiClient) {
|
||||
|
||||
options = options || {};
|
||||
options.type = options.type || "Backdrop";
|
||||
|
||||
options.width = null;
|
||||
delete options.width;
|
||||
options.maxWidth = null;
|
||||
delete options.maxWidth;
|
||||
options.maxHeight = null;
|
||||
delete options.maxHeight;
|
||||
options.height = null;
|
||||
delete options.height;
|
||||
|
||||
// If not resizing, get the original image
|
||||
if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) {
|
||||
options.quality = 100;
|
||||
}
|
||||
|
||||
if (item.BackdropImageTags && item.BackdropImageTags.length) {
|
||||
|
||||
options.tag = item.BackdropImageTags[0];
|
||||
return apiClient.getScaledImageUrl(item.Id, options);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getImgUrl(item, original) {
|
||||
|
||||
var apiClient = connectionManager.getApiClient(item.ServerId);
|
||||
var imageOptions = {};
|
||||
|
||||
if (!original) {
|
||||
imageOptions.maxWidth = screen.availWidth;
|
||||
}
|
||||
if (item.BackdropImageTags && item.BackdropImageTags.length) {
|
||||
return getBackdropImageUrl(item, imageOptions, apiClient);
|
||||
} else {
|
||||
|
||||
if (item.MediaType == 'Photo' && original) {
|
||||
return apiClient.getUrl("Items/" + item.Id + "/Download", {
|
||||
api_key: apiClient.accessToken()
|
||||
});
|
||||
}
|
||||
imageOptions.type = "Primary";
|
||||
return getImageUrl(item, imageOptions, apiClient);
|
||||
}
|
||||
}
|
||||
|
||||
return function (options) {
|
||||
|
||||
var self = this;
|
||||
var swiperInstance;
|
||||
var dlg;
|
||||
var currentTimeout;
|
||||
var currentIntervalMs;
|
||||
var currentOptions;
|
||||
var currentIndex;
|
||||
|
||||
function createElements(options) {
|
||||
|
||||
dlg = dialogHelper.createDialog({
|
||||
exitAnimationDuration: options.interactive ? 400 : 800,
|
||||
size: 'fullscreen'
|
||||
size: 'fullscreen',
|
||||
autoFocus: false
|
||||
});
|
||||
|
||||
dlg.classList.add('slideshowDialog');
|
||||
@ -19,16 +120,41 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c
|
||||
|
||||
if (options.interactive) {
|
||||
|
||||
var actionButtonsOnTop = layoutManager.mobile;
|
||||
|
||||
html += '<div>';
|
||||
html += '<div class="slideshowSwiperContainer"><div class="swiper-wrapper"></div></div>';
|
||||
|
||||
html += '<paper-fab mini icon="slideshow:arrow-back" class="btnSlideshowExit" tabindex="-1"></paper-fab>';
|
||||
html += '<paper-icon-button icon="slideshow:keyboard-arrow-left" class="btnSlideshowPrevious slideshowButton" tabindex="-1"></paper-icon-button>';
|
||||
html += '<paper-icon-button icon="slideshow:keyboard-arrow-right" class="btnSlideshowNext slideshowButton" tabindex="-1"></paper-icon-button>';
|
||||
|
||||
html += '<div class="slideshowControlBar">';
|
||||
html += '<paper-icon-button icon="slideshow:skip-previous" class="btnSlideshowPrevious slideshowButton"></paper-icon-button>';
|
||||
html += '<paper-icon-button icon="slideshow:pause" class="btnSlideshowPause slideshowButton" autoFocus></paper-icon-button>';
|
||||
html += '<paper-icon-button icon="slideshow:skip-next" class="btnSlideshowNext slideshowButton"></paper-icon-button>';
|
||||
html += '<div class="topActionButtons">';
|
||||
if (actionButtonsOnTop) {
|
||||
if (appHost.supports('filedownload')) {
|
||||
html += '<paper-icon-button icon="slideshow:file-download" class="btnDownload slideshowButton"></paper-icon-button>';
|
||||
}
|
||||
if (appHost.supports('sharing')) {
|
||||
html += '<paper-icon-button icon="slideshow:share" class="btnShare slideshowButton"></paper-icon-button>';
|
||||
}
|
||||
}
|
||||
html += '<paper-icon-button icon="slideshow:close" class="btnSlideshowExit" tabindex="-1"></paper-icon-button>';
|
||||
html += '</div>';
|
||||
|
||||
if (!actionButtonsOnTop) {
|
||||
html += '<div class="slideshowBottomBar hide">';
|
||||
|
||||
//html += '<paper-icon-button icon="slideshow:share" class="btnShare slideshowButton"></paper-icon-button>';
|
||||
html += '<paper-icon-button icon="slideshow:pause" class="btnSlideshowPause slideshowButton" autoFocus></paper-icon-button>';
|
||||
if (appHost.supports('filedownload')) {
|
||||
html += '<paper-icon-button icon="slideshow:file-download" class="btnDownload slideshowButton"></paper-icon-button>';
|
||||
}
|
||||
if (appHost.supports('sharing')) {
|
||||
html += '<paper-icon-button icon="slideshow:share" class="btnShare slideshowButton"></paper-icon-button>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
} else {
|
||||
@ -44,7 +170,21 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c
|
||||
});
|
||||
dlg.querySelector('.btnSlideshowNext').addEventListener('click', nextImage);
|
||||
dlg.querySelector('.btnSlideshowPrevious').addEventListener('click', previousImage);
|
||||
dlg.querySelector('.btnSlideshowPause').addEventListener('click', playPause);
|
||||
|
||||
var btnPause = dlg.querySelector('.btnSlideshowPause');
|
||||
if (btnPause) {
|
||||
btnPause.addEventListener('click', playPause);
|
||||
}
|
||||
|
||||
var btnDownload = dlg.querySelector('.btnDownload');
|
||||
if (btnDownload) {
|
||||
btnDownload.addEventListener('click', download);
|
||||
}
|
||||
|
||||
var btnShare = dlg.querySelector('.btnShare');
|
||||
if (btnShare) {
|
||||
btnShare.addEventListener('click', share);
|
||||
}
|
||||
}
|
||||
|
||||
document.body.appendChild(dlg);
|
||||
@ -56,6 +196,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c
|
||||
});
|
||||
|
||||
inputmanager.on(window, onInputCommand);
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
|
||||
dlg.addEventListener('close', onDialogClosed);
|
||||
|
||||
@ -101,9 +242,12 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c
|
||||
function getSwiperSlideHtmlFromItem(item) {
|
||||
|
||||
return getSwiperSlideHtmlFromSlide({
|
||||
imageUrl: getImgUrl(item)
|
||||
imageUrl: getImgUrl(item),
|
||||
originalImage: getImgUrl(item, true),
|
||||
//title: item.Name,
|
||||
//description: item.Overview
|
||||
Id: item.Id,
|
||||
ServerId: item.ServerId
|
||||
});
|
||||
}
|
||||
|
||||
@ -128,7 +272,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c
|
||||
function getSwiperSlideHtmlFromSlide(item) {
|
||||
|
||||
var html = '';
|
||||
html += '<div class="swiper-slide">';
|
||||
html += '<div class="swiper-slide" data-original="' + item.originalImage + '" data-itemid="' + item.Id + '" data-serverid="' + item.ServerId + '">';
|
||||
html += '<img data-src="' + item.imageUrl + '" class="swiper-lazy">';
|
||||
html += '<paper-spinner></paper-spinner>';
|
||||
if (item.title || item.subtitle) {
|
||||
@ -179,15 +323,59 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentImageInfo() {
|
||||
|
||||
if (swiperInstance) {
|
||||
var slide = document.querySelector('.swiper-slide-active');
|
||||
|
||||
if (slide) {
|
||||
return {
|
||||
url: slide.getAttribute('data-original'),
|
||||
itemId: slide.getAttribute('data-itemid'),
|
||||
serverId: slide.getAttribute('data-serverid')
|
||||
};
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function download() {
|
||||
|
||||
var imageInfo = getCurrentImageInfo();
|
||||
|
||||
require(['fileDownloader'], function (fileDownloader) {
|
||||
fileDownloader.download([imageInfo]);
|
||||
});
|
||||
}
|
||||
|
||||
function share() {
|
||||
|
||||
var imageInfo = getCurrentImageInfo();
|
||||
|
||||
require(['sharingmanager'], function (sharingManager) {
|
||||
sharingManager.showMenu(imageInfo);
|
||||
});
|
||||
}
|
||||
|
||||
function play() {
|
||||
|
||||
dlg.querySelector('.btnSlideshowPause').icon = "slideshow:pause";
|
||||
var btnSlideshowPause = dlg.querySelector('.btnSlideshowPause');
|
||||
if (btnSlideshowPause) {
|
||||
btnSlideshowPause.icon = "slideshow:pause";
|
||||
}
|
||||
|
||||
swiperInstance.startAutoplay();
|
||||
}
|
||||
|
||||
function pause() {
|
||||
|
||||
dlg.querySelector('.btnSlideshowPause').icon = "slideshow:play-arrow";
|
||||
var btnSlideshowPause = dlg.querySelector('.btnSlideshowPause');
|
||||
if (btnSlideshowPause) {
|
||||
btnSlideshowPause.icon = "slideshow:play-arrow";
|
||||
}
|
||||
|
||||
swiperInstance.stopAutoplay();
|
||||
}
|
||||
|
||||
@ -212,13 +400,9 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c
|
||||
}
|
||||
|
||||
inputmanager.off(window, onInputCommand);
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
}
|
||||
|
||||
var currentTimeout;
|
||||
var currentIntervalMs;
|
||||
var currentOptions;
|
||||
var currentIndex;
|
||||
|
||||
function startInterval(options) {
|
||||
|
||||
currentOptions = options;
|
||||
@ -232,93 +416,143 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c
|
||||
}
|
||||
}
|
||||
|
||||
function getImgUrl(item) {
|
||||
var _osdOpen = false;
|
||||
function isOsdOpen() {
|
||||
return _osdOpen;
|
||||
}
|
||||
|
||||
var apiClient = connectionManager.getApiClient(item.ServerId);
|
||||
if (item.BackdropImageTags && item.BackdropImageTags.length) {
|
||||
return getBackdropImageUrl(item, {
|
||||
maxWidth: screen.availWidth
|
||||
}, apiClient);
|
||||
} else {
|
||||
return getImageUrl(item, {
|
||||
type: "Primary",
|
||||
maxWidth: screen.availWidth
|
||||
}, apiClient);
|
||||
function getOsdBottom() {
|
||||
return dlg.querySelector('.slideshowBottomBar');
|
||||
}
|
||||
|
||||
function showOsd() {
|
||||
|
||||
var bottom = getOsdBottom();
|
||||
if (bottom) {
|
||||
slideUpToShow(bottom);
|
||||
startHideTimer();
|
||||
}
|
||||
}
|
||||
|
||||
function getBackdropImageUrl(item, options, apiClient) {
|
||||
function hideOsd() {
|
||||
|
||||
options = options || {};
|
||||
options.type = options.type || "Backdrop";
|
||||
|
||||
options.width = null;
|
||||
delete options.width;
|
||||
options.maxWidth = null;
|
||||
delete options.maxWidth;
|
||||
options.maxHeight = null;
|
||||
delete options.maxHeight;
|
||||
options.height = null;
|
||||
delete options.height;
|
||||
|
||||
// If not resizing, get the original image
|
||||
if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) {
|
||||
options.quality = 100;
|
||||
var bottom = getOsdBottom();
|
||||
if (bottom) {
|
||||
slideDownToHide(bottom);
|
||||
}
|
||||
|
||||
if (item.BackdropImageTags && item.BackdropImageTags.length) {
|
||||
|
||||
options.tag = item.BackdropImageTags[0];
|
||||
return apiClient.getScaledImageUrl(item.Id, options);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getImageUrl(item, options, apiClient) {
|
||||
var hideTimeout;
|
||||
function startHideTimer() {
|
||||
stopHideTimer();
|
||||
hideTimeout = setTimeout(hideOsd, 4000);
|
||||
}
|
||||
function stopHideTimer() {
|
||||
if (hideTimeout) {
|
||||
clearTimeout(hideTimeout);
|
||||
hideTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
options.type = options.type || "Primary";
|
||||
function slideUpToShow(elem) {
|
||||
|
||||
if (typeof (item) === 'string') {
|
||||
return apiClient.getScaledImageUrl(item, options);
|
||||
if (!elem.classList.contains('hide')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.ImageTags && item.ImageTags[options.type]) {
|
||||
_osdOpen = true;
|
||||
elem.classList.remove('hide');
|
||||
|
||||
options.tag = item.ImageTags[options.type];
|
||||
return apiClient.getScaledImageUrl(item.Id, options);
|
||||
requestAnimationFrame(function () {
|
||||
|
||||
var keyframes = [
|
||||
{ transform: 'translate3d(0,' + elem.offsetHeight + 'px,0)', opacity: '.3', offset: 0 },
|
||||
{ transform: 'translate3d(0,0,0)', opacity: '1', offset: 1 }];
|
||||
var timing = { duration: 300, iterations: 1, easing: 'ease-out' };
|
||||
elem.animate(keyframes, timing).onfinish = function () {
|
||||
focusManager.focus(elem.querySelector('.btnSlideshowPause'));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function slideDownToHide(elem) {
|
||||
|
||||
if (elem.classList.contains('hide')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.type == 'Primary') {
|
||||
if (item.AlbumId && item.AlbumPrimaryImageTag) {
|
||||
requestAnimationFrame(function () {
|
||||
|
||||
options.tag = item.AlbumPrimaryImageTag;
|
||||
return apiClient.getScaledImageUrl(item.AlbumId, options);
|
||||
}
|
||||
var keyframes = [
|
||||
{ transform: 'translate3d(0,0,0)', opacity: '1', offset: 0 },
|
||||
{ transform: 'translate3d(0,' + elem.offsetHeight + 'px,0)', opacity: '.3', offset: 1 }];
|
||||
var timing = { duration: 300, iterations: 1, easing: 'ease-out' };
|
||||
elem.animate(keyframes, timing).onfinish = function () {
|
||||
elem.classList.add('hide');
|
||||
_osdOpen = false;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
//else if (item.AlbumId && item.SeriesPrimaryImageTag) {
|
||||
var lastMouseMoveData;
|
||||
function onMouseMove(e) {
|
||||
|
||||
// imgUrl = ApiClient.getScaledImageUrl(item.SeriesId, {
|
||||
// type: "Primary",
|
||||
// width: downloadWidth,
|
||||
// tag: item.SeriesPrimaryImageTag,
|
||||
// minScale: minScale
|
||||
// });
|
||||
var eventX = e.screenX || 0;
|
||||
var eventY = e.screenY || 0;
|
||||
|
||||
//}
|
||||
//else if (item.ParentPrimaryImageTag) {
|
||||
|
||||
// imgUrl = ApiClient.getImageUrl(item.ParentPrimaryImageItemId, {
|
||||
// type: "Primary",
|
||||
// width: downloadWidth,
|
||||
// tag: item.ParentPrimaryImageTag,
|
||||
// minScale: minScale
|
||||
// });
|
||||
//}
|
||||
var obj = lastMouseMoveData;
|
||||
if (!obj) {
|
||||
lastMouseMoveData = {
|
||||
x: eventX,
|
||||
y: eventY
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
return null;
|
||||
// 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;
|
||||
|
||||
showOsd();
|
||||
}
|
||||
|
||||
function onInputCommand(e) {
|
||||
|
||||
switch (e.detail.command) {
|
||||
|
||||
case 'left':
|
||||
if (!isOsdOpen()) {
|
||||
e.preventDefault();
|
||||
previousImage();
|
||||
}
|
||||
break;
|
||||
case 'right':
|
||||
if (!isOsdOpen()) {
|
||||
e.preventDefault();
|
||||
nextImage();
|
||||
}
|
||||
break;
|
||||
case 'up':
|
||||
case 'down':
|
||||
case 'select':
|
||||
case 'menu':
|
||||
case 'info':
|
||||
case 'play':
|
||||
case 'playpause':
|
||||
case 'pause':
|
||||
case 'fastforward':
|
||||
case 'rewind':
|
||||
case 'next':
|
||||
case 'previous':
|
||||
showOsd();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function showNextImage(index, skipPreload) {
|
||||
@ -397,33 +631,6 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'c
|
||||
}
|
||||
}
|
||||
|
||||
function onInputCommand(e) {
|
||||
|
||||
switch (e.detail.command) {
|
||||
|
||||
case 'left':
|
||||
previousImage();
|
||||
break;
|
||||
case 'right':
|
||||
nextImage();
|
||||
break;
|
||||
case 'play':
|
||||
play();
|
||||
break;
|
||||
case 'pause':
|
||||
pause();
|
||||
break;
|
||||
case 'playpause':
|
||||
playPause();
|
||||
break;
|
||||
default:
|
||||
return
|
||||
break;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
self.show = function () {
|
||||
startInterval(options);
|
||||
};
|
||||
|
@ -8,7 +8,6 @@
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1001;
|
||||
background-position: center center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
@ -46,43 +45,84 @@
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.btnSlideshowExit {
|
||||
.slideshowDialog paper-icon-button {
|
||||
width: 5.2vh;
|
||||
height: 5.2vh;
|
||||
color: #fff;
|
||||
opacity: .7;
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.layout-tv .slideshowDialog paper-icon-button {
|
||||
width: 7vh;
|
||||
height: 7vh;
|
||||
}
|
||||
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.3), only screen and (min-device-pixel-ratio: 1.3), only screen and (min-resolution: 120dpi) {
|
||||
.slideshowDialog paper-icon-button {
|
||||
width: 11vmin;
|
||||
height: 11vmin;
|
||||
}
|
||||
}
|
||||
|
||||
.btnSlideshowPrevious {
|
||||
left: .5vh;
|
||||
top: 45vh;
|
||||
z-index: 1002;
|
||||
position: absolute;
|
||||
top: 1.5vh;
|
||||
left: 1.5vh;
|
||||
width: 6vh;
|
||||
height: 6vh;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
paper-fab.btnSlideshowExit {
|
||||
background-color: #444;
|
||||
.btnSlideshowNext {
|
||||
right: .5vh;
|
||||
top: 45vh;
|
||||
z-index: 1002;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.slideshowControlBar {
|
||||
.topActionButtons {
|
||||
right: .5vh;
|
||||
top: .5vh;
|
||||
z-index: 1002;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.slideshowBottomBar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1002;
|
||||
background: rgba(0,0,0,.5);
|
||||
text-align: center;
|
||||
color: #eee;
|
||||
background-color: rgba(0, 0, 0, .7);
|
||||
color: #fff;
|
||||
padding: .5%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mouseIdle .btnSlideshowExit {
|
||||
.slideshowTopBar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, .7);
|
||||
color: #fff;
|
||||
padding: .5%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
text-align: right;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.mouseIdle .btnSlideshowPrevious, .mouseIdle .btnSlideshowNext, .mouseIdle .btnSlideshowExit {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mouseIdle .slideshowControlBar {
|
||||
transform: translateY(100%);
|
||||
transition: transform 600ms ease-out;
|
||||
}
|
||||
|
||||
.slideshowButton {
|
||||
width: 8vh;
|
||||
height: 8vh;
|
||||
.slideshowExtraButtons {
|
||||
margin-left: auto;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.slideText {
|
||||
|
36
dashboard-ui/bower_components/fingerprintjs2/.bower.json
vendored
Normal file
36
dashboard-ui/bower_components/fingerprintjs2/.bower.json
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "fingerprintjs2",
|
||||
"description": "Modern & flexible browser fingerprinting library",
|
||||
"main": "dist/fingerprint2.min.js",
|
||||
"moduleType": [
|
||||
"es6"
|
||||
],
|
||||
"keywords": [
|
||||
"browser",
|
||||
"fingerprint",
|
||||
"fingerprinting",
|
||||
"security",
|
||||
"privacy"
|
||||
],
|
||||
"authors": [
|
||||
"Valentin Vasilev"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"spec"
|
||||
],
|
||||
"homepage": "https://github.com/Valve/fingerprintjs2",
|
||||
"version": "1.1.4",
|
||||
"_release": "1.1.4",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "1.1.4",
|
||||
"commit": "ae5719db3d942a1a84ab43b707d4d1c34138934e"
|
||||
},
|
||||
"_source": "https://github.com/Valve/fingerprintjs2.git",
|
||||
"_target": "^1.1.3",
|
||||
"_originalSource": "fingerprintjs2"
|
||||
}
|
35
dashboard-ui/bower_components/fingerprintjs2/CONTRIBUTING.md
vendored
Normal file
35
dashboard-ui/bower_components/fingerprintjs2/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
Contributing to FingerprintJS2
|
||||
==============================
|
||||
|
||||
## Found a bug?
|
||||
|
||||
Please submit an issue.
|
||||
Include in the issue:
|
||||
|
||||
* List of components you received in the `get` call (make sure values are not truncated)
|
||||
* If FP is different every time you call the library, include 2 versions of components
|
||||
* Include your OS version
|
||||
* Include steps to reproduce
|
||||
* Include library call code (I need all options you used when calling the library function)
|
||||
|
||||
## Want to add a feature / contribute?
|
||||
|
||||
* Fork the project and make the required changes in it (don't forget to add specs)
|
||||
* PRs w/out specs will not be accepted
|
||||
* Run `gulp` to catch stylistic errors and produce the minified version.
|
||||
* Run specs by opening the `specs/spec_runner.html` or typing `npm test` (requires phantomjs for console running).
|
||||
* Make a PR.
|
||||
* Make sure you only make one commit per feature you want to add
|
||||
* Make sure your commit message is descriptive and tells what you changed (`Updated the library` - that's a bad commit message)
|
||||
|
||||
If your code changes the list of fingerprinting sources, please update
|
||||
the README.
|
||||
|
||||
If you're unsure about the feature you want to add, submit an issue with
|
||||
a `question` tag.
|
||||
|
||||
## Want to ask?
|
||||
* Please read FAQ first
|
||||
* If you have not found the answer you were looking for - use gitter.im to ask your question (link is in the readme)
|
||||
|
||||
Happy Coding!
|
18
dashboard-ui/bower_components/fingerprintjs2/FAQ.md
vendored
Normal file
18
dashboard-ui/bower_components/fingerprintjs2/FAQ.md
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
#### Can I use this library to uniquely identify users?
|
||||
##### No, you cannot. This library is built to be able to associate string identifiers with devices. Since there are a lot of identical devices, you will get a lot of identical identifiers.
|
||||
|
||||
#### OK, I get it, I cannot _uniquely_ identify users, but can I identify users at all?
|
||||
##### No, you cannot. This library is strictly for non-deterministic device identification.
|
||||
|
||||
|
||||
#### How good is your library? Can you guarantee that different devices will have different identifiers?
|
||||
##### This library is not good. It has an error margin of 10-20%
|
||||
|
||||
#### Can you improve the library to be 100% accurate for device identification?
|
||||
##### I don't think it is possible now and don't think it will be possible in the future.
|
||||
|
||||
#### Can you improve the library to be more accurate (since you cannot make it 100% accurate)?
|
||||
##### I can, but it takes a lot of time. I need a lot of devices, enviroments and more importantly - time. Since this is my hobby project, I spend very little time on it.
|
||||
|
||||
#### How can I build a complete identification solution?
|
||||
##### You should either use commercial services, such as https://augur.io, or develop such service yourself. If you don't know how to do it, please use StackOverflow.
|
25
dashboard-ui/bower_components/fingerprintjs2/bower.json
vendored
Normal file
25
dashboard-ui/bower_components/fingerprintjs2/bower.json
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "fingerprintjs2",
|
||||
"description": "Modern & flexible browser fingerprinting library",
|
||||
"main": "dist/fingerprint2.min.js",
|
||||
"moduleType": [
|
||||
"es6"
|
||||
],
|
||||
"keywords": [
|
||||
"browser",
|
||||
"fingerprint",
|
||||
"fingerprinting",
|
||||
"security",
|
||||
"privacy"
|
||||
],
|
||||
"authors": [
|
||||
"Valentin Vasilev"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"spec"
|
||||
]
|
||||
}
|
2
dashboard-ui/bower_components/fingerprintjs2/dist/fingerprint2.min.js
vendored
Normal file
2
dashboard-ui/bower_components/fingerprintjs2/dist/fingerprint2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1211
dashboard-ui/bower_components/fingerprintjs2/fingerprint2.js
vendored
Normal file
1211
dashboard-ui/bower_components/fingerprintjs2/fingerprint2.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
31
dashboard-ui/bower_components/fingerprintjs2/flash/FontList.as
vendored
Normal file
31
dashboard-ui/bower_components/fingerprintjs2/flash/FontList.as
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package {
|
||||
import flash.display.Sprite;
|
||||
import flash.display.LoaderInfo;
|
||||
import flash.text.Font;
|
||||
import flash.external.ExternalInterface;
|
||||
|
||||
public class FontList extends Sprite {
|
||||
|
||||
public function FontList() {
|
||||
var params:Object = loadParams();
|
||||
loadExternalInterface(params);
|
||||
}
|
||||
|
||||
private function loadParams():Object {
|
||||
return LoaderInfo(this.root.loaderInfo).parameters;
|
||||
}
|
||||
|
||||
private function loadExternalInterface(params:Object):void {
|
||||
ExternalInterface.call(params.onReady, fonts());
|
||||
}
|
||||
|
||||
private function fonts():Array {
|
||||
var fontNames:Array = [];
|
||||
for each (var font:Font in Font.enumerateFonts(true) )
|
||||
{
|
||||
fontNames.push(font.fontName);
|
||||
}
|
||||
return fontNames;
|
||||
}
|
||||
}
|
||||
}
|
7
dashboard-ui/bower_components/fingerprintjs2/flash/Makefile
vendored
Normal file
7
dashboard-ui/bower_components/fingerprintjs2/flash/Makefile
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
all: FontList.swf
|
||||
|
||||
FontList.swf: clean
|
||||
mxmlc -static-link-runtime-shared-libraries FontList.as
|
||||
|
||||
clean:
|
||||
rm -f FontList.swf
|
BIN
dashboard-ui/bower_components/fingerprintjs2/flash/compiled/FontList.swf
vendored
Normal file
BIN
dashboard-ui/bower_components/fingerprintjs2/flash/compiled/FontList.swf
vendored
Normal file
Binary file not shown.
29
dashboard-ui/bower_components/fingerprintjs2/gulpfile.js
vendored
Normal file
29
dashboard-ui/bower_components/fingerprintjs2/gulpfile.js
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
var gulp = require("gulp"),
|
||||
eslint = require("gulp-eslint"),
|
||||
rename = require("gulp-rename"),
|
||||
uglify = require("gulp-uglify");
|
||||
|
||||
gulp.task("lint", function() {
|
||||
return gulp
|
||||
.src("fingerprint2.js")
|
||||
.pipe(eslint())
|
||||
.pipe(eslint.format())
|
||||
.pipe(eslint.failOnError());
|
||||
});
|
||||
|
||||
gulp.task("minify", function() {
|
||||
return gulp
|
||||
.src("fingerprint2.js")
|
||||
.pipe(rename({suffix: ".min"}))
|
||||
.pipe(uglify({
|
||||
compress: {
|
||||
global_defs: {
|
||||
NODEBUG: true
|
||||
}
|
||||
}
|
||||
}))
|
||||
.pipe(gulp.dest("dist/"));
|
||||
});
|
||||
|
||||
|
||||
gulp.task("default", ["lint", "minify"], function() {});
|
98
dashboard-ui/bower_components/fingerprintjs2/index.html
vendored
Normal file
98
dashboard-ui/bower_components/fingerprintjs2/index.html
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Fingerprintjs2 test</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-42202458-2', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
|
||||
<style>
|
||||
body{
|
||||
font-family: sans-serif;
|
||||
max-width: 48em;
|
||||
margin: auto;
|
||||
padding: 0 5%;
|
||||
background: #222;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 2em 0 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.2em
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-size: 1.2em;
|
||||
background: #27e;
|
||||
padding: 0.5em 0.75em 0.6em;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 3px 0 #05c;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(3px);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
letter-spacing: 1px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
@media (min-width: 32em) {
|
||||
h1 {
|
||||
font-size: 4em;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
|
||||
|
||||
<h1>Fingerprintjs2</h1>
|
||||
|
||||
<p>Your browser fingerprint: <strong id="fp"></strong></p>
|
||||
<p><code id="time"/></p>
|
||||
|
||||
<button type="button" id="btn">Get my fingerprint</button>
|
||||
|
||||
<a href="https://github.com/Valve/fingerprintjs2"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a>
|
||||
|
||||
<script src="fingerprint2.js"></script>
|
||||
<script>
|
||||
$("#btn").on("click", function () {
|
||||
var d1 = new Date();
|
||||
var fp = new Fingerprint2();
|
||||
fp.get(function(result) {
|
||||
var d2 = new Date();
|
||||
var timeString = "Time took to calculate the fingerprint: " + (d2 - d1) + "ms";
|
||||
if(typeof window.console !== "undefined") {
|
||||
console.log(timeString);
|
||||
console.log(result);
|
||||
}
|
||||
$("#fp").text(result);
|
||||
$("#time").text(timeString);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
33
dashboard-ui/bower_components/fingerprintjs2/package.json
vendored
Normal file
33
dashboard-ui/bower_components/fingerprintjs2/package.json
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "fingerprintjs2",
|
||||
"version": "1.1.4",
|
||||
"description": "Modern & flexible browser fingerprinting library",
|
||||
"main": "dist/fingerprint2.min.js",
|
||||
"devDependencies": {
|
||||
"gulp": "^3.8.11",
|
||||
"eslint": "^0.14.1",
|
||||
"gulp-eslint": "^0.4.2",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-uglify": "^1.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "specs/phantomjs.runner.sh specs/spec_runner.html"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Valve/fingerprintjs2.git"
|
||||
},
|
||||
"keywords": [
|
||||
"browser",
|
||||
"identification",
|
||||
"fingerprint",
|
||||
"fingerprinting",
|
||||
"privacy"
|
||||
],
|
||||
"author": "Valentin Vasilyev",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Valve/fingerprintjs2/issues"
|
||||
},
|
||||
"homepage": "https://github.com/Valve/fingerprintjs2"
|
||||
}
|
121
dashboard-ui/bower_components/fingerprintjs2/specs/lib/jasmine-2.3.4/boot.js
vendored
Normal file
121
dashboard-ui/bower_components/fingerprintjs2/specs/lib/jasmine-2.3.4/boot.js
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
/**
|
||||
Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project.
|
||||
|
||||
If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms.
|
||||
|
||||
The location of `boot.js` can be specified and/or overridden in `jasmine.yml`.
|
||||
|
||||
[jasmine-gem]: http://github.com/pivotal/jasmine-gem
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
/**
|
||||
* ## Require & Instantiate
|
||||
*
|
||||
* Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
|
||||
*/
|
||||
window.jasmine = jasmineRequire.core(jasmineRequire);
|
||||
|
||||
/**
|
||||
* Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
|
||||
*/
|
||||
jasmineRequire.html(jasmine);
|
||||
|
||||
/**
|
||||
* Create the Jasmine environment. This is used to run all specs in a project.
|
||||
*/
|
||||
var env = jasmine.getEnv();
|
||||
|
||||
/**
|
||||
* ## The Global Interface
|
||||
*
|
||||
* Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
|
||||
*/
|
||||
var jasmineInterface = jasmineRequire.interface(jasmine, env);
|
||||
|
||||
/**
|
||||
* Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
|
||||
*/
|
||||
extend(window, jasmineInterface);
|
||||
|
||||
/**
|
||||
* ## Runner Parameters
|
||||
*
|
||||
* More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface.
|
||||
*/
|
||||
|
||||
var queryString = new jasmine.QueryString({
|
||||
getWindowLocation: function() { return window.location; }
|
||||
});
|
||||
|
||||
var catchingExceptions = queryString.getParam("catch");
|
||||
env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions);
|
||||
|
||||
var throwingExpectationFailures = queryString.getParam("throwFailures");
|
||||
env.throwOnExpectationFailure(throwingExpectationFailures);
|
||||
|
||||
/**
|
||||
* ## Reporters
|
||||
* The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
|
||||
*/
|
||||
var htmlReporter = new jasmine.HtmlReporter({
|
||||
env: env,
|
||||
onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); },
|
||||
onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); },
|
||||
addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); },
|
||||
getContainer: function() { return document.body; },
|
||||
createElement: function() { return document.createElement.apply(document, arguments); },
|
||||
createTextNode: function() { return document.createTextNode.apply(document, arguments); },
|
||||
timer: new jasmine.Timer()
|
||||
});
|
||||
|
||||
/**
|
||||
* The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
|
||||
*/
|
||||
env.addReporter(jasmineInterface.jsApiReporter);
|
||||
env.addReporter(htmlReporter);
|
||||
|
||||
/**
|
||||
* Filter which specs will be run by matching the start of the full name against the `spec` query param.
|
||||
*/
|
||||
var specFilter = new jasmine.HtmlSpecFilter({
|
||||
filterString: function() { return queryString.getParam("spec"); }
|
||||
});
|
||||
|
||||
env.specFilter = function(spec) {
|
||||
return specFilter.matches(spec.getFullName());
|
||||
};
|
||||
|
||||
/**
|
||||
* Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack.
|
||||
*/
|
||||
window.setTimeout = window.setTimeout;
|
||||
window.setInterval = window.setInterval;
|
||||
window.clearTimeout = window.clearTimeout;
|
||||
window.clearInterval = window.clearInterval;
|
||||
|
||||
/**
|
||||
* ## Execution
|
||||
*
|
||||
* Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
|
||||
*/
|
||||
var currentWindowOnload = window.onload;
|
||||
|
||||
window.onload = function() {
|
||||
if (currentWindowOnload) {
|
||||
currentWindowOnload();
|
||||
}
|
||||
htmlReporter.initialize();
|
||||
env.execute();
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function for readability above.
|
||||
*/
|
||||
function extend(destination, source) {
|
||||
for (var property in source) destination[property] = source[property];
|
||||
return destination;
|
||||
}
|
||||
|
||||
}());
|
446
dashboard-ui/bower_components/fingerprintjs2/specs/lib/jasmine-2.3.4/jasmine-html.js
vendored
Normal file
446
dashboard-ui/bower_components/fingerprintjs2/specs/lib/jasmine-2.3.4/jasmine-html.js
vendored
Normal file
@ -0,0 +1,446 @@
|
||||
/*
|
||||
Copyright (c) 2008-2015 Pivotal Labs
|
||||
|
||||
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.
|
||||
*/
|
||||
jasmineRequire.html = function(j$) {
|
||||
j$.ResultsNode = jasmineRequire.ResultsNode();
|
||||
j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
|
||||
j$.QueryString = jasmineRequire.QueryString();
|
||||
j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
|
||||
};
|
||||
|
||||
jasmineRequire.HtmlReporter = function(j$) {
|
||||
|
||||
var noopTimer = {
|
||||
start: function() {},
|
||||
elapsed: function() { return 0; }
|
||||
};
|
||||
|
||||
function HtmlReporter(options) {
|
||||
var env = options.env || {},
|
||||
getContainer = options.getContainer,
|
||||
createElement = options.createElement,
|
||||
createTextNode = options.createTextNode,
|
||||
onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {},
|
||||
onThrowExpectationsClick = options.onThrowExpectationsClick || function() {},
|
||||
addToExistingQueryString = options.addToExistingQueryString || defaultQueryString,
|
||||
timer = options.timer || noopTimer,
|
||||
results = [],
|
||||
specsExecuted = 0,
|
||||
failureCount = 0,
|
||||
pendingSpecCount = 0,
|
||||
htmlReporterMain,
|
||||
symbols,
|
||||
failedSuites = [];
|
||||
|
||||
this.initialize = function() {
|
||||
clearPrior();
|
||||
htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'},
|
||||
createDom('div', {className: 'banner'},
|
||||
createDom('a', {className: 'title', href: 'http://jasmine.github.io/', target: '_blank'}),
|
||||
createDom('span', {className: 'version'}, j$.version)
|
||||
),
|
||||
createDom('ul', {className: 'symbol-summary'}),
|
||||
createDom('div', {className: 'alert'}),
|
||||
createDom('div', {className: 'results'},
|
||||
createDom('div', {className: 'failures'})
|
||||
)
|
||||
);
|
||||
getContainer().appendChild(htmlReporterMain);
|
||||
|
||||
symbols = find('.symbol-summary');
|
||||
};
|
||||
|
||||
var totalSpecsDefined;
|
||||
this.jasmineStarted = function(options) {
|
||||
totalSpecsDefined = options.totalSpecsDefined || 0;
|
||||
timer.start();
|
||||
};
|
||||
|
||||
var summary = createDom('div', {className: 'summary'});
|
||||
|
||||
var topResults = new j$.ResultsNode({}, '', null),
|
||||
currentParent = topResults;
|
||||
|
||||
this.suiteStarted = function(result) {
|
||||
currentParent.addChild(result, 'suite');
|
||||
currentParent = currentParent.last();
|
||||
};
|
||||
|
||||
this.suiteDone = function(result) {
|
||||
if (result.status == 'failed') {
|
||||
failedSuites.push(result);
|
||||
}
|
||||
|
||||
if (currentParent == topResults) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentParent = currentParent.parent;
|
||||
};
|
||||
|
||||
this.specStarted = function(result) {
|
||||
currentParent.addChild(result, 'spec');
|
||||
};
|
||||
|
||||
var failures = [];
|
||||
this.specDone = function(result) {
|
||||
if(noExpectations(result) && typeof console !== 'undefined' && typeof console.error !== 'undefined') {
|
||||
console.error('Spec \'' + result.fullName + '\' has no expectations.');
|
||||
}
|
||||
|
||||
if (result.status != 'disabled') {
|
||||
specsExecuted++;
|
||||
}
|
||||
|
||||
symbols.appendChild(createDom('li', {
|
||||
className: noExpectations(result) ? 'empty' : result.status,
|
||||
id: 'spec_' + result.id,
|
||||
title: result.fullName
|
||||
}
|
||||
));
|
||||
|
||||
if (result.status == 'failed') {
|
||||
failureCount++;
|
||||
|
||||
var failure =
|
||||
createDom('div', {className: 'spec-detail failed'},
|
||||
createDom('div', {className: 'description'},
|
||||
createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName)
|
||||
),
|
||||
createDom('div', {className: 'messages'})
|
||||
);
|
||||
var messages = failure.childNodes[1];
|
||||
|
||||
for (var i = 0; i < result.failedExpectations.length; i++) {
|
||||
var expectation = result.failedExpectations[i];
|
||||
messages.appendChild(createDom('div', {className: 'result-message'}, expectation.message));
|
||||
messages.appendChild(createDom('div', {className: 'stack-trace'}, expectation.stack));
|
||||
}
|
||||
|
||||
failures.push(failure);
|
||||
}
|
||||
|
||||
if (result.status == 'pending') {
|
||||
pendingSpecCount++;
|
||||
}
|
||||
};
|
||||
|
||||
this.jasmineDone = function() {
|
||||
var banner = find('.banner');
|
||||
var alert = find('.alert');
|
||||
alert.appendChild(createDom('span', {className: 'duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
|
||||
|
||||
banner.appendChild(
|
||||
createDom('div', { className: 'run-options' },
|
||||
createDom('span', { className: 'trigger' }, 'Options'),
|
||||
createDom('div', { className: 'payload' },
|
||||
createDom('div', { className: 'exceptions' },
|
||||
createDom('input', {
|
||||
className: 'raise',
|
||||
id: 'raise-exceptions',
|
||||
type: 'checkbox'
|
||||
}),
|
||||
createDom('label', { className: 'label', 'for': 'raise-exceptions' }, 'raise exceptions')),
|
||||
createDom('div', { className: 'throw-failures' },
|
||||
createDom('input', {
|
||||
className: 'throw',
|
||||
id: 'throw-failures',
|
||||
type: 'checkbox'
|
||||
}),
|
||||
createDom('label', { className: 'label', 'for': 'throw-failures' }, 'stop spec on expectation failure'))
|
||||
)
|
||||
));
|
||||
|
||||
var raiseCheckbox = find('#raise-exceptions');
|
||||
|
||||
raiseCheckbox.checked = !env.catchingExceptions();
|
||||
raiseCheckbox.onclick = onRaiseExceptionsClick;
|
||||
|
||||
var throwCheckbox = find('#throw-failures');
|
||||
throwCheckbox.checked = env.throwingExpectationFailures();
|
||||
throwCheckbox.onclick = onThrowExpectationsClick;
|
||||
|
||||
var optionsMenu = find('.run-options'),
|
||||
optionsTrigger = optionsMenu.querySelector('.trigger'),
|
||||
optionsPayload = optionsMenu.querySelector('.payload'),
|
||||
isOpen = /\bopen\b/;
|
||||
|
||||
optionsTrigger.onclick = function() {
|
||||
if (isOpen.test(optionsPayload.className)) {
|
||||
optionsPayload.className = optionsPayload.className.replace(isOpen, '');
|
||||
} else {
|
||||
optionsPayload.className += ' open';
|
||||
}
|
||||
};
|
||||
|
||||
if (specsExecuted < totalSpecsDefined) {
|
||||
var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all';
|
||||
alert.appendChild(
|
||||
createDom('span', {className: 'bar skipped'},
|
||||
createDom('a', {href: '?', title: 'Run all specs'}, skippedMessage)
|
||||
)
|
||||
);
|
||||
}
|
||||
var statusBarMessage = '';
|
||||
var statusBarClassName = 'bar ';
|
||||
|
||||
if (totalSpecsDefined > 0) {
|
||||
statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount);
|
||||
if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); }
|
||||
statusBarClassName += (failureCount > 0) ? 'failed' : 'passed';
|
||||
} else {
|
||||
statusBarClassName += 'skipped';
|
||||
statusBarMessage += 'No specs found';
|
||||
}
|
||||
|
||||
alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage));
|
||||
|
||||
for(i = 0; i < failedSuites.length; i++) {
|
||||
var failedSuite = failedSuites[i];
|
||||
for(var j = 0; j < failedSuite.failedExpectations.length; j++) {
|
||||
var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message;
|
||||
var errorBarClassName = 'bar errored';
|
||||
alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage));
|
||||
}
|
||||
}
|
||||
|
||||
var results = find('.results');
|
||||
results.appendChild(summary);
|
||||
|
||||
summaryList(topResults, summary);
|
||||
|
||||
function summaryList(resultsTree, domParent) {
|
||||
var specListNode;
|
||||
for (var i = 0; i < resultsTree.children.length; i++) {
|
||||
var resultNode = resultsTree.children[i];
|
||||
if (resultNode.type == 'suite') {
|
||||
var suiteListNode = createDom('ul', {className: 'suite', id: 'suite-' + resultNode.result.id},
|
||||
createDom('li', {className: 'suite-detail'},
|
||||
createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description)
|
||||
)
|
||||
);
|
||||
|
||||
summaryList(resultNode, suiteListNode);
|
||||
domParent.appendChild(suiteListNode);
|
||||
}
|
||||
if (resultNode.type == 'spec') {
|
||||
if (domParent.getAttribute('class') != 'specs') {
|
||||
specListNode = createDom('ul', {className: 'specs'});
|
||||
domParent.appendChild(specListNode);
|
||||
}
|
||||
var specDescription = resultNode.result.description;
|
||||
if(noExpectations(resultNode.result)) {
|
||||
specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
|
||||
}
|
||||
if(resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') {
|
||||
specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
|
||||
}
|
||||
specListNode.appendChild(
|
||||
createDom('li', {
|
||||
className: resultNode.result.status,
|
||||
id: 'spec-' + resultNode.result.id
|
||||
},
|
||||
createDom('a', {href: specHref(resultNode.result)}, specDescription)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.length) {
|
||||
alert.appendChild(
|
||||
createDom('span', {className: 'menu bar spec-list'},
|
||||
createDom('span', {}, 'Spec List | '),
|
||||
createDom('a', {className: 'failures-menu', href: '#'}, 'Failures')));
|
||||
alert.appendChild(
|
||||
createDom('span', {className: 'menu bar failure-list'},
|
||||
createDom('a', {className: 'spec-list-menu', href: '#'}, 'Spec List'),
|
||||
createDom('span', {}, ' | Failures ')));
|
||||
|
||||
find('.failures-menu').onclick = function() {
|
||||
setMenuModeTo('failure-list');
|
||||
};
|
||||
find('.spec-list-menu').onclick = function() {
|
||||
setMenuModeTo('spec-list');
|
||||
};
|
||||
|
||||
setMenuModeTo('failure-list');
|
||||
|
||||
var failureNode = find('.failures');
|
||||
for (var i = 0; i < failures.length; i++) {
|
||||
failureNode.appendChild(failures[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return this;
|
||||
|
||||
function find(selector) {
|
||||
return getContainer().querySelector('.jasmine_html-reporter ' + selector);
|
||||
}
|
||||
|
||||
function clearPrior() {
|
||||
// return the reporter
|
||||
var oldReporter = find('');
|
||||
|
||||
if(oldReporter) {
|
||||
getContainer().removeChild(oldReporter);
|
||||
}
|
||||
}
|
||||
|
||||
function createDom(type, attrs, childrenVarArgs) {
|
||||
var el = createElement(type);
|
||||
|
||||
for (var i = 2; i < arguments.length; i++) {
|
||||
var child = arguments[i];
|
||||
|
||||
if (typeof child === 'string') {
|
||||
el.appendChild(createTextNode(child));
|
||||
} else {
|
||||
if (child) {
|
||||
el.appendChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var attr in attrs) {
|
||||
if (attr == 'className') {
|
||||
el[attr] = attrs[attr];
|
||||
} else {
|
||||
el.setAttribute(attr, attrs[attr]);
|
||||
}
|
||||
}
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
function pluralize(singular, count) {
|
||||
var word = (count == 1 ? singular : singular + 's');
|
||||
|
||||
return '' + count + ' ' + word;
|
||||
}
|
||||
|
||||
function specHref(result) {
|
||||
return addToExistingQueryString('spec', result.fullName);
|
||||
}
|
||||
|
||||
function defaultQueryString(key, value) {
|
||||
return '?' + key + '=' + value;
|
||||
}
|
||||
|
||||
function setMenuModeTo(mode) {
|
||||
htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode);
|
||||
}
|
||||
|
||||
function noExpectations(result) {
|
||||
return (result.failedExpectations.length + result.passedExpectations.length) === 0 &&
|
||||
result.status === 'passed';
|
||||
}
|
||||
}
|
||||
|
||||
return HtmlReporter;
|
||||
};
|
||||
|
||||
jasmineRequire.HtmlSpecFilter = function() {
|
||||
function HtmlSpecFilter(options) {
|
||||
var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
||||
var filterPattern = new RegExp(filterString);
|
||||
|
||||
this.matches = function(specName) {
|
||||
return filterPattern.test(specName);
|
||||
};
|
||||
}
|
||||
|
||||
return HtmlSpecFilter;
|
||||
};
|
||||
|
||||
jasmineRequire.ResultsNode = function() {
|
||||
function ResultsNode(result, type, parent) {
|
||||
this.result = result;
|
||||
this.type = type;
|
||||
this.parent = parent;
|
||||
|
||||
this.children = [];
|
||||
|
||||
this.addChild = function(result, type) {
|
||||
this.children.push(new ResultsNode(result, type, this));
|
||||
};
|
||||
|
||||
this.last = function() {
|
||||
return this.children[this.children.length - 1];
|
||||
};
|
||||
}
|
||||
|
||||
return ResultsNode;
|
||||
};
|
||||
|
||||
jasmineRequire.QueryString = function() {
|
||||
function QueryString(options) {
|
||||
|
||||
this.navigateWithNewParam = function(key, value) {
|
||||
options.getWindowLocation().search = this.fullStringWithNewParam(key, value);
|
||||
};
|
||||
|
||||
this.fullStringWithNewParam = function(key, value) {
|
||||
var paramMap = queryStringToParamMap();
|
||||
paramMap[key] = value;
|
||||
return toQueryString(paramMap);
|
||||
};
|
||||
|
||||
this.getParam = function(key) {
|
||||
return queryStringToParamMap()[key];
|
||||
};
|
||||
|
||||
return this;
|
||||
|
||||
function toQueryString(paramMap) {
|
||||
var qStrPairs = [];
|
||||
for (var prop in paramMap) {
|
||||
qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop]));
|
||||
}
|
||||
return '?' + qStrPairs.join('&');
|
||||
}
|
||||
|
||||
function queryStringToParamMap() {
|
||||
var paramStr = options.getWindowLocation().search.substring(1),
|
||||
params = [],
|
||||
paramMap = {};
|
||||
|
||||
if (paramStr.length > 0) {
|
||||
params = paramStr.split('&');
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var p = params[i].split('=');
|
||||
var value = decodeURIComponent(p[1]);
|
||||
if (value === 'true' || value === 'false') {
|
||||
value = JSON.parse(value);
|
||||
}
|
||||
paramMap[decodeURIComponent(p[0])] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return paramMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return QueryString;
|
||||
};
|
1757
dashboard-ui/bower_components/fingerprintjs2/specs/lib/jasmine-2.3.4/jasmine-matchers.js
vendored
Normal file
1757
dashboard-ui/bower_components/fingerprintjs2/specs/lib/jasmine-2.3.4/jasmine-matchers.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
58
dashboard-ui/bower_components/fingerprintjs2/specs/lib/jasmine-2.3.4/jasmine.css
vendored
Normal file
58
dashboard-ui/bower_components/fingerprintjs2/specs/lib/jasmine-2.3.4/jasmine.css
vendored
Normal file
File diff suppressed because one or more lines are too long
3298
dashboard-ui/bower_components/fingerprintjs2/specs/lib/jasmine-2.3.4/jasmine.js
vendored
Normal file
3298
dashboard-ui/bower_components/fingerprintjs2/specs/lib/jasmine-2.3.4/jasmine.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
dashboard-ui/bower_components/fingerprintjs2/specs/lib/jasmine-2.3.4/jasmine_favicon.png
vendored
Normal file
BIN
dashboard-ui/bower_components/fingerprintjs2/specs/lib/jasmine-2.3.4/jasmine_favicon.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
261
dashboard-ui/bower_components/fingerprintjs2/specs/lib/jasmine-2.3.4/terminal.js
vendored
Normal file
261
dashboard-ui/bower_components/fingerprintjs2/specs/lib/jasmine-2.3.4/terminal.js
vendored
Normal file
@ -0,0 +1,261 @@
|
||||
(function(global) {
|
||||
var UNDEFINED,
|
||||
exportObject;
|
||||
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
exportObject = exports;
|
||||
} else {
|
||||
exportObject = global.jasmineReporters = global.jasmineReporters || {};
|
||||
}
|
||||
|
||||
function elapsed(start, end) { return (end - start)/1000; }
|
||||
function isFailed(obj) { return obj.status === "failed"; }
|
||||
function isSkipped(obj) { return obj.status === "pending"; }
|
||||
function isDisabled(obj) { return obj.status === "disabled"; }
|
||||
function extend(dupe, obj) { // performs a shallow copy of all props of `obj` onto `dupe`
|
||||
for (var prop in obj) {
|
||||
if (obj.hasOwnProperty(prop)) {
|
||||
dupe[prop] = obj[prop];
|
||||
}
|
||||
}
|
||||
return dupe;
|
||||
}
|
||||
function log(str) {
|
||||
var con = global.console || console;
|
||||
if (con && con.log && str && str.length) {
|
||||
con.log(str);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Basic reporter that outputs spec results to the terminal.
|
||||
* Use this reporter in your build pipeline.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* jasmine.getEnv().addReporter(new jasmineReporters.TerminalReporter(options);
|
||||
*
|
||||
* @param {object} [options]
|
||||
* @param {number} [options.verbosity] meaningful values are 0 through 3; anything
|
||||
* greater than 3 is treated as 3 (default: 2)
|
||||
* @param {boolean} [options.color] print in color or not (default: true)
|
||||
* @param {boolean} [opts.showStack] show stack trace for failed specs (default: false)
|
||||
*/
|
||||
var DEFAULT_VERBOSITY = 2,
|
||||
ATTRIBUTES_TO_ANSI = {
|
||||
"off": 0,
|
||||
"bold": 1,
|
||||
"red": 31,
|
||||
"green": 32,
|
||||
"yellow": 33,
|
||||
"blue": 34,
|
||||
"magenta": 35,
|
||||
"cyan": 36
|
||||
};
|
||||
|
||||
exportObject.TerminalReporter = function(options) {
|
||||
var self = this;
|
||||
self.started = false;
|
||||
self.finished = false;
|
||||
|
||||
// sanitize arguments
|
||||
options = options || {};
|
||||
self.verbosity = typeof options.verbosity === "number" ? options.verbosity : DEFAULT_VERBOSITY;
|
||||
self.color = options.color;
|
||||
self.showStack = options.showStack;
|
||||
|
||||
var indent_string = ' ',
|
||||
startTime,
|
||||
currentSuite = null,
|
||||
totalSpecsExecuted = 0,
|
||||
totalSpecsSkipped = 0,
|
||||
totalSpecsDisabled = 0,
|
||||
totalSpecsFailed = 0,
|
||||
totalSpecsDefined,
|
||||
// when use use fit, jasmine never calls suiteStarted / suiteDone, so make a fake one to use
|
||||
fakeFocusedSuite = {
|
||||
id: 'focused',
|
||||
description: 'focused specs',
|
||||
fullName: 'focused specs'
|
||||
};
|
||||
|
||||
var __suites = {}, __specs = {};
|
||||
function getSuite(suite) {
|
||||
__suites[suite.id] = extend(__suites[suite.id] || {}, suite);
|
||||
return __suites[suite.id];
|
||||
}
|
||||
function getSpec(spec) {
|
||||
__specs[spec.id] = extend(__specs[spec.id] || {}, spec);
|
||||
return __specs[spec.id];
|
||||
}
|
||||
|
||||
self.jasmineStarted = function(summary) {
|
||||
totalSpecsDefined = summary && summary.totalSpecsDefined || NaN;
|
||||
startTime = exportObject.startTime = new Date();
|
||||
self.started = true;
|
||||
};
|
||||
self.suiteStarted = function(suite) {
|
||||
suite = getSuite(suite);
|
||||
suite._specs = 0;
|
||||
suite._nestedSpecs = 0;
|
||||
suite._failures = 0;
|
||||
suite._nestedFailures = 0;
|
||||
suite._skipped = 0;
|
||||
suite._nestedSkipped = 0;
|
||||
suite._disabled = 0;
|
||||
suite._nestedDisabled = 0;
|
||||
suite._depth = currentSuite ? currentSuite._depth+1 : 1;
|
||||
suite._parent = currentSuite;
|
||||
currentSuite = suite;
|
||||
if (self.verbosity > 2) {
|
||||
log(indentWithLevel(suite._depth, inColor(suite.description, "bold")));
|
||||
}
|
||||
};
|
||||
self.specStarted = function(spec) {
|
||||
if (!currentSuite) {
|
||||
// focused spec (fit) -- suiteStarted was never called
|
||||
self.suiteStarted(fakeFocusedSuite);
|
||||
}
|
||||
spec = getSpec(spec);
|
||||
spec._suite = currentSuite;
|
||||
spec._depth = currentSuite._depth+1;
|
||||
currentSuite._specs++;
|
||||
if (self.verbosity > 2) {
|
||||
log(indentWithLevel(spec._depth, spec.description + ' ...'));
|
||||
}
|
||||
};
|
||||
self.specDone = function(spec) {
|
||||
spec = getSpec(spec);
|
||||
var failed = false,
|
||||
skipped = false,
|
||||
disabled = false,
|
||||
color = 'green',
|
||||
resultText = '';
|
||||
if (isSkipped(spec)) {
|
||||
skipped = true;
|
||||
color = '';
|
||||
spec._suite._skipped++;
|
||||
totalSpecsSkipped++;
|
||||
}
|
||||
if (isFailed(spec)) {
|
||||
failed = true;
|
||||
color = 'red';
|
||||
spec._suite._failures++;
|
||||
totalSpecsFailed++;
|
||||
}
|
||||
if (isDisabled(spec)) {
|
||||
disabled = true;
|
||||
color = 'yellow';
|
||||
spec._suite._disabled++;
|
||||
totalSpecsDisabled++;
|
||||
}
|
||||
totalSpecsExecuted++;
|
||||
|
||||
if (self.verbosity === 2) {
|
||||
resultText = failed ? 'F' : skipped ? 'S' : disabled ? 'D' : '.';
|
||||
} else if (self.verbosity > 2) {
|
||||
resultText = ' ' + (failed ? 'Failed' : skipped ? 'Skipped' : disabled ? 'Disabled' : 'Passed');
|
||||
}
|
||||
log(inColor(resultText, color));
|
||||
|
||||
if (failed) {
|
||||
if (self.verbosity === 1) {
|
||||
log(spec.fullName);
|
||||
} else if (self.verbosity === 2) {
|
||||
log(' ');
|
||||
log(indentWithLevel(spec._depth, spec.fullName));
|
||||
}
|
||||
|
||||
for (var i = 0; i < spec.failedExpectations.length; i++) {
|
||||
log(inColor(indentWithLevel(spec._depth, indent_string + spec.failedExpectations[i].message), color));
|
||||
if (self.showStack){
|
||||
logStackLines(spec._depth, spec.failedExpectations[i].stack.split('\n'));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
self.suiteDone = function(suite) {
|
||||
suite = getSuite(suite);
|
||||
if (suite._parent === UNDEFINED) {
|
||||
// disabled suite (xdescribe) -- suiteStarted was never called
|
||||
self.suiteStarted(suite);
|
||||
}
|
||||
if (suite._parent) {
|
||||
suite._parent._specs += suite._specs + suite._nestedSpecs;
|
||||
suite._parent._failures += suite._failures + suite._nestedFailures;
|
||||
suite._parent._skipped += suite._skipped + suite._nestedSkipped;
|
||||
suite._parent._disabled += suite._disabled + suite._nestedDisabled;
|
||||
|
||||
}
|
||||
currentSuite = suite._parent;
|
||||
if (self.verbosity < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
var total = suite._specs + suite._nestedSpecs,
|
||||
failed = suite._failures + suite._nestedFailures,
|
||||
skipped = suite._skipped + suite._nestedSkipped,
|
||||
disabled = suite._disabled + suite._nestedDisabled,
|
||||
passed = total - failed - skipped,
|
||||
color = failed ? 'red+bold' : 'green+bold',
|
||||
str = passed + ' of ' + total + ' passed (' + skipped + ' skipped, ' + disabled + ' disabled)';
|
||||
log(indentWithLevel(suite._depth, inColor(str+'.', color)));
|
||||
};
|
||||
self.jasmineDone = function() {
|
||||
if (currentSuite) {
|
||||
// focused spec (fit) -- suiteDone was never called
|
||||
self.suiteDone(fakeFocusedSuite);
|
||||
}
|
||||
var now = new Date(),
|
||||
dur = elapsed(startTime, now),
|
||||
total = totalSpecsDefined || totalSpecsExecuted,
|
||||
disabled = total - totalSpecsExecuted + totalSpecsDisabled,
|
||||
skipped = totalSpecsSkipped,
|
||||
spec_str = total + (total === 1 ? " spec, " : " specs, "),
|
||||
fail_str = totalSpecsFailed + (totalSpecsFailed === 1 ? " failure, " : " failures, "),
|
||||
skip_str = skipped + " skipped, ",
|
||||
disabled_str = disabled + " disabled in ",
|
||||
summary_str = spec_str + fail_str + skip_str + disabled_str + dur + "s.",
|
||||
result_str = (totalSpecsFailed && "FAILURE: " || "SUCCESS: ") + summary_str,
|
||||
result_color = totalSpecsFailed && "red+bold" || "green+bold";
|
||||
|
||||
if (self.verbosity === 2) {
|
||||
log('');
|
||||
}
|
||||
|
||||
if (self.verbosity > 0) {
|
||||
log(inColor(result_str, result_color));
|
||||
}
|
||||
//log("Specs skipped but not reported (entire suite skipped or targeted to specific specs)", totalSpecsDefined - totalSpecsExecuted + totalSpecsDisabled);
|
||||
|
||||
self.finished = true;
|
||||
// this is so phantomjs-testrunner.js can tell if we're done executing
|
||||
exportObject.endTime = now;
|
||||
};
|
||||
function indentWithLevel(level, string) {
|
||||
return new Array(level).join(indent_string) + string;
|
||||
}
|
||||
function logStackLines(depth, lines) {
|
||||
lines.forEach(function(line){
|
||||
log(inColor(indentWithLevel(depth, indent_string + line), 'magenta'));
|
||||
});
|
||||
}
|
||||
function inColor(string, color) {
|
||||
var color_attributes = color && color.split("+"),
|
||||
ansi_string = "",
|
||||
i;
|
||||
|
||||
if (!self.color || !color_attributes) {
|
||||
return string;
|
||||
}
|
||||
|
||||
for(i = 0; i < color_attributes.length; i++) {
|
||||
ansi_string += "\033[" + ATTRIBUTES_TO_ANSI[color_attributes[i]] + "m";
|
||||
}
|
||||
ansi_string += string + "\033[" + ATTRIBUTES_TO_ANSI["off"] + "m";
|
||||
|
||||
return ansi_string;
|
||||
}
|
||||
};
|
||||
})(this);
|
230
dashboard-ui/bower_components/fingerprintjs2/specs/phantomjs-testrunner.js
vendored
Normal file
230
dashboard-ui/bower_components/fingerprintjs2/specs/phantomjs-testrunner.js
vendored
Normal file
@ -0,0 +1,230 @@
|
||||
/* globals jasmineRequire, phantom */
|
||||
// Verify arguments
|
||||
var system = require('system');
|
||||
var args;
|
||||
|
||||
if(phantom.args) {
|
||||
args = phantom.args;
|
||||
} else {
|
||||
args = system.args.slice(1);//use system args for phantom 2.0+
|
||||
}
|
||||
|
||||
if (args.length === 0) {
|
||||
console.log("Simple JasmineBDD test runner for phantom.js");
|
||||
console.log("Usage: phantomjs-testrunner.js url_to_runner.html");
|
||||
console.log("Accepts http:// and file:// urls");
|
||||
console.log("");
|
||||
console.log("NOTE: This script depends on jasmine.HtmlReporter being used\non the page, for the DOM elements it creates.\n");
|
||||
phantom.exit(2);
|
||||
}
|
||||
else {
|
||||
var fs = require("fs"),
|
||||
pages = [],
|
||||
page, address, resultsKey, i, l;
|
||||
|
||||
|
||||
var setupPageFn = function(p, k) {
|
||||
return function() {
|
||||
overloadPageEvaluate(p);
|
||||
setupWriteFileFunction(p, k, fs.separator);
|
||||
};
|
||||
};
|
||||
|
||||
for (i = 0, l = args.length; i < l; i++) {
|
||||
address = args[i];
|
||||
console.log("Loading " + address);
|
||||
|
||||
// if provided a url without a protocol, try to use file://
|
||||
address = address.indexOf("://") === -1 ? "file://" + address : address;
|
||||
|
||||
// create a WebPage object to work with
|
||||
page = require("webpage").create();
|
||||
page.url = address;
|
||||
|
||||
// When initialized, inject the reporting functions before the page is loaded
|
||||
// (and thus before it will try to utilize the functions)
|
||||
resultsKey = "__jr" + Math.ceil(Math.random() * 1000000);
|
||||
page.onInitialized = setupPageFn(page, resultsKey);
|
||||
page.open(address, processPage(null, page, resultsKey));
|
||||
pages.push(page);
|
||||
|
||||
page.onConsoleMessage = logAndWorkAroundDefaultLineBreaking;
|
||||
}
|
||||
|
||||
// bail when all pages have been processed
|
||||
setInterval(function(){
|
||||
var exit_code = 0;
|
||||
for (i = 0, l = pages.length; i < l; i++) {
|
||||
page = pages[i];
|
||||
if (page.__exit_code === null) {
|
||||
// wait until later
|
||||
return;
|
||||
}
|
||||
exit_code |= page.__exit_code;
|
||||
}
|
||||
phantom.exit(exit_code);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Thanks to hoisting, these helpers are still available when needed above
|
||||
/**
|
||||
* Logs a message. Does not add a line-break for single characters '.' and 'F' or lines ending in ' ...'
|
||||
*
|
||||
* @param msg
|
||||
*/
|
||||
function logAndWorkAroundDefaultLineBreaking(msg) {
|
||||
var interpretAsWithoutNewline = /(^(\033\[\d+m)*[\.F](\033\[\d+m)*$)|( \.\.\.$)/;
|
||||
if (navigator.userAgent.indexOf("Windows") < 0 && interpretAsWithoutNewline.test(msg)) {
|
||||
try {
|
||||
system.stdout.write(msg);
|
||||
} catch (e) {
|
||||
var fs = require('fs');
|
||||
fs.write('/dev/stdout', msg, 'w');
|
||||
}
|
||||
} else {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stringifies the function, replacing any %placeholders% with mapped values.
|
||||
*
|
||||
* @param {function} fn The function to replace occurrences within.
|
||||
* @param {object} replacements Key => Value object of string replacements.
|
||||
*/
|
||||
function replaceFunctionPlaceholders(fn, replacements) {
|
||||
if (replacements && typeof replacements === "object") {
|
||||
fn = fn.toString();
|
||||
for (var p in replacements) {
|
||||
if (replacements.hasOwnProperty(p)) {
|
||||
var match = new RegExp("%" + p + "%", "g");
|
||||
do {
|
||||
fn = fn.replace(match, replacements[p]);
|
||||
} while(fn.indexOf(match) !== -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the "evaluate" method with one we can easily do substitution with.
|
||||
*
|
||||
* @param {phantomjs.WebPage} page The WebPage object to overload
|
||||
*/
|
||||
function overloadPageEvaluate(page) {
|
||||
page._evaluate = page.evaluate;
|
||||
page.evaluate = function(fn, replacements) { return page._evaluate(replaceFunctionPlaceholders(fn, replacements)); };
|
||||
return page;
|
||||
}
|
||||
|
||||
/** Stubs a fake writeFile function into the test runner.
|
||||
*
|
||||
* @param {phantomjs.WebPage} page The WebPage object to inject functions into.
|
||||
* @param {string} key The name of the global object in which file data should
|
||||
* be stored for later retrieval.
|
||||
*/
|
||||
// TODO: not bothering with error checking for now (closed environment)
|
||||
function setupWriteFileFunction(page, key, path_separator) {
|
||||
page.evaluate(function(){
|
||||
window["%resultsObj%"] = {};
|
||||
window.fs_path_separator = "%fs_path_separator%";
|
||||
window.__phantom_writeFile = function(filename, text) {
|
||||
window["%resultsObj%"][filename] = text;
|
||||
};
|
||||
}, {resultsObj: key, fs_path_separator: path_separator.replace("\\", "\\\\")});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the loaded page's filename => output object.
|
||||
*
|
||||
* @param {phantomjs.WebPage} page The WebPage object to retrieve data from.
|
||||
* @param {string} key The name of the global object to be returned. Should
|
||||
* be the same key provided to setupWriteFileFunction.
|
||||
*/
|
||||
function getXmlResults(page, key) {
|
||||
return page.evaluate(function(){
|
||||
return window["%resultsObj%"] || {};
|
||||
}, {resultsObj: key});
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a page.
|
||||
*
|
||||
* @param {string} status The status from opening the page via WebPage#open.
|
||||
* @param {phantomjs.WebPage} page The WebPage to be processed.
|
||||
*/
|
||||
function processPage(status, page, resultsKey) {
|
||||
if (status === null && page) {
|
||||
page.__exit_code = null;
|
||||
return function(stat){
|
||||
processPage(stat, page, resultsKey);
|
||||
};
|
||||
}
|
||||
if (status !== "success") {
|
||||
console.error("Unable to load resource: " + address);
|
||||
page.__exit_code = 2;
|
||||
}
|
||||
else {
|
||||
var isFinished = function() {
|
||||
return page.evaluate(function(){
|
||||
// if there's a JUnitXmlReporter, return a boolean indicating if it is finished
|
||||
if (window.jasmineReporters && window.jasmineReporters.startTime) {
|
||||
return !!window.jasmineReporters.endTime;
|
||||
}
|
||||
// otherwise, scrape the DOM for the HtmlReporter "finished in ..." output
|
||||
var durElem = document.querySelector(".html-reporter .duration");
|
||||
if (!durElem) {
|
||||
durElem = document.querySelector(".jasmine_html-reporter .duration");
|
||||
}
|
||||
return durElem && durElem.textContent && durElem.textContent.toLowerCase().indexOf("finished in") === 0;
|
||||
});
|
||||
};
|
||||
var getResultsFromHtmlRunner = function() {
|
||||
return page.evaluate(function(){
|
||||
var resultElem = document.querySelector(".html-reporter .alert .bar");
|
||||
if (!resultElem) {
|
||||
resultElem = document.querySelector(".jasmine_html-reporter .alert .bar");
|
||||
}
|
||||
return resultElem && resultElem.textContent &&
|
||||
resultElem.textContent.match(/(\d+) spec.* (\d+) failure.*/) ||
|
||||
["Unable to determine success or failure."];
|
||||
});
|
||||
};
|
||||
var timeout = 60000;
|
||||
var loopInterval = 100;
|
||||
var ival = setInterval(function(){
|
||||
if (isFinished()) {
|
||||
// get the results that need to be written to disk
|
||||
var fs = require("fs"),
|
||||
xml_results = getXmlResults(page, resultsKey),
|
||||
output;
|
||||
for (var filename in xml_results) {
|
||||
if (xml_results.hasOwnProperty(filename) && (output = xml_results[filename]) && typeof(output) === "string") {
|
||||
fs.write(filename, output, "w");
|
||||
}
|
||||
}
|
||||
|
||||
// print out a success / failure message of the results
|
||||
var results = getResultsFromHtmlRunner();
|
||||
var failures = Number(results[2]);
|
||||
if (failures > 0) {
|
||||
page.__exit_code = 1;
|
||||
clearInterval(ival);
|
||||
}
|
||||
else {
|
||||
page.__exit_code = 0;
|
||||
clearInterval(ival);
|
||||
}
|
||||
}
|
||||
else {
|
||||
timeout -= loopInterval;
|
||||
if (timeout <= 0) {
|
||||
console.log('Page has timed out; aborting.');
|
||||
page.__exit_code = 2;
|
||||
clearInterval(ival);
|
||||
}
|
||||
}
|
||||
}, loopInterval);
|
||||
}
|
||||
}
|
40
dashboard-ui/bower_components/fingerprintjs2/specs/phantomjs.runner.sh
vendored
Normal file
40
dashboard-ui/bower_components/fingerprintjs2/specs/phantomjs.runner.sh
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
|
||||
# sanity check to make sure phantomjs exists in the PATH
|
||||
hash /usr/bin/env phantomjs &> /dev/null
|
||||
if [ $? -eq 1 ]; then
|
||||
echo "ERROR: phantomjs is not installed"
|
||||
echo "Please visit http://www.phantomjs.org/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# sanity check number of args
|
||||
if [ $# -lt 1 ]
|
||||
then
|
||||
echo "Usage: `basename $0` path_to_runner.html"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPTDIR=$(dirname `perl -e 'use Cwd "abs_path";print abs_path(shift)' $0`)
|
||||
TESTFILE=""
|
||||
while (( "$#" )); do
|
||||
if [ ${1:0:7} == "http://" -o ${1:0:8} == "https://" ]; then
|
||||
TESTFILE="$TESTFILE $1"
|
||||
else
|
||||
TESTFILE="$TESTFILE `perl -e 'use Cwd "abs_path";print abs_path(shift)' $1`"
|
||||
fi
|
||||
shift
|
||||
done
|
||||
|
||||
# cleanup previous test runs
|
||||
cd $SCRIPTDIR
|
||||
rm -f *.xml
|
||||
|
||||
# make sure phantomjs submodule is initialized
|
||||
cd ..
|
||||
git submodule update --init
|
||||
|
||||
# fire up the phantomjs environment and run the test
|
||||
cd $SCRIPTDIR
|
||||
/usr/bin/env phantomjs $SCRIPTDIR/phantomjs-testrunner.js $TESTFILE
|
35
dashboard-ui/bower_components/fingerprintjs2/specs/spec_runner.html
vendored
Normal file
35
dashboard-ui/bower_components/fingerprintjs2/specs/spec_runner.html
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Fingerprint2 spec runner</title>
|
||||
|
||||
<link rel="shortcut icon" type="image/png" href="lib/jasmine-2.3.4/jasmine_favicon.png">
|
||||
<link rel="stylesheet" href="lib/jasmine-2.3.4/jasmine.css">
|
||||
|
||||
<script src="lib/jasmine-2.3.4/jasmine.js"></script>
|
||||
<script src="lib/jasmine-2.3.4/jasmine-html.js"></script>
|
||||
<script src="lib/jasmine-2.3.4/boot.js"></script>
|
||||
<script src="lib/jasmine-2.3.4/terminal.js"></script>
|
||||
<script src="lib/jasmine-2.3.4/jasmine-matchers.js"></script>
|
||||
|
||||
<!-- include source files here... -->
|
||||
<script src="../fingerprint2.js"></script>
|
||||
|
||||
<!-- include spec files here... -->
|
||||
<script src="./specs.js"></script>
|
||||
|
||||
<script>
|
||||
if(navigator.userAgent.match(/phantom/i)) {
|
||||
jasmine.getEnv().addReporter(new jasmineReporters.TerminalReporter({
|
||||
verbosity: 3,
|
||||
color: true,
|
||||
showStack: true
|
||||
}));
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
180
dashboard-ui/bower_components/fingerprintjs2/specs/specs.js
vendored
Normal file
180
dashboard-ui/bower_components/fingerprintjs2/specs/specs.js
vendored
Normal file
@ -0,0 +1,180 @@
|
||||
"use strict";
|
||||
describe("Fingerprint2", function () {
|
||||
describe("new", function () {
|
||||
it("creates a new instance of FP2", function () {
|
||||
expect(new Fingerprint2()).not.toBeNull();
|
||||
});
|
||||
|
||||
it("accepts an empty options object", function () {
|
||||
expect(new Fingerprint2({})).not.toBeNull();
|
||||
});
|
||||
|
||||
it("uses default options", function () {
|
||||
var fp2 = new Fingerprint2();
|
||||
expect(fp2.options.swfContainerId).toEqual("fingerprintjs2");
|
||||
expect(fp2.options.swfPath).toEqual("flash/compiled/FontList.swf");
|
||||
});
|
||||
|
||||
it("allows to override default options", function () {
|
||||
var fp2 = new Fingerprint2({swfPath: "newpath"});
|
||||
expect(fp2.options.swfContainerId).toEqual("fingerprintjs2");
|
||||
expect(fp2.options.swfPath).toEqual("newpath");
|
||||
});
|
||||
|
||||
it("allows to add new options", function () {
|
||||
var fp2 = new Fingerprint2({excludeUserAgent: true});
|
||||
expect(fp2.options.swfContainerId).toEqual("fingerprintjs2");
|
||||
expect(fp2.options.swfPath).toEqual("flash/compiled/FontList.swf");
|
||||
expect(fp2.options.excludeUserAgent).toBe(true);
|
||||
});
|
||||
|
||||
describe("sortPluginsFor", function () {
|
||||
it("has default value", function (){
|
||||
var fp2 = new Fingerprint2();
|
||||
expect(fp2.options.sortPluginsFor).toEqual([/palemoon/i]);
|
||||
});
|
||||
|
||||
it("allows to set new array of regexes", function () {
|
||||
var fp2 = new Fingerprint2({sortPluginsFor: [/firefox/i, /chrome/i]});
|
||||
expect(fp2.options.sortPluginsFor).toEqual([/firefox/i, /chrome/i]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("get", function () {
|
||||
describe("default options", function () {
|
||||
it("calculates fingerprint", function (done) {
|
||||
var fp2 = new Fingerprint2();
|
||||
fp2.get(function(result){
|
||||
expect(result).toMatch(/^[0-9a-f]{32}$/i);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("does not try calling flash font detection", function (done) {
|
||||
var fp2 = new Fingerprint2();
|
||||
spyOn(fp2, "flashFontsKey");
|
||||
fp2.get(function(result) {
|
||||
expect(fp2.flashFontsKey).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("non-default options", function () {
|
||||
it("does not use userAgent when excluded", function (done) {
|
||||
var fp2 = new Fingerprint2({excludeUserAgent: true});
|
||||
spyOn(fp2, "getUserAgent");
|
||||
fp2.get(function(result) {
|
||||
expect(fp2.getUserAgent).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("does not use screen resolution when excluded", function (done) {
|
||||
var fp2 = new Fingerprint2({excludeScreenResolution: true});
|
||||
spyOn(fp2, "getScreenResolution");
|
||||
fp2.get(function(result) {
|
||||
expect(fp2.getScreenResolution).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("does not use available screen resolution when excluded", function (done) {
|
||||
var fp2 = new Fingerprint2({excludeAvailableScreenResolution: true});
|
||||
spyOn(fp2, "getAvailableScreenResolution");
|
||||
fp2.get(function(result) {
|
||||
expect(fp2.getAvailableScreenResolution).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("does not use plugins info when excluded", function (done) {
|
||||
var fp2 = new Fingerprint2({excludePlugins: true});
|
||||
spyOn(fp2, "getRegularPlugins");
|
||||
fp2.get(function(result) {
|
||||
expect(fp2.getRegularPlugins).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("does not use IE plugins info when excluded", function (done) {
|
||||
var fp2 = new Fingerprint2({excludeIEPlugins: true});
|
||||
spyOn(fp2, "getIEPlugins");
|
||||
fp2.get(function(result) {
|
||||
expect(fp2.getIEPlugins).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("returns components", function () {
|
||||
it("does it return components as a second argument to callback", function (done) {
|
||||
var fp2 = new Fingerprint2();
|
||||
fp2.get(function(result, components) {
|
||||
expect(components).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("checks if returned components is array", function (done) {
|
||||
var fp2 = new Fingerprint2();
|
||||
fp2.get(function(result, components) {
|
||||
expect(components).toBeArrayOfObjects();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("checks if js_fonts component is array", function (done) {
|
||||
var fp2 = new Fingerprint2();
|
||||
fp2.get(function(result, components) {
|
||||
for(var x = 0; x < components.length; x++) {
|
||||
if(components[x].key == "js_fonts") {
|
||||
expect(components[x].value).toBeArray();
|
||||
}
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("returns user_agent as the first element", function (done) {
|
||||
var fp2 = new Fingerprint2();
|
||||
fp2.get(function(result, components) {
|
||||
expect(components[0].key).toEqual("user_agent");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("baseFontArray iteration", function () {
|
||||
it("only iterates specified items", function (done) {
|
||||
var baseFonts = ["monospace", "sans-serif", "serif"];
|
||||
var ctr = 0;
|
||||
for (var x in baseFonts) {
|
||||
ctr++;
|
||||
}
|
||||
|
||||
expect(baseFonts.length).toEqual(3);
|
||||
expect(ctr).toEqual(baseFonts.length);
|
||||
|
||||
// Somewhere deep in your JavaScript library...
|
||||
Array.prototype.foo = 1;
|
||||
Array.prototype.bar = 2;
|
||||
ctr = 0;
|
||||
for (var x in baseFonts) {
|
||||
console.log(x);
|
||||
ctr++;
|
||||
// Now foo & bar is a part of EVERY array and
|
||||
// will show up here as a value of 'x'.
|
||||
}
|
||||
|
||||
expect(baseFonts.length).toEqual(3);
|
||||
// sadface
|
||||
expect(ctr).not.toEqual(baseFonts.length);
|
||||
expect(ctr).toEqual(5);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -14,14 +14,14 @@
|
||||
"package.json"
|
||||
],
|
||||
"homepage": "https://github.com/hammerjs/hammer.js",
|
||||
"version": "2.0.6",
|
||||
"_release": "2.0.6",
|
||||
"version": "2.0.8",
|
||||
"_release": "2.0.8",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v2.0.6",
|
||||
"commit": "05f0872c6130daf537bad27a393310cd5833c9a1"
|
||||
"tag": "v2.0.8",
|
||||
"commit": "ee611316bec077fcfbba3fd604ebc4b0b35ac288"
|
||||
},
|
||||
"_source": "git://github.com/hammerjs/hammer.js.git",
|
||||
"_source": "https://github.com/hammerjs/hammer.js.git",
|
||||
"_target": "~2.0.4",
|
||||
"_originalSource": "hammer.js"
|
||||
}
|
127
dashboard-ui/bower_components/hammerjs/hammer.js
vendored
127
dashboard-ui/bower_components/hammerjs/hammer.js
vendored
@ -1,8 +1,8 @@
|
||||
/*! Hammer.JS - v2.0.6 - 2015-12-23
|
||||
/*! Hammer.JS - v2.0.7 - 2016-04-22
|
||||
* http://hammerjs.github.io/
|
||||
*
|
||||
* Copyright (c) 2015 Jorik Tangelder;
|
||||
* Licensed under the license */
|
||||
* Copyright (c) 2016 Jorik Tangelder;
|
||||
* Licensed under the MIT license */
|
||||
(function(window, document, exportName, undefined) {
|
||||
'use strict';
|
||||
|
||||
@ -130,7 +130,7 @@ if (typeof Object.assign !== 'function') {
|
||||
* means that properties in dest will be overwritten by the ones in src.
|
||||
* @param {Object} dest
|
||||
* @param {Object} src
|
||||
* @param {Boolean=false} [merge]
|
||||
* @param {Boolean} [merge=false]
|
||||
* @returns {Object} dest
|
||||
*/
|
||||
var extend = deprecate(function extend(dest, src, merge) {
|
||||
@ -791,7 +791,6 @@ function MouseInput() {
|
||||
this.evEl = MOUSE_ELEMENT_EVENTS;
|
||||
this.evWin = MOUSE_WINDOW_EVENTS;
|
||||
|
||||
this.allow = true; // used by Input.TouchMouse to disable mouse events
|
||||
this.pressed = false; // mousedown state
|
||||
|
||||
Input.apply(this, arguments);
|
||||
@ -814,8 +813,8 @@ inherit(MouseInput, Input, {
|
||||
eventType = INPUT_END;
|
||||
}
|
||||
|
||||
// mouse must be down, and mouse events are allowed (see the TouchMouse input)
|
||||
if (!this.pressed || !this.allow) {
|
||||
// mouse must be down
|
||||
if (!this.pressed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1098,12 +1097,19 @@ function getTouches(ev, type) {
|
||||
* @constructor
|
||||
* @extends Input
|
||||
*/
|
||||
|
||||
var DEDUP_TIMEOUT = 2500;
|
||||
var DEDUP_DISTANCE = 25;
|
||||
|
||||
function TouchMouseInput() {
|
||||
Input.apply(this, arguments);
|
||||
|
||||
var handler = bindFn(this.handler, this);
|
||||
this.touch = new TouchInput(this.manager, handler);
|
||||
this.mouse = new MouseInput(this.manager, handler);
|
||||
|
||||
this.primaryTouch = null;
|
||||
this.lastTouches = [];
|
||||
}
|
||||
|
||||
inherit(TouchMouseInput, Input, {
|
||||
@ -1117,17 +1123,15 @@ inherit(TouchMouseInput, Input, {
|
||||
var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
|
||||
isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
|
||||
|
||||
// when we're in a touch event, so block all upcoming mouse events
|
||||
// most mobile browser also emit mouseevents, right after touchstart
|
||||
if (isTouch) {
|
||||
this.mouse.allow = false;
|
||||
} else if (isMouse && !this.mouse.allow) {
|
||||
if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
|
||||
return;
|
||||
}
|
||||
|
||||
// reset the allowMouse when we're done
|
||||
if (inputEvent & (INPUT_END | INPUT_CANCEL)) {
|
||||
this.mouse.allow = true;
|
||||
// when we're in a touch event, record touches to de-dupe synthetic mouse event
|
||||
if (isTouch) {
|
||||
recordTouches.call(this, inputEvent, inputData);
|
||||
} else if (isMouse && isSyntheticEvent.call(this, inputData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.callback(manager, inputEvent, inputData);
|
||||
@ -1142,6 +1146,44 @@ inherit(TouchMouseInput, Input, {
|
||||
}
|
||||
});
|
||||
|
||||
function recordTouches(eventType, eventData) {
|
||||
if (eventType & INPUT_START) {
|
||||
this.primaryTouch = eventData.changedPointers[0].identifier;
|
||||
setLastTouch.call(this, eventData);
|
||||
} else if (eventType & (INPUT_END | INPUT_CANCEL)) {
|
||||
setLastTouch.call(this, eventData);
|
||||
}
|
||||
}
|
||||
|
||||
function setLastTouch(eventData) {
|
||||
var touch = eventData.changedPointers[0];
|
||||
|
||||
if (touch.identifier === this.primaryTouch) {
|
||||
var lastTouch = {x: touch.clientX, y: touch.clientY};
|
||||
this.lastTouches.push(lastTouch);
|
||||
var lts = this.lastTouches;
|
||||
var removeLastTouch = function() {
|
||||
var i = lts.indexOf(lastTouch);
|
||||
if (i > -1) {
|
||||
lts.splice(i, 1);
|
||||
}
|
||||
};
|
||||
setTimeout(removeLastTouch, DEDUP_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
function isSyntheticEvent(eventData) {
|
||||
var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
|
||||
for (var i = 0; i < this.lastTouches.length; i++) {
|
||||
var t = this.lastTouches[i];
|
||||
var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
|
||||
if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
|
||||
var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
|
||||
|
||||
@ -1152,6 +1194,7 @@ var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
|
||||
var TOUCH_ACTION_NONE = 'none';
|
||||
var TOUCH_ACTION_PAN_X = 'pan-x';
|
||||
var TOUCH_ACTION_PAN_Y = 'pan-y';
|
||||
var TOUCH_ACTION_MAP = getTouchActionProps();
|
||||
|
||||
/**
|
||||
* Touch Action
|
||||
@ -1176,7 +1219,7 @@ TouchAction.prototype = {
|
||||
value = this.compute();
|
||||
}
|
||||
|
||||
if (NATIVE_TOUCH_ACTION && this.manager.element.style) {
|
||||
if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
|
||||
this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
|
||||
}
|
||||
this.actions = value.toLowerCase().trim();
|
||||
@ -1208,11 +1251,6 @@ TouchAction.prototype = {
|
||||
* @param {Object} input
|
||||
*/
|
||||
preventDefaults: function(input) {
|
||||
// not needed with native support for the touchAction property
|
||||
if (NATIVE_TOUCH_ACTION) {
|
||||
return;
|
||||
}
|
||||
|
||||
var srcEvent = input.srcEvent;
|
||||
var direction = input.offsetDirection;
|
||||
|
||||
@ -1223,9 +1261,9 @@ TouchAction.prototype = {
|
||||
}
|
||||
|
||||
var actions = this.actions;
|
||||
var hasNone = inStr(actions, TOUCH_ACTION_NONE);
|
||||
var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
|
||||
var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
|
||||
var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
|
||||
var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
|
||||
var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
|
||||
|
||||
if (hasNone) {
|
||||
//do not prevent defaults if this is a tap gesture
|
||||
@ -1296,6 +1334,21 @@ function cleanTouchActions(actions) {
|
||||
return TOUCH_ACTION_AUTO;
|
||||
}
|
||||
|
||||
function getTouchActionProps() {
|
||||
if (!NATIVE_TOUCH_ACTION) {
|
||||
return false;
|
||||
}
|
||||
var touchMap = {};
|
||||
var cssSupports = window.CSS && window.CSS.supports;
|
||||
['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
|
||||
|
||||
// If css.supports is not supported but there is native touch-action assume it supports
|
||||
// all values. This is the case for IE 10 and 11.
|
||||
touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
|
||||
});
|
||||
return touchMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recognizer flow explained; *
|
||||
* All recognizers have the initial state of POSSIBLE when a input session starts.
|
||||
@ -2092,7 +2145,7 @@ function Hammer(element, options) {
|
||||
/**
|
||||
* @const {string}
|
||||
*/
|
||||
Hammer.VERSION = '2.0.6';
|
||||
Hammer.VERSION = '2.0.7';
|
||||
|
||||
/**
|
||||
* default settings
|
||||
@ -2223,6 +2276,7 @@ function Manager(element, options) {
|
||||
this.handlers = {};
|
||||
this.session = {};
|
||||
this.recognizers = [];
|
||||
this.oldCssProps = {};
|
||||
|
||||
this.element = element;
|
||||
this.input = createInputInstance(this);
|
||||
@ -2401,6 +2455,13 @@ Manager.prototype = {
|
||||
* @returns {EventEmitter} this
|
||||
*/
|
||||
on: function(events, handler) {
|
||||
if (events === undefined) {
|
||||
return;
|
||||
}
|
||||
if (handler === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var handlers = this.handlers;
|
||||
each(splitStr(events), function(event) {
|
||||
handlers[event] = handlers[event] || [];
|
||||
@ -2416,6 +2477,10 @@ Manager.prototype = {
|
||||
* @returns {EventEmitter} this
|
||||
*/
|
||||
off: function(events, handler) {
|
||||
if (events === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var handlers = this.handlers;
|
||||
each(splitStr(events), function(event) {
|
||||
if (!handler) {
|
||||
@ -2480,9 +2545,19 @@ function toggleCssProps(manager, add) {
|
||||
if (!element.style) {
|
||||
return;
|
||||
}
|
||||
var prop;
|
||||
each(manager.options.cssProps, function(value, name) {
|
||||
element.style[prefixed(element.style, name)] = add ? value : '';
|
||||
prop = prefixed(element.style, name);
|
||||
if (add) {
|
||||
manager.oldCssProps[prop] = element.style[prop];
|
||||
element.style[prop] = value;
|
||||
} else {
|
||||
element.style[prop] = manager.oldCssProps[prop] || '';
|
||||
}
|
||||
});
|
||||
if (!add) {
|
||||
manager.oldCssProps = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
File diff suppressed because one or more lines are too long
1
dashboard-ui/bower_components/hammerjs/hammer.min.js.map
vendored
Normal file
1
dashboard-ui/bower_components/hammerjs/hammer.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hls.js",
|
||||
"version": "0.5.22",
|
||||
"version": "0.5.24",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Media Source Extension - HLS library, by/for Dailymotion",
|
||||
"homepage": "https://github.com/dailymotion/hls.js",
|
||||
@ -16,11 +16,11 @@
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"_release": "0.5.22",
|
||||
"_release": "0.5.24",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v0.5.22",
|
||||
"commit": "ed41f6bffac7c6e35963c8aa741e00be8edea2c8"
|
||||
"tag": "v0.5.24",
|
||||
"commit": "276d45148e6306e9ae06f4c9fa5778e723b78d05"
|
||||
},
|
||||
"_source": "git://github.com/dailymotion/hls.js.git",
|
||||
"_target": "~0.5.7",
|
||||
|
32
dashboard-ui/bower_components/hls.js/API.md
vendored
32
dashboard-ui/bower_components/hls.js/API.md
vendored
@ -86,26 +86,12 @@ each error is categorized by :
|
||||
- ```Hls.ErrorTypes.MEDIA_ERROR```for media/video related errors
|
||||
- ```Hls.ErrorTypes.OTHER_ERROR```for all other errors
|
||||
- its details:
|
||||
- ```Hls.ErrorDetails.MANIFEST_LOAD_ERROR```raised when manifest loading fails because of a network error
|
||||
- ```Hls.ErrorDetails.MANIFEST_LOAD_TIMEOUT```raised when manifest loading fails because of a timeout
|
||||
- ```Hls.ErrorDetails.MANIFEST_PARSING_ERROR```raised when manifest parsing failed to find proper content
|
||||
- ```Hls.ErrorDetails.LEVEL_LOAD_ERROR```raised when level loading fails because of a network error
|
||||
- ```Hls.ErrorDetails.LEVEL_LOAD_TIMEOUT```raised when level loading fails because of a timeout
|
||||
- ```Hls.ErrorDetails.LEVEL_SWITCH_ERROR```raised when level switching fails
|
||||
- ```Hls.ErrorDetails.FRAG_LOAD_ERROR```raised when fragment loading fails because of a network error
|
||||
- ```Hls.ErrorDetails.FRAG_LOOP_LOADING_ERROR```raised upon detection of same fragment being requested in loop
|
||||
- ```Hls.ErrorDetails.FRAG_LOAD_TIMEOUT```raised when fragment loading fails because of a timeout
|
||||
- ```Hls.ErrorDetails.FRAG_DECRYPT_ERROR```raised when fragment decryption fails
|
||||
- ```Hls.ErrorDetails.FRAG_PARSING_ERROR```raised when fragment parsing fails
|
||||
- ```Hls.ErrorDetails.BUFFER_ADD_CODEC_ERROR```raised when exception is raised while adding a new sourceBuffer to MediaSource
|
||||
- ```Hls.ErrorDetails.BUFFER_APPEND_ERROR```raised when exception is raised while preparing buffer append
|
||||
- ```Hls.ErrorDetails.BUFFER_APPENDING_ERROR```raised when exception is raised during buffer appending
|
||||
- ```Hls.ErrorDetails.BUFFER_STALLED_ERROR```raised when playback stalls because the buffer runs out
|
||||
- refer to [Errors details](#Errors)
|
||||
- its fatality:
|
||||
- ```false```if error is not fatal, hls.js will try to recover it
|
||||
- ```true```if error is fatal, an action is required to (try to) recover it.
|
||||
|
||||
full details is described [below](##Errors)
|
||||
full details is described [below](#Errors)
|
||||
|
||||
|
||||
see sample code below to listen to errors:
|
||||
@ -560,10 +546,6 @@ get/set : capping/max level value that could be used by ABR Controller
|
||||
default value is -1 (no level capping)
|
||||
|
||||
|
||||
## Version Control
|
||||
|
||||
#### ```Hls.version```
|
||||
static getter: return hls.js dist version number
|
||||
|
||||
## Network Loading Control API
|
||||
|
||||
@ -620,7 +602,7 @@ full list of Events available below :
|
||||
- `Hls.Events.FRAG_LOADING` - fired when a fragment loading starts
|
||||
- data: { frag : fragment object}
|
||||
- `Hls.Events.FRAG_LOAD_PROGRESS` - fired when a fragment load is in progress
|
||||
- data: { frag : fragment object with frag.loaded=stats.loaded, stats : { trequest, tfirst, loaded, total} }
|
||||
- data: { frag : fragment object with frag.loaded=stats.loaded, stats : { trequest, tfirst, loaded} }
|
||||
- `Hls.Events.FRAG_LOADED` - fired when a fragment loading is completed
|
||||
- data: { frag : fragment object, payload : fragment payload, stats : { trequest, tfirst, tload, length}}
|
||||
- `Hls.Events.FRAG_PARSING_INIT_SEGMENT` - fired when Init Segment has been extracted from fragment
|
||||
@ -637,8 +619,6 @@ full list of Events available below :
|
||||
- data: { frag : fragment object }
|
||||
- `Hls.Events.FPS_DROP` - triggered when FPS drop in last monitoring period is higher than given threshold
|
||||
- data: {curentDropped : nb of dropped frames in last monitoring period, currentDecoded: nb of decoded frames in last monitoring period, totalDropped : total dropped frames on this video element}
|
||||
- `Hls.Events.FPS_DROP_LEVEL_CAPPING` - triggered when FPS drop triggers auto level capping
|
||||
- data: { level: suggested new auto level capping by fps controller, droppedLevel : level has to much dropped frame will be restricted }
|
||||
- `Hls.Events.ERROR` - Identifier for an error event
|
||||
- data: { type : error Type, details : error details, fatal : is error fatal or not, other error specific data}
|
||||
- `Hls.Events.DESTROYING` - fired when hls.js instance starts destroying. Different from MEDIA_DETACHED as one could want to detach and reattach a video to the instance of hls.js to handle mid-rolls for example.
|
||||
@ -676,8 +656,6 @@ full list of Errors is described below:
|
||||
### Media Errors
|
||||
- ```Hls.ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR```raised when manifest only contains quality level with codecs incompatible with MediaSource Engine.
|
||||
- data: { type : ```MEDIA_ERROR```, details : ```Hls.ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR```, fatal : ```true```, url : manifest URL}
|
||||
- ```Hls.ErrorDetails.BUFFER_ADD_CODEC_ERROR```raised when MediaSource fails to add new sourceBuffer
|
||||
- data: { type : ```MEDIA_ERROR```, details : ```Hls.ErrorDetails.BUFFER_ADD_CODEC_ERROR```, fatal : ```false```, err : error raised by MediaSource, mimeType: mimeType on which the failure happened}
|
||||
- ```Hls.ErrorDetails.BUFFER_APPEND_ERROR```raised when exception is raised while calling buffer append
|
||||
- data: { type : ```MEDIA_ERROR```, details : ```Hls.ErrorDetails.BUFFER_APPEND_ERROR```, fatal : ```true```, frag : fragment object}
|
||||
- ```Hls.ErrorDetails.BUFFER_APPENDING_ERROR```raised when exception is raised during buffer appending
|
||||
@ -687,7 +665,9 @@ full list of Errors is described below:
|
||||
- ```Hls.ErrorDetails.BUFFER_FULL_ERROR```raised when no data can be appended anymore in media buffer because it is full. this error is recovered automatically by performing a smooth level switching that empty buffers (without disrupting the playback) and reducing the max buffer length.
|
||||
- data: { type : ```MEDIA_ERROR```, details : ```Hls.ErrorDetails.BUFFER_FULL_ERROR```, fatal : ```false```}
|
||||
- ```Hls.ErrorDetails.BUFFER_SEEK_OVER_HOLE```raised after hls.js seeks over a buffer hole to unstuck the playback,
|
||||
- data: { type : ```MEDIA_ERROR```, details : ```Hls.ErrorDetails.BUFFER_SEEK_OVER_HOLE```, fatal : ```false```}
|
||||
- data: { type : ```MEDIA_ERROR```, details : ```Hls.ErrorDetails.BUFFER_SEEK_OVER_HOLE```, fatal : ```false```, hole : hole duration}
|
||||
- ```Hls.ErrorDetails.BUFFER_SEEK_STUCK_IN_BUFFERED```raised after hls.js seeks to workaround a playback stuck although currentTime is buffered
|
||||
- data: { type : ```MEDIA_ERROR```, details : ```Hls.ErrorDetails.BUFFER_SEEK_STUCK_IN_BUFFERED```, fatal : ```false```}
|
||||
|
||||
## Objects
|
||||
### Level
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hls.js",
|
||||
"version": "0.6.1",
|
||||
"version": "0.5.24",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Media Source Extension - HLS library, by/for Dailymotion",
|
||||
"homepage": "https://github.com/dailymotion/hls.js",
|
||||
|
@ -93,7 +93,7 @@ header {
|
||||
|
||||
<video id="video" controls autoplay class="videoCentered"></video><br>
|
||||
<canvas id="buffered_c" height="15" class="videoCentered" onclick="buffered_seek(event);"></canvas><br><br>
|
||||
<pre id="HlsStatus" class="center" style="white-space: pre-wrap;"></pre>
|
||||
<pre id="HlsStatus" class="center"></pre>
|
||||
|
||||
<div class="center" id="toggleButtons">
|
||||
<button type="button" class="btn btn-sm" onclick="$('#PlaybackControl').toggle();">toggle playback controls</button>
|
||||
@ -477,9 +477,6 @@ $(document).ready(function() {
|
||||
case Hls.ErrorDetails.BUFFER_APPEND_ERROR:
|
||||
$("#HlsStatus").text("Buffer Append Error");
|
||||
break;
|
||||
case Hls.ErrorDetails.BUFFER_ADD_CODEC_ERROR:
|
||||
$("#HlsStatus").text("Buffer Add Codec Error for " + data.mimeType + ":" + data.err.message);
|
||||
break;
|
||||
case Hls.ErrorDetails.BUFFER_APPENDING_ERROR:
|
||||
$("#HlsStatus").text("Buffer Appending Error");
|
||||
break;
|
||||
@ -910,10 +907,21 @@ function timeRangesToString(r) {
|
||||
if(v.videoWidth) {
|
||||
$("#currentResolution").html("video resolution:" + v.videoWidth + 'x' + v.videoHeight);
|
||||
}
|
||||
$("#currentLevelControl").html(html1);
|
||||
$("#loadLevelControl").html(html2);
|
||||
$("#levelCappingControl").html(html3);
|
||||
$("#nextLevelControl").html(html4);
|
||||
if($("#currentLevelControl").html() != html1) {
|
||||
$("#currentLevelControl").html(html1);
|
||||
}
|
||||
|
||||
if($("#loadLevelControl").html() != html2) {
|
||||
$("#loadLevelControl").html(html2);
|
||||
}
|
||||
|
||||
if($("#levelCappingControl").html() != html3) {
|
||||
$("#levelCappingControl").html(html3);
|
||||
}
|
||||
|
||||
if($("#nextLevelControl").html() != html4) {
|
||||
$("#nextLevelControl").html(html4);
|
||||
}
|
||||
}
|
||||
|
||||
function level2label(index) {
|
||||
|
625
dashboard-ui/bower_components/hls.js/dist/hls.js
vendored
625
dashboard-ui/bower_components/hls.js/dist/hls.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
||||
#/bin/sh
|
||||
git checkout gh-pages
|
||||
git rebase master
|
||||
git rebase v0.5.x
|
||||
git push origin gh-pages --force
|
||||
git checkout master
|
||||
git checkout v0.5.x
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hls.js",
|
||||
"version": "0.6.1",
|
||||
"version": "0.5.24",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Media Source Extension - HLS library, by/for Dailymotion",
|
||||
"homepage": "https://github.com/dailymotion/hls.js",
|
||||
@ -17,7 +17,7 @@
|
||||
"scripts": {
|
||||
"clean": "find dist -mindepth 1 -delete",
|
||||
"prebuild": "npm run clean & npm run test",
|
||||
"build": "npm run babel && browserify -t browserify-versionify -t [babelify] -s Hls src/index.js --debug | exorcist dist/hls.js.map -b . > dist/hls.js",
|
||||
"build": "npm run babel && browserify -t [babelify] -s Hls src/index.js --debug | exorcist dist/hls.js.map -b . > dist/hls.js",
|
||||
"postbuild": "npm run minify",
|
||||
"prerelease": "npm run prebuild && npm run build && npm run postbuild && git add dist/* && git commit -m 'update dist'",
|
||||
"patch": "npm run prerelease && mversion p",
|
||||
@ -38,14 +38,13 @@
|
||||
"webworkify": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"arraybuffer-equal": "^1.0.4",
|
||||
"babel": "^6.3.26",
|
||||
"babel-cli": "^6.3.17",
|
||||
"babel-preset-es2015": "^6.3.13",
|
||||
"babel-register": "^6.3.13",
|
||||
"babelify": "^7.2.0",
|
||||
"arraybuffer-equal": "^1.0.4",
|
||||
"babel": "^6.3.26",
|
||||
"babel-cli": "^6.3.17",
|
||||
"browserify": "^13.0.0",
|
||||
"browserify-versionify": "^1.0.6",
|
||||
"deep-strict-equal": "^0.1.0",
|
||||
"exorcist": "^0.4.0",
|
||||
"http-server": "^0.9.0",
|
||||
|
@ -67,7 +67,7 @@ class BufferController extends EventHandler {
|
||||
this.mediaSource = null;
|
||||
this.media = null;
|
||||
this.pendingTracks = null;
|
||||
this.sourceBuffer = {};
|
||||
this.sourceBuffer = null;
|
||||
}
|
||||
this.onmso = this.onmse = this.onmsc = null;
|
||||
this.hls.trigger(Event.MEDIA_DETACHED);
|
||||
@ -122,16 +122,18 @@ class BufferController extends EventHandler {
|
||||
|
||||
onBufferReset() {
|
||||
var sourceBuffer = this.sourceBuffer;
|
||||
for(var type in sourceBuffer) {
|
||||
var sb = sourceBuffer[type];
|
||||
try {
|
||||
this.mediaSource.removeSourceBuffer(sb);
|
||||
sb.removeEventListener('updateend', this.onsbue);
|
||||
sb.removeEventListener('error', this.onsbe);
|
||||
} catch(err) {
|
||||
if (sourceBuffer) {
|
||||
for(var type in sourceBuffer) {
|
||||
var sb = sourceBuffer[type];
|
||||
try {
|
||||
this.mediaSource.removeSourceBuffer(sb);
|
||||
sb.removeEventListener('updateend', this.onsbue);
|
||||
sb.removeEventListener('error', this.onsbe);
|
||||
} catch(err) {
|
||||
}
|
||||
}
|
||||
this.sourceBuffer = null;
|
||||
}
|
||||
this.sourceBuffer = {};
|
||||
this.flushRange = [];
|
||||
this.appended = 0;
|
||||
}
|
||||
@ -144,24 +146,19 @@ class BufferController extends EventHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceBuffer = this.sourceBuffer,mediaSource = this.mediaSource;
|
||||
|
||||
for (trackName in tracks) {
|
||||
if(!sourceBuffer[trackName]) {
|
||||
if (!this.sourceBuffer) {
|
||||
var sourceBuffer = {}, mediaSource = this.mediaSource;
|
||||
for (trackName in tracks) {
|
||||
track = tracks[trackName];
|
||||
// use levelCodec as first priority
|
||||
codec = track.levelCodec || track.codec;
|
||||
mimeType = `${track.container};codecs=${codec}`;
|
||||
logger.log(`creating sourceBuffer with mimeType:${mimeType}`);
|
||||
try {
|
||||
sb = sourceBuffer[trackName] = mediaSource.addSourceBuffer(mimeType);
|
||||
sb.addEventListener('updateend', this.onsbue);
|
||||
sb.addEventListener('error', this.onsbe);
|
||||
} catch(err) {
|
||||
logger.error(`error while trying to add sourceBuffer:${err.message}`);
|
||||
this.hls.trigger(Event.ERROR, {type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_ADD_CODEC_ERROR, fatal: false, err: err, mimeType : mimeType});
|
||||
}
|
||||
sb = sourceBuffer[trackName] = mediaSource.addSourceBuffer(mimeType);
|
||||
sb.addEventListener('updateend', this.onsbue);
|
||||
sb.addEventListener('error', this.onsbe);
|
||||
}
|
||||
this.sourceBuffer = sourceBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,8 +223,10 @@ class BufferController extends EventHandler {
|
||||
// let's recompute this.appended, which is used to avoid flush looping
|
||||
var appended = 0;
|
||||
var sourceBuffer = this.sourceBuffer;
|
||||
for (var type in sourceBuffer) {
|
||||
appended += sourceBuffer[type].buffered.length;
|
||||
if (sourceBuffer) {
|
||||
for (var type in sourceBuffer) {
|
||||
appended += sourceBuffer[type].buffered.length;
|
||||
}
|
||||
}
|
||||
this.appended = appended;
|
||||
this.hls.trigger(Event.BUFFER_FLUSHED);
|
||||
@ -252,16 +251,9 @@ class BufferController extends EventHandler {
|
||||
var segment = segments.shift();
|
||||
try {
|
||||
//logger.log(`appending ${segment.type} SB, size:${segment.data.length});
|
||||
if(sourceBuffer[segment.type]) {
|
||||
sourceBuffer[segment.type].appendBuffer(segment.data);
|
||||
this.appendError = 0;
|
||||
this.appended++;
|
||||
} else {
|
||||
// in case we don't have any source buffer matching with this segment type,
|
||||
// it means that Mediasource fails to create sourcebuffer
|
||||
// discard this segment, and trigger update end
|
||||
this.onSBUpdateEnd();
|
||||
}
|
||||
sourceBuffer[segment.type].appendBuffer(segment.data);
|
||||
this.appendError = 0;
|
||||
this.appended++;
|
||||
} catch(err) {
|
||||
// in case any error occured while appending, put back segment in segments table
|
||||
logger.error(`error while trying to append buffer:${err.message}`);
|
||||
|
@ -8,14 +8,13 @@ import EventHandler from '../event-handler';
|
||||
class CapLevelController extends EventHandler {
|
||||
constructor(hls) {
|
||||
super(hls,
|
||||
Event.FPS_DROP_LEVEL_CAPPING,
|
||||
Event.MEDIA_ATTACHING,
|
||||
Event.MANIFEST_PARSED);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.hls.config.capLevelToPlayerSize) {
|
||||
this.media = this.restrictedLevels = null;
|
||||
this.media = null;
|
||||
this.autoLevelCapping = Number.POSITIVE_INFINITY;
|
||||
if (this.timer) {
|
||||
this.timer = clearInterval(this.timer);
|
||||
@ -23,15 +22,6 @@ class CapLevelController extends EventHandler {
|
||||
}
|
||||
}
|
||||
|
||||
onFpsDropLevelCapping(data) {
|
||||
if (!this.restrictedLevels) {
|
||||
this.restrictedLevels = [];
|
||||
}
|
||||
if (!this.isLevelRestricted(data.droppedLevel)) {
|
||||
this.restrictedLevels.push(data.droppedLevel);
|
||||
}
|
||||
}
|
||||
|
||||
onMediaAttaching(data) {
|
||||
this.media = data.media instanceof HTMLVideoElement ? data.media : null;
|
||||
}
|
||||
@ -66,7 +56,7 @@ class CapLevelController extends EventHandler {
|
||||
* returns level should be the one with the dimensions equal or greater than the media (player) dimensions (so the video will be downscaled)
|
||||
*/
|
||||
getMaxLevel(capLevelIndex) {
|
||||
let result = 0,
|
||||
let result,
|
||||
i,
|
||||
level,
|
||||
mWidth = this.mediaWidth,
|
||||
@ -76,9 +66,6 @@ class CapLevelController extends EventHandler {
|
||||
|
||||
for (i = 0; i <= capLevelIndex; i++) {
|
||||
level = this.levels[i];
|
||||
if (this.isLevelRestricted(i)) {
|
||||
break;
|
||||
}
|
||||
result = i;
|
||||
lWidth = level.width;
|
||||
lHeight = level.height;
|
||||
@ -89,10 +76,6 @@ class CapLevelController extends EventHandler {
|
||||
return result;
|
||||
}
|
||||
|
||||
isLevelRestricted(level) {
|
||||
return (this.restrictedLevels && this.restrictedLevels.indexOf(level) !== -1) ? true : false;
|
||||
}
|
||||
|
||||
get contentScaleFactor() {
|
||||
let pixelRatio = 1;
|
||||
try {
|
||||
|
@ -3,69 +3,43 @@
|
||||
*/
|
||||
|
||||
import Event from '../events';
|
||||
import EventHandler from '../event-handler';
|
||||
import {logger} from '../utils/logger';
|
||||
|
||||
class FPSController extends EventHandler{
|
||||
class FPSController {
|
||||
|
||||
constructor(hls) {
|
||||
super(hls, Event.MEDIA_ATTACHING);
|
||||
this.hls = hls;
|
||||
this.timer = setInterval(this.checkFPS, hls.config.fpsDroppedMonitoringPeriod);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
this.isVideoPlaybackQualityAvailable = false;
|
||||
}
|
||||
|
||||
onMediaAttaching(data) {
|
||||
if (this.hls.config.capLevelOnFPSDrop) {
|
||||
this.video = data.media instanceof HTMLVideoElement ? data.media : null;
|
||||
if (typeof this.video.getVideoPlaybackQuality === 'function') {
|
||||
this.isVideoPlaybackQualityAvailable = true;
|
||||
}
|
||||
clearInterval(this.timer);
|
||||
this.timer = setInterval(this.checkFPSInterval.bind(this), this.hls.config.fpsDroppedMonitoringPeriod);
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
}
|
||||
|
||||
checkFPS(video, decodedFrames, droppedFrames) {
|
||||
let currentTime = performance.now();
|
||||
if (decodedFrames) {
|
||||
if (this.lastTime) {
|
||||
let currentPeriod = currentTime - this.lastTime,
|
||||
currentDropped = droppedFrames - this.lastDroppedFrames,
|
||||
currentDecoded = decodedFrames - this.lastDecodedFrames,
|
||||
droppedFPS = 1000 * currentDropped / currentPeriod;
|
||||
this.hls.trigger(Event.FPS_DROP, {currentDropped: currentDropped, currentDecoded: currentDecoded, totalDroppedFrames: droppedFrames});
|
||||
if (droppedFPS > 0) {
|
||||
//logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
||||
if (currentDropped > this.hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
||||
let currentLevel = this.hls.currentLevel;
|
||||
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
||||
if (currentLevel > 0 && (this.hls.autoLevelCapping === -1 || this.hls.autoLevelCapping >= currentLevel)) {
|
||||
currentLevel = currentLevel - 1;
|
||||
this.hls.trigger(Event.FPS_DROP_LEVEL_CAPPING, {level: currentLevel, droppedLevel: this.hls.currentLevel});
|
||||
this.hls.autoLevelCapping = currentLevel;
|
||||
this.hls.streamController.nextLevelSwitch();
|
||||
checkFPS() {
|
||||
var v = this.hls.video;
|
||||
if (v) {
|
||||
var decodedFrames = v.webkitDecodedFrameCount, droppedFrames = v.webkitDroppedFrameCount, currentTime = new Date();
|
||||
if (decodedFrames) {
|
||||
if (this.lastTime) {
|
||||
var currentPeriod = currentTime - this.lastTime;
|
||||
var currentDropped = droppedFrames - this.lastDroppedFrames;
|
||||
var currentDecoded = decodedFrames - this.lastDecodedFrames;
|
||||
var decodedFPS = 1000 * currentDecoded / currentPeriod;
|
||||
var droppedFPS = 1000 * currentDropped / currentPeriod;
|
||||
if (droppedFPS > 0) {
|
||||
logger.log(`checkFPS : droppedFPS/decodedFPS:${droppedFPS.toFixed(1)}/${decodedFPS.toFixed(1)}`);
|
||||
if (currentDropped > this.hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
||||
logger.warn('drop FPS ratio greater than max allowed value');
|
||||
this.hls.trigger(Event.FPS_DROP, {currentDropped: currentDropped, currentDecoded: currentDecoded, totalDroppedFrames: droppedFrames});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.lastTime = currentTime;
|
||||
this.lastDroppedFrames = droppedFrames;
|
||||
this.lastDecodedFrames = decodedFrames;
|
||||
}
|
||||
}
|
||||
|
||||
checkFPSInterval() {
|
||||
if (this.video) {
|
||||
if (this.isVideoPlaybackQualityAvailable) {
|
||||
let videoPlaybackQuality = this.video.getVideoPlaybackQuality();
|
||||
this.checkFPS(this.video, videoPlaybackQuality.totalVideoFrames, videoPlaybackQuality.droppedVideoFrames);
|
||||
} else {
|
||||
this.checkFPS(this.video, this.video.webkitDecodedFrameCount, this.video.webkitDroppedFrameCount);
|
||||
this.lastTime = currentTime;
|
||||
this.lastDroppedFrames = droppedFrames;
|
||||
this.lastDecodedFrames = decodedFrames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +113,8 @@ class LevelController extends EventHandler {
|
||||
}
|
||||
|
||||
set level(newLevel) {
|
||||
if (this._level !== newLevel || this._levels[newLevel].details === undefined) {
|
||||
if (this._levels && this._levels.length > newLevel &&
|
||||
(this._level !== newLevel || this._levels[newLevel].details === undefined)) {
|
||||
this.setLevelInternal(newLevel);
|
||||
}
|
||||
}
|
||||
@ -234,15 +235,10 @@ class LevelController extends EventHandler {
|
||||
|
||||
onLevelLoaded(data) {
|
||||
// check if current playlist is a live playlist
|
||||
if (data.details.live) {
|
||||
if (data.details.live && !this.timer) {
|
||||
// if live playlist we will have to reload it periodically
|
||||
// set reload period to average of the frag duration, if average not set then use playlist target duration
|
||||
let timerInterval = data.details.averagetargetduration ? data.details.averagetargetduration : data.details.targetduration;
|
||||
if (!this.timer || timerInterval !== this.timerInterval) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = setInterval(this.ontick, 1000 * timerInterval);
|
||||
this.timerInterval = timerInterval;
|
||||
}
|
||||
// set reload period to playlist target duration
|
||||
this.timer = setInterval(this.ontick, 1000 * data.details.targetduration);
|
||||
}
|
||||
if (!data.details.live && this.timer) {
|
||||
// playlist is not live and timer is armed : stopping it
|
||||
|
@ -218,7 +218,7 @@ class StreamController extends EventHandler {
|
||||
// level 1 loaded [182580162,182580168] <============= here we should have bufferEnd > end. in that case break to avoid reloading 182580168
|
||||
// level 1 loaded [182580164,182580171]
|
||||
//
|
||||
if (bufferEnd > end) {
|
||||
if (levelDetails.PTSKnown && bufferEnd > end) {
|
||||
break;
|
||||
}
|
||||
|
||||
@ -486,13 +486,11 @@ class StreamController extends EventHandler {
|
||||
fragCurrent.loader.abort();
|
||||
}
|
||||
this.fragCurrent = null;
|
||||
// flush everything
|
||||
this.hls.trigger(Event.BUFFER_FLUSHING, {startOffset: 0, endOffset: Number.POSITIVE_INFINITY});
|
||||
this.state = State.PAUSED;
|
||||
// increase fragment load Index to avoid frag loop loading error after buffer flush
|
||||
this.fragLoadIdx += 2 * this.config.fragLoadingLoopThreshold;
|
||||
// speed up switching, trigger timer function
|
||||
this.tick();
|
||||
this.state = State.PAUSED;
|
||||
// flush everything
|
||||
this.hls.trigger(Event.BUFFER_FLUSHING, {startOffset: 0, endOffset: Number.POSITIVE_INFINITY});
|
||||
}
|
||||
|
||||
/*
|
||||
@ -515,12 +513,14 @@ class StreamController extends EventHandler {
|
||||
we should take into account new segment fetch time
|
||||
*/
|
||||
var fetchdelay, currentRange, nextRange;
|
||||
// increase fragment load Index to avoid frag loop loading error after buffer flush
|
||||
this.fragLoadIdx += 2 * this.config.fragLoadingLoopThreshold;
|
||||
currentRange = this.getBufferRange(this.media.currentTime);
|
||||
if (currentRange && currentRange.start > 1) {
|
||||
// flush buffer preceding current fragment (flush until current fragment start offset)
|
||||
// minus 1s to avoid video freezing, that could happen if we flush keyframe of current video ...
|
||||
this.hls.trigger(Event.BUFFER_FLUSHING, {startOffset: 0, endOffset: currentRange.start - 1});
|
||||
this.state = State.PAUSED;
|
||||
this.hls.trigger(Event.BUFFER_FLUSHING, {startOffset: 0, endOffset: currentRange.start - 1});
|
||||
}
|
||||
if (!this.media.paused) {
|
||||
// add a safety delay of 1s
|
||||
@ -540,17 +540,15 @@ class StreamController extends EventHandler {
|
||||
// we can flush buffer range following this one without stalling playback
|
||||
nextRange = this.followingBufferRange(nextRange);
|
||||
if (nextRange) {
|
||||
// flush position is the start position of this new buffer
|
||||
this.hls.trigger(Event.BUFFER_FLUSHING, {startOffset: nextRange.start, endOffset: Number.POSITIVE_INFINITY});
|
||||
this.state = State.PAUSED;
|
||||
// if we are here, we can also cancel any loading/demuxing in progress, as they are useless
|
||||
var fragCurrent = this.fragCurrent;
|
||||
if (fragCurrent && fragCurrent.loader) {
|
||||
fragCurrent.loader.abort();
|
||||
}
|
||||
this.fragCurrent = null;
|
||||
// increase fragment load Index to avoid frag loop loading error after buffer flush
|
||||
this.fragLoadIdx += 2 * this.config.fragLoadingLoopThreshold;
|
||||
// flush position is the start position of this new buffer
|
||||
this.state = State.PAUSED;
|
||||
this.hls.trigger(Event.BUFFER_FLUSHING, {startOffset: nextRange.start, endOffset: Number.POSITIVE_INFINITY});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1020,6 +1018,7 @@ _checkBuffer() {
|
||||
// check buffer upfront
|
||||
// if less than jumpThreshold second is buffered, and media is expected to play but playhead is not moving,
|
||||
// and we have a new buffer range available upfront, let's seek to that one
|
||||
let configSeekHoleNudgeDuration = this.config.seekHoleNudgeDuration;
|
||||
if(expectedPlaying && bufferInfo.len <= jumpThreshold) {
|
||||
if(playheadMoving) {
|
||||
// playhead moving
|
||||
@ -1033,7 +1032,7 @@ _checkBuffer() {
|
||||
this.hls.trigger(Event.ERROR, {type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_STALLED_ERROR, fatal: false});
|
||||
this.stalled = true;
|
||||
} else {
|
||||
this.seekHoleNudgeDuration += this.config.seekHoleNudgeDuration;
|
||||
this.seekHoleNudgeDuration += configSeekHoleNudgeDuration;
|
||||
}
|
||||
}
|
||||
// if we are below threshold, try to jump if next buffer range is close
|
||||
@ -1047,14 +1046,35 @@ _checkBuffer() {
|
||||
// next buffer is close ! adjust currentTime to nextBufferStart
|
||||
// this will ensure effective video decoding
|
||||
logger.log(`adjust currentTime from ${media.currentTime} to next buffered @ ${nextBufferStart} + nudge ${this.seekHoleNudgeDuration}`);
|
||||
let hole = nextBufferStart + this.seekHoleNudgeDuration - media.currentTime;
|
||||
media.currentTime = nextBufferStart + this.seekHoleNudgeDuration;
|
||||
this.hls.trigger(Event.ERROR, {type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_SEEK_OVER_HOLE, fatal: false});
|
||||
this.hls.trigger(Event.ERROR, {type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_SEEK_OVER_HOLE, fatal: false, hole : hole});
|
||||
}
|
||||
}
|
||||
// in any case reset stalledInBuffered
|
||||
this.stalledInBuffered = 0;
|
||||
} else {
|
||||
if (targetSeekPosition && media.currentTime !== targetSeekPosition) {
|
||||
logger.log(`adjust currentTime from ${media.currentTime} to ${targetSeekPosition}`);
|
||||
media.currentTime = targetSeekPosition;
|
||||
} else if (expectedPlaying && !playheadMoving) {
|
||||
// if we are in this condition, it means that currentTime is in a buffered area, but playhead is not moving
|
||||
// if that happens, we wait for a couple of cycle (config.stalledInBufferedNudgeThreshold), then we nudge
|
||||
// media.currentTime to try to recover that situation.
|
||||
if (this.stalledInBuffered !== undefined) {
|
||||
this.stalledInBuffered++;
|
||||
} else {
|
||||
this.stalledInBuffered = 1;
|
||||
}
|
||||
if (this.stalledInBuffered >= this.config.stalledInBufferedNudgeThreshold) {
|
||||
logger.log(`playback stuck @ ${media.currentTime}, in buffered area, nudge currentTime by ${configSeekHoleNudgeDuration}`);
|
||||
this.hls.trigger(Event.ERROR, {type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_SEEK_STUCK_IN_BUFFERED, fatal: false});
|
||||
media.currentTime+=configSeekHoleNudgeDuration;
|
||||
this.stalledInBuffered = 0;
|
||||
}
|
||||
} else {
|
||||
// currentTime is buffered, playhead is moving or playback not expected... everything is fine
|
||||
this.stalledInBuffered = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,8 +36,6 @@ export const ErrorDetails = {
|
||||
KEY_LOAD_ERROR: 'keyLoadError',
|
||||
// Identifier for decrypt key load timeout error - data: { frag : fragment object}
|
||||
KEY_LOAD_TIMEOUT: 'keyLoadTimeOut',
|
||||
// Triggered when an exception occurs while adding a sourceBuffer to MediaSource - data : { err : exception , mimeType : mimeType }
|
||||
BUFFER_ADD_CODEC_ERROR: 'bufferAddCodecError',
|
||||
// Identifier for a buffer append error - data: append error description
|
||||
BUFFER_APPEND_ERROR: 'bufferAppendError',
|
||||
// Identifier for a buffer appending error event - data: appending error description
|
||||
@ -48,6 +46,8 @@ export const ErrorDetails = {
|
||||
BUFFER_FULL_ERROR: 'bufferFullError',
|
||||
// Identifier for a buffer seek over hole event
|
||||
BUFFER_SEEK_OVER_HOLE: 'bufferSeekOverHole',
|
||||
// Identifier for a seek triggered to workaround a playback stuck although currentTime is buffered
|
||||
BUFFER_SEEK_STUCK_IN_BUFFERED : 'bufferSeekStuckInBuffered',
|
||||
// Identifier for an internal exception happening inside hls.js while handling an event
|
||||
INTERNAL_EXCEPTION: 'internalException'
|
||||
};
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
import {logger} from './utils/logger';
|
||||
import {ErrorTypes, ErrorDetails} from './errors';
|
||||
import Event from './events';
|
||||
|
||||
class EventHandler {
|
||||
|
||||
|
@ -59,10 +59,8 @@ module.exports = {
|
||||
FRAG_BUFFERED: 'hlsFragBuffered',
|
||||
// fired when fragment matching with current media position is changing - data : { frag : fragment object }
|
||||
FRAG_CHANGED: 'hlsFragChanged',
|
||||
// Identifier for a FPS drop event - data: {curentDropped, currentDecoded, totalDroppedFrames}
|
||||
// Identifier for a FPS drop event - data: {curentDropped, currentDecoded, totalDroppedFrames}
|
||||
FPS_DROP: 'hlsFpsDrop',
|
||||
//triggered when FPS drop triggers auto level capping - data: {level, droppedlevel}
|
||||
FPS_DROP_LEVEL_CAPPING: 'hlsFpsDropLevelCapping',
|
||||
// Identifier for an error event - data: { type : error type, details : error details, fatal : if true, hls.js cannot/will not try to recover, if false, hls.js will try to recover,other error specific data}
|
||||
ERROR: 'hlsError',
|
||||
// fired when hls.js instance starts destroying. Different from MEDIA_DETACHED as one could want to detach and reattach a media to the instance of hls.js to handle mid-rolls for example
|
||||
|
18
dashboard-ui/bower_components/hls.js/src/hls.js
vendored
18
dashboard-ui/bower_components/hls.js/src/hls.js
vendored
@ -13,7 +13,7 @@ import CapLevelController from './controller/cap-level-controller';
|
||||
import StreamController from './controller/stream-controller';
|
||||
import LevelController from './controller/level-controller';
|
||||
import TimelineController from './controller/timeline-controller';
|
||||
import FPSController from './controller/fps-controller';
|
||||
//import FPSController from './controller/fps-controller';
|
||||
import {logger, enableLogs} from './utils/logger';
|
||||
import XhrLoader from './utils/xhr-loader';
|
||||
import EventEmitter from 'events';
|
||||
@ -21,11 +21,6 @@ import KeyLoader from './loader/key-loader';
|
||||
|
||||
class Hls {
|
||||
|
||||
static get version() {
|
||||
// replaced with browserify-versionify transform
|
||||
return '__VERSION__';
|
||||
}
|
||||
|
||||
static isSupported() {
|
||||
return (window.MediaSource && window.MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"'));
|
||||
}
|
||||
@ -47,13 +42,13 @@ class Hls {
|
||||
Hls.defaultConfig = {
|
||||
autoStartLoad: true,
|
||||
debug: false,
|
||||
capLevelOnFPSDrop: false,
|
||||
capLevelToPlayerSize: false,
|
||||
maxBufferLength: 30,
|
||||
maxBufferSize: 60 * 1000 * 1000,
|
||||
maxBufferHole: 0.5,
|
||||
maxSeekHole: 2,
|
||||
seekHoleNudgeDuration : 0.01,
|
||||
stalledInBufferedNudgeThreshold: 10,
|
||||
maxFragLookUpTolerance : 0.2,
|
||||
liveSyncDurationCount:3,
|
||||
liveMaxLatencyDurationCount: Infinity,
|
||||
@ -73,8 +68,8 @@ class Hls {
|
||||
fragLoadingRetryDelay: 1000,
|
||||
fragLoadingLoopThreshold: 3,
|
||||
startFragPrefetch : false,
|
||||
fpsDroppedMonitoringPeriod: 5000,
|
||||
fpsDroppedMonitoringThreshold: 0.2,
|
||||
// fpsDroppedMonitoringPeriod: 5000,
|
||||
// fpsDroppedMonitoringThreshold: 0.2,
|
||||
appendErrorMaxRetry: 3,
|
||||
loader: XhrLoader,
|
||||
fLoader: undefined,
|
||||
@ -82,7 +77,6 @@ class Hls {
|
||||
abrController : AbrController,
|
||||
bufferController : BufferController,
|
||||
capLevelController : CapLevelController,
|
||||
fpsController: FPSController,
|
||||
streamController: StreamController,
|
||||
timelineController: TimelineController,
|
||||
enableCEA708Captions: true,
|
||||
@ -136,10 +130,10 @@ class Hls {
|
||||
this.abrController = new config.abrController(this);
|
||||
this.bufferController = new config.bufferController(this);
|
||||
this.capLevelController = new config.capLevelController(this);
|
||||
this.fpsController = new config.fpsController(this);
|
||||
this.streamController = new config.streamController(this);
|
||||
this.timelineController = new config.timelineController(this);
|
||||
this.keyLoader = new KeyLoader(this);
|
||||
//this.fpsController = new FPSController(this);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@ -152,10 +146,10 @@ class Hls {
|
||||
this.abrController.destroy();
|
||||
this.bufferController.destroy();
|
||||
this.capLevelController.destroy();
|
||||
this.fpsController.destroy();
|
||||
this.streamController.destroy();
|
||||
this.timelineController.destroy();
|
||||
this.keyLoader.destroy();
|
||||
//this.fpsController.destroy();
|
||||
this.url = null;
|
||||
this.observer.removeAllListeners();
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ class FragmentLoader extends EventHandler {
|
||||
this.hls.trigger(Event.ERROR, {type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_TIMEOUT, fatal: false, frag: this.frag});
|
||||
}
|
||||
|
||||
loadprogress(stats) {
|
||||
loadprogress(event, stats) {
|
||||
this.frag.loaded = stats.loaded;
|
||||
this.hls.trigger(Event.FRAG_LOAD_PROGRESS, {frag: this.frag, stats: stats});
|
||||
}
|
||||
|
@ -211,7 +211,6 @@ class PlaylistLoader extends EventHandler {
|
||||
totalduration-=frag.duration;
|
||||
}
|
||||
level.totalduration = totalduration;
|
||||
level.averagetargetduration = totalduration / level.fragments.length;
|
||||
level.endSN = currentSN - 1;
|
||||
return level;
|
||||
}
|
||||
@ -225,8 +224,7 @@ class PlaylistLoader extends EventHandler {
|
||||
hls = this.hls,
|
||||
levels;
|
||||
// responseURL not supported on some browsers (it is used to detect URL redirection)
|
||||
// data-uri mode also not supported (but no need to detect redirection)
|
||||
if (url === undefined || url.indexOf('data:') === 0) {
|
||||
if (url === undefined) {
|
||||
// fallback to initial URL
|
||||
url = this.url;
|
||||
}
|
||||
|
@ -132,164 +132,135 @@ class MP4Remuxer {
|
||||
}
|
||||
|
||||
remuxVideo(track, timeOffset, contiguous) {
|
||||
var offset = 8,
|
||||
var view,
|
||||
offset = 8,
|
||||
pesTimeScale = this.PES_TIMESCALE,
|
||||
pes2mp4ScaleFactor = this.PES2MP4SCALEFACTOR,
|
||||
mp4SampleDuration,
|
||||
avcSample,
|
||||
mp4Sample,
|
||||
mp4SampleLength,
|
||||
unit,
|
||||
mdat, moof,
|
||||
firstPTS, firstDTS,
|
||||
nextDTS,
|
||||
lastPTS, lastDTS,
|
||||
inputSamples = track.samples,
|
||||
outputSamples = [];
|
||||
|
||||
// PTS is coded on 33bits, and can loop from -2^32 to 2^32
|
||||
// PTSNormalize will make PTS/DTS value monotonic, we use last known DTS value as reference value
|
||||
let nextAvcDts;
|
||||
if (contiguous) {
|
||||
// if parsed fragment is contiguous with last one, let's use last DTS value as reference
|
||||
nextAvcDts = this.nextAvcDts;
|
||||
} else {
|
||||
// if not contiguous, let's use target timeOffset
|
||||
nextAvcDts = timeOffset*pesTimeScale;
|
||||
}
|
||||
|
||||
// compute first DTS and last DTS, normalize them against reference value
|
||||
let sample = inputSamples[0];
|
||||
firstDTS = Math.max(this._PTSNormalize(sample.dts,nextAvcDts) - this._initDTS,0);
|
||||
firstPTS = Math.max(this._PTSNormalize(sample.pts,nextAvcDts) - this._initDTS,0);
|
||||
|
||||
// check timestamp continuity accross consecutive fragments (this is to remove inter-fragment gap/hole)
|
||||
let delta = Math.round((firstDTS - nextAvcDts) / 90);
|
||||
// if fragment are contiguous, or if there is a huge delta (more than 10s) between expected PTS and sample PTS
|
||||
if (contiguous || Math.abs(delta) > 10000) {
|
||||
if (delta) {
|
||||
if (delta > 1) {
|
||||
logger.log(`AVC:${delta} ms hole between fragments detected,filling it`);
|
||||
} else if (delta < -1) {
|
||||
logger.log(`AVC:${(-delta)} ms overlapping between fragments detected`);
|
||||
}
|
||||
// remove hole/gap : set DTS to next expected DTS
|
||||
firstDTS = nextAvcDts;
|
||||
inputSamples[0].dts = firstDTS + this._initDTS;
|
||||
// offset PTS as well, ensure that PTS is smaller or equal than new DTS
|
||||
firstPTS = Math.max(firstPTS - delta, nextAvcDts);
|
||||
inputSamples[0].pts = firstPTS + this._initDTS;
|
||||
logger.log(`Video/PTS/DTS adjusted: ${firstPTS}/${firstDTS},delta:${delta}`);
|
||||
}
|
||||
}
|
||||
nextDTS = firstDTS;
|
||||
|
||||
// compute lastPTS/lastDTS
|
||||
sample = inputSamples[inputSamples.length-1];
|
||||
lastDTS = Math.max(this._PTSNormalize(sample.dts,nextAvcDts) - this._initDTS,0);
|
||||
lastPTS = Math.max(this._PTSNormalize(sample.pts,nextAvcDts) - this._initDTS,0);
|
||||
lastPTS = Math.max(lastPTS, lastDTS);
|
||||
|
||||
let vendor = navigator.vendor, userAgent = navigator.userAgent,
|
||||
isSafari = vendor && vendor.indexOf('Apple') > -1 && userAgent && !userAgent.match('CriOS');
|
||||
|
||||
// on Safari let's signal the same sample duration for all samples
|
||||
// sample duration (as expected by trun MP4 boxes), should be the delta between sample DTS
|
||||
// set this constant duration as being the avg delta between consecutive DTS.
|
||||
if (isSafari) {
|
||||
mp4SampleDuration = Math.round((lastDTS-firstDTS)/(pes2mp4ScaleFactor*(inputSamples.length-1)));
|
||||
}
|
||||
|
||||
// normalize all PTS/DTS now ...
|
||||
for (let i = 0; i < inputSamples.length; i++) {
|
||||
let sample = inputSamples[i];
|
||||
if (isSafari) {
|
||||
// sample DTS is computed using a constant decoding offset (mp4SampleDuration) between samples
|
||||
sample.dts = firstDTS + i*pes2mp4ScaleFactor*mp4SampleDuration;
|
||||
} else {
|
||||
// ensure sample monotonic DTS
|
||||
sample.dts = Math.max(this._PTSNormalize(sample.dts, nextAvcDts) - this._initDTS,firstDTS);
|
||||
// ensure dts is a multiple of scale factor to avoid rounding issues
|
||||
sample.dts = Math.round(sample.dts/pes2mp4ScaleFactor)*pes2mp4ScaleFactor;
|
||||
}
|
||||
// we normalize PTS against nextAvcDts, we also substract initDTS (some streams don't start @ PTS O)
|
||||
// and we ensure that computed value is greater or equal than sample DTS
|
||||
sample.pts = Math.max(this._PTSNormalize(sample.pts,nextAvcDts) - this._initDTS, sample.dts);
|
||||
// ensure pts is a multiple of scale factor to avoid rounding issues
|
||||
sample.pts = Math.round(sample.pts/pes2mp4ScaleFactor)*pes2mp4ScaleFactor;
|
||||
}
|
||||
|
||||
firstPTS, firstDTS, lastDTS,
|
||||
pts, dts, ptsnorm, dtsnorm,
|
||||
flags,
|
||||
samples = [];
|
||||
/* concatenate the video data and construct the mdat in place
|
||||
(need 8 more bytes to fill length and mpdat type) */
|
||||
mdat = new Uint8Array(track.len + (4 * track.nbNalu) + 8);
|
||||
let view = new DataView(mdat.buffer);
|
||||
view = new DataView(mdat.buffer);
|
||||
view.setUint32(0, mdat.byteLength);
|
||||
mdat.set(MP4.types.mdat, 4);
|
||||
|
||||
for (let i = 0; i < inputSamples.length; i++) {
|
||||
let avcSample = inputSamples[i],
|
||||
mp4SampleLength = 0,
|
||||
compositionTimeOffset;
|
||||
while (track.samples.length) {
|
||||
avcSample = track.samples.shift();
|
||||
mp4SampleLength = 0;
|
||||
// convert NALU bitstream to MP4 format (prepend NALU with size field)
|
||||
while (avcSample.units.units.length) {
|
||||
let unit = avcSample.units.units.shift();
|
||||
unit = avcSample.units.units.shift();
|
||||
view.setUint32(offset, unit.data.byteLength);
|
||||
offset += 4;
|
||||
mdat.set(unit.data, offset);
|
||||
offset += unit.data.byteLength;
|
||||
mp4SampleLength += 4 + unit.data.byteLength;
|
||||
}
|
||||
|
||||
if(!isSafari) {
|
||||
// expected sample duration is the Decoding Timestamp diff of consecutive samples
|
||||
if (i < inputSamples.length - 1) {
|
||||
mp4SampleDuration = inputSamples[i+1].dts - avcSample.dts;
|
||||
} else {
|
||||
// last sample duration is same than previous one
|
||||
mp4SampleDuration = avcSample.dts - inputSamples[i-1].dts;
|
||||
pts = avcSample.pts - this._initDTS;
|
||||
dts = avcSample.dts - this._initDTS;
|
||||
// ensure DTS is not bigger than PTS
|
||||
dts = Math.min(pts,dts);
|
||||
//logger.log(`Video/PTS/DTS:${Math.round(pts/90)}/${Math.round(dts/90)}`);
|
||||
// if not first AVC sample of video track, normalize PTS/DTS with previous sample value
|
||||
// and ensure that sample duration is positive
|
||||
if (lastDTS !== undefined) {
|
||||
ptsnorm = this._PTSNormalize(pts, lastDTS);
|
||||
dtsnorm = this._PTSNormalize(dts, lastDTS);
|
||||
var sampleDuration = (dtsnorm - lastDTS) / pes2mp4ScaleFactor;
|
||||
if (sampleDuration <= 0) {
|
||||
logger.log(`invalid sample duration at PTS/DTS: ${avcSample.pts}/${avcSample.dts}:${sampleDuration}`);
|
||||
sampleDuration = 1;
|
||||
}
|
||||
mp4SampleDuration /= pes2mp4ScaleFactor;
|
||||
compositionTimeOffset = Math.round((avcSample.pts - avcSample.dts) / pes2mp4ScaleFactor);
|
||||
mp4Sample.duration = sampleDuration;
|
||||
} else {
|
||||
compositionTimeOffset = Math.max(0,mp4SampleDuration*Math.round((avcSample.pts - avcSample.dts)/(pes2mp4ScaleFactor*mp4SampleDuration)));
|
||||
let nextAvcDts, delta;
|
||||
if (contiguous) {
|
||||
nextAvcDts = this.nextAvcDts;
|
||||
} else {
|
||||
nextAvcDts = timeOffset*pesTimeScale;
|
||||
}
|
||||
// first AVC sample of video track, normalize PTS/DTS
|
||||
ptsnorm = this._PTSNormalize(pts, nextAvcDts);
|
||||
dtsnorm = this._PTSNormalize(dts, nextAvcDts);
|
||||
delta = Math.round((dtsnorm - nextAvcDts) / 90);
|
||||
// if fragment are contiguous, detect hole/overlapping between fragments
|
||||
if (contiguous) {
|
||||
if (delta) {
|
||||
if (delta > 1) {
|
||||
logger.log(`AVC:${delta} ms hole between fragments detected,filling it`);
|
||||
} else if (delta < -1) {
|
||||
logger.log(`AVC:${(-delta)} ms overlapping between fragments detected`);
|
||||
}
|
||||
// set DTS to next DTS
|
||||
dtsnorm = nextAvcDts;
|
||||
// offset PTS as well, ensure that PTS is smaller or equal than new DTS
|
||||
ptsnorm = Math.max(ptsnorm - delta, dtsnorm);
|
||||
logger.log(`Video/PTS/DTS adjusted: ${ptsnorm}/${dtsnorm},delta:${delta}`);
|
||||
}
|
||||
}
|
||||
// remember first PTS of our avcSamples, ensure value is positive
|
||||
firstPTS = Math.max(0, ptsnorm);
|
||||
firstDTS = Math.max(0, dtsnorm);
|
||||
}
|
||||
|
||||
|
||||
//console.log('PTS/DTS/initDTS/normPTS/normDTS/relative PTS : ${avcSample.pts}/${avcSample.dts}/${this._initDTS}/${ptsnorm}/${dtsnorm}/${(avcSample.pts/4294967296).toFixed(3)}');
|
||||
outputSamples.push({
|
||||
mp4Sample = {
|
||||
size: mp4SampleLength,
|
||||
// constant duration
|
||||
duration: mp4SampleDuration,
|
||||
cts: compositionTimeOffset,
|
||||
duration: 0,
|
||||
cts: (ptsnorm - dtsnorm) / pes2mp4ScaleFactor,
|
||||
flags: {
|
||||
isLeading: 0,
|
||||
isDependedOn: 0,
|
||||
hasRedundancy: 0,
|
||||
degradPrio: 0,
|
||||
dependsOn : avcSample.key ? 2 : 1,
|
||||
isNonSync : avcSample.key ? 0 : 1
|
||||
degradPrio: 0
|
||||
}
|
||||
});
|
||||
};
|
||||
flags = mp4Sample.flags;
|
||||
if (avcSample.key === true) {
|
||||
// the current sample is a key frame
|
||||
flags.dependsOn = 2;
|
||||
flags.isNonSync = 0;
|
||||
} else {
|
||||
flags.dependsOn = 1;
|
||||
flags.isNonSync = 1;
|
||||
}
|
||||
samples.push(mp4Sample);
|
||||
lastDTS = dtsnorm;
|
||||
}
|
||||
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
||||
this.nextAvcDts = lastDTS + mp4SampleDuration*pes2mp4ScaleFactor;
|
||||
var lastSampleDuration = 0;
|
||||
if (samples.length >= 2) {
|
||||
lastSampleDuration = samples[samples.length - 2].duration;
|
||||
mp4Sample.duration = lastSampleDuration;
|
||||
}
|
||||
// next AVC sample DTS should be equal to last sample DTS + last sample duration
|
||||
this.nextAvcDts = dtsnorm + lastSampleDuration * pes2mp4ScaleFactor;
|
||||
track.len = 0;
|
||||
track.nbNalu = 0;
|
||||
if(outputSamples.length && navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
|
||||
let flags = outputSamples[0].flags;
|
||||
if(samples.length && navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
|
||||
flags = samples[0].flags;
|
||||
// chrome workaround, mark first sample as being a Random Access Point to avoid sourcebuffer append issue
|
||||
// https://code.google.com/p/chromium/issues/detail?id=229412
|
||||
flags.dependsOn = 2;
|
||||
flags.isNonSync = 0;
|
||||
}
|
||||
track.samples = outputSamples;
|
||||
track.samples = samples;
|
||||
moof = MP4.moof(track.sequenceNumber++, firstDTS / pes2mp4ScaleFactor, track);
|
||||
track.samples = [];
|
||||
this.observer.trigger(Event.FRAG_PARSING_DATA, {
|
||||
data1: moof,
|
||||
data2: mdat,
|
||||
startPTS: firstPTS / pesTimeScale,
|
||||
endPTS: (lastPTS + pes2mp4ScaleFactor * mp4SampleDuration) / pesTimeScale,
|
||||
endPTS: (ptsnorm + pes2mp4ScaleFactor * lastSampleDuration) / pesTimeScale,
|
||||
startDTS: firstDTS / pesTimeScale,
|
||||
endDTS: this.nextAvcDts / pesTimeScale,
|
||||
type: 'video',
|
||||
nb: outputSamples.length
|
||||
nb: samples.length
|
||||
});
|
||||
}
|
||||
|
||||
@ -328,7 +299,7 @@ class MP4Remuxer {
|
||||
mp4Sample.duration = (dtsnorm - lastDTS) / pes2mp4ScaleFactor;
|
||||
if(Math.abs(mp4Sample.duration - expectedSampleDuration) > expectedSampleDuration/10) {
|
||||
// more than 10% diff between sample duration and expectedSampleDuration .... lets log that
|
||||
logger.trace(`invalid AAC sample duration at PTS ${Math.round(pts/90)},should be 1024,found :${Math.round(mp4Sample.duration*track.audiosamplerate/track.timescale)}`);
|
||||
logger.log(`invalid AAC sample duration at PTS ${Math.round(pts/90)},should be 1024,found :${Math.round(mp4Sample.duration*track.audiosamplerate/track.timescale)}`);
|
||||
}
|
||||
// always adjust sample duration to avoid av sync issue
|
||||
mp4Sample.duration = expectedSampleDuration;
|
||||
@ -343,20 +314,20 @@ class MP4Remuxer {
|
||||
ptsnorm = this._PTSNormalize(pts, nextAacPts);
|
||||
dtsnorm = this._PTSNormalize(dts, nextAacPts);
|
||||
delta = Math.round(1000 * (ptsnorm - nextAacPts) / pesTimeScale);
|
||||
// if fragment are contiguous, or if there is a huge delta (more than 10s) between expected PTS and sample PTS
|
||||
if (contiguous || Math.abs(delta) > 10000) {
|
||||
// if fragment are contiguous, detect hole/overlapping between fragments
|
||||
if (contiguous) {
|
||||
// log delta
|
||||
if (delta) {
|
||||
if (delta > 0) {
|
||||
logger.log(`${delta} ms hole between AAC samples detected,filling it`);
|
||||
// if we have frame overlap, overlapping for more than half a frame duraion
|
||||
// if we have frame overlap, overlapping for more than half a frame duration
|
||||
} else if (delta < -12) {
|
||||
// drop overlapping audio frames... browser will deal with it
|
||||
logger.log(`${(-delta)} ms overlapping between AAC samples detected, drop frame`);
|
||||
track.len -= unit.byteLength;
|
||||
continue;
|
||||
}
|
||||
// set PTS/DTS to expected PTS/DTS
|
||||
// set PTS/DTS to next PTS/DTS
|
||||
ptsnorm = dtsnorm = nextAacPts;
|
||||
}
|
||||
}
|
||||
|
@ -112,11 +112,8 @@ class XhrLoader {
|
||||
stats.tfirst = performance.now();
|
||||
}
|
||||
stats.loaded = event.loaded;
|
||||
if (event.lengthComputable) {
|
||||
stats.total = event.total;
|
||||
}
|
||||
if (this.onProgress) {
|
||||
this.onProgress(stats);
|
||||
this.onProgress(event, stats);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,14 +30,14 @@
|
||||
"web-component-tester": "polymer/web-component-tester#^3.4.0"
|
||||
},
|
||||
"ignore": [],
|
||||
"homepage": "https://github.com/PolymerElements/iron-a11y-announcer",
|
||||
"homepage": "https://github.com/polymerelements/iron-a11y-announcer",
|
||||
"_release": "1.0.4",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v1.0.4",
|
||||
"commit": "5ce3eb8c4282bb53cd72e348858dc6be6b4c50b9"
|
||||
},
|
||||
"_source": "git://github.com/PolymerElements/iron-a11y-announcer.git",
|
||||
"_source": "git://github.com/polymerelements/iron-a11y-announcer.git",
|
||||
"_target": "^1.0.0",
|
||||
"_originalSource": "PolymerElements/iron-a11y-announcer"
|
||||
"_originalSource": "polymerelements/iron-a11y-announcer"
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "iron-behaviors",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.14",
|
||||
"description": "Provides a set of behaviors for the iron elements",
|
||||
"private": true,
|
||||
"authors": [
|
||||
@ -29,14 +29,14 @@
|
||||
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0"
|
||||
},
|
||||
"ignore": [],
|
||||
"homepage": "https://github.com/polymerelements/iron-behaviors",
|
||||
"_release": "1.0.13",
|
||||
"homepage": "https://github.com/PolymerElements/iron-behaviors",
|
||||
"_release": "1.0.14",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v1.0.13",
|
||||
"commit": "a7bc3428a6da2beed21987b3a8028206826a12bc"
|
||||
"tag": "v1.0.14",
|
||||
"commit": "c1d38a26219cf2e83b31a71a2bd8ae35ebee7ed7"
|
||||
},
|
||||
"_source": "git://github.com/polymerelements/iron-behaviors.git",
|
||||
"_source": "git://github.com/PolymerElements/iron-behaviors.git",
|
||||
"_target": "^1.0.0",
|
||||
"_originalSource": "polymerelements/iron-behaviors"
|
||||
"_originalSource": "PolymerElements/iron-behaviors"
|
||||
}
|
33
dashboard-ui/bower_components/iron-behaviors/.github/ISSUE_TEMPLATE.md
vendored
Normal file
33
dashboard-ui/bower_components/iron-behaviors/.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
<!-- Instructions: https://github.com/PolymerElements/iron-behaviors/CONTRIBUTING.md#filing-issues -->
|
||||
### Description
|
||||
<!-- Example: The `paper-foo` element causes the page to turn pink when clicked. -->
|
||||
|
||||
### Expected outcome
|
||||
|
||||
<!-- Example: The page stays the same color. -->
|
||||
|
||||
### Actual outcome
|
||||
|
||||
<!-- Example: The page turns pink. -->
|
||||
|
||||
### Live Demo
|
||||
<!-- Example: https://jsbin.com/cagaye/edit?html,output -->
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
<!-- Example
|
||||
1. Put a `paper-foo` element in the page.
|
||||
2. Open the page in a web browser.
|
||||
3. Click the `paper-foo` element.
|
||||
-->
|
||||
|
||||
### Browsers Affected
|
||||
<!-- Check all that apply -->
|
||||
- [ ] Chrome
|
||||
- [ ] Firefox
|
||||
- [ ] Safari 9
|
||||
- [ ] Safari 8
|
||||
- [ ] Safari 7
|
||||
- [ ] Edge
|
||||
- [ ] IE 11
|
||||
- [ ] IE 10
|
@ -1,5 +1,5 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
sudo: required
|
||||
before_script:
|
||||
- npm install -g bower polylint web-component-tester
|
||||
- bower install
|
||||
@ -8,18 +8,16 @@ env:
|
||||
global:
|
||||
- secure: ZOqj2XVNVwfT74rHxg/ljcAsS6FnmDpRSsXbsy1Icv9DcLHrMlmyQ10gWBjE/YXYF0Uv4akQ1qqn0TJaKOtp9HZeH+P6OPAYk2vJbWD7qp52pPtIqEFomcsUyflt4IjfaXKuN4FMod7PSWVSGJ+DxSguJvZKILkrs5d/rJdFv3c=
|
||||
- secure: clkqemGQG16TXyAPkv9LBv6x3SbT3ZM0eo8LETx4uNKi3WzlwgXxZA9b5Sr5wYzxyxFFpnhDXW7CL4+UjYu1atGNeTW2TuSaYUPHtgu67OFDr8Jbw047p1XQb5enPSt9+YxrHKfjHBiJvWulJ8rCSQshU9Rhe0DC6NrFRPFgk0A=
|
||||
- CXX=g++-4.8
|
||||
node_js: stable
|
||||
addons:
|
||||
firefox: latest
|
||||
apt:
|
||||
sources:
|
||||
- google-chrome
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- google-chrome-stable
|
||||
- g++-4.8
|
||||
sauce_connect: true
|
||||
script:
|
||||
- xvfb-run wct
|
||||
- "if [ \"${TRAVIS_PULL_REQUEST}\" = \"false\" ]; then wct -s 'default'; fi"
|
||||
dist: trusty
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "iron-behaviors",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.14",
|
||||
"description": "Provides a set of behaviors for the iron elements",
|
||||
"private": true,
|
||||
"authors": [
|
||||
|
@ -1,5 +1,6 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
|
@ -1,4 +1,5 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
|
@ -87,7 +87,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
this.style.pointerEvents = disabled ? 'none' : '';
|
||||
if (disabled) {
|
||||
this._oldTabIndex = this.tabIndex;
|
||||
this.focused = false;
|
||||
this._setFocused(false);
|
||||
this.tabIndex = -1;
|
||||
this.blur();
|
||||
} else if (this._oldTabIndex !== undefined) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
|
@ -1,5 +1,6 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
|
@ -1,5 +1,6 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
@license
|
||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user