Merge remote-tracking branch 'upstream/master' into es6

With conflicts
This commit is contained in:
MrTimscampi 2020-07-24 10:23:14 +02:00
commit 3713091382
165 changed files with 4194 additions and 2714 deletions

View file

@ -36,6 +36,14 @@ jobs:
targetPath: '$(Build.SourcesDirectory)/deployment/dist'
artifactName: 'jellyfin-web-$(BuildConfiguration)'
- task: SSH@0
displayName: 'Create target directory on repository server'
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
inputs:
sshEndpoint: repository
runOptions: 'inline'
inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
- task: CopyFilesOverSSH@0
displayName: 'Upload artifacts to repository server'
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))

View file

@ -2,4 +2,4 @@ version: 1
update_configs:
- package_manager: "javascript"
directory: "/"
update_schedule: "weekly"
update_schedule: "live"

4
.github/CODEOWNERS vendored
View file

@ -1,4 +1,6 @@
.ci @dkanada @EraYaN
.github @jellyfin/core
build.sh @joshuaboniface
fedora @joshuaboniface
debian @joshuaboniface
.copr @joshuaboniface
deployment @joshuaboniface

View file

@ -1,23 +1,20 @@
---
name: Bug report
about: Create a bug report
title: ''
name: Bug Report
about: You have noticed a general issue or regression, and would like to report it
labels: bug
assignees: ''
---
**Describe the bug**
**Describe The Bug**
<!-- A clear and concise description of what the bug is. -->
**To Reproduce**
**Steps To Reproduce**
<!-- Steps to reproduce the behavior: -->
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
**Expected Behavior**
<!-- A clear and concise description of what you expected to happen. -->
**Logs**
@ -27,9 +24,9 @@ assignees: ''
<!-- If applicable, add screenshots to help explain your problem. -->
**System (please complete the following information):**
- OS: [e.g. Docker, Debian, Windows]
- Platform: [e.g. Linux, Windows, iPhone, Tizen]
- Browser: [e.g. Firefox, Chrome, Safari]
- Jellyfin Version: [e.g. 10.0.1]
- Jellyfin Version: [e.g. 10.6.0]
**Additional context**
**Additional Context**
<!-- Add any other context about the problem here. -->

View file

@ -0,0 +1,22 @@
---
name: Playback Issue
about: You have playback issues with some files
labels: playback
---
**Describe The Bug**
<!-- A clear and concise description of what the bug is. -->
**Media Information**
<!-- Please paste any ffprobe or MediaInfo logs. -->
**Screenshots**
<!-- Add screenshots from the Playback Data and Media Info. -->
**System (please complete the following information):**
- Platform: [e.g. Linux, Windows, iPhone, Tizen]
- Browser: [e.g. Firefox, Chrome, Safari]
- Jellyfin Version: [e.g. 10.6.0]
**Additional Context**
<!-- Add any other context about the problem here. -->

View file

@ -0,0 +1,13 @@
---
name: Technical Discussion
about: You want to discuss technical aspects of changes you intend to make
labels: enhancement
---
<!-- Explain the change and the motivations behind it.
For example, if you plan to rely on a new dependency, explain why and what
it brings to the project.
If you plan to make significant changes, go roughly over the steps you intend
to take and how you would divide the change in PRs of a manageable size. -->

View file

@ -0,0 +1,9 @@
---
name: Meta Issue
about: You want to track a number of other issues as part of a larger project
labels: meta
---
* [ ] Issue 1 [#123]
* [ ] Issue 2 [#456]
* [ ] ...

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Feature Request
url: https://features.jellyfin.org/
about: Please head over to our feature request hub to vote on or submit a feature.
- name: Help Or Question
url: https://matrix.to/#/#jellyfin-troubleshooting:matrix.org
about: Please join the troubleshooting Matrix channel to get some help.

24
.github/SUPPORT.md vendored Normal file
View file

@ -0,0 +1,24 @@
# Support
Jellyfin contributors have limited availability to address general support
questions. Please make sure you are using the latest version of Jellyfin.
When looking for support or information, please first search for your
question in these venues:
* [Jellyfin Forum](https://forum.jellyfin.org)
* [Jellyfin Documentation](https://docs.jellyfin.org)
* [Open or **closed** issues in the organization](https://github.com/issues?q=sort%3Aupdated-desc+org%3Ajellyfin+is%3Aissue+)
If you didn't find an answer in the resources above, contributors and other
users are reachable through the following channels:
* #jellyfin on [Matrix](https://matrix.to/#/#jellyfin:matrix.org%22) or [IRC](https://webchat.freenode.net/#jellyfin)
* #jellyfin-troubleshooting on [Matrix](https://matrix.to/#/#jellyfin-troubleshooting:matrix.org) or [IRC](https://webchat.freenode.net/#jellyfin-troubleshooting)
* [/r/jellyfin on Reddit](https://www.reddit.com/r/jellyfin)
GitHub issues are for tracking enhancements and bugs, not general support.
The open source license grants you the freedom to use Jellyfin.
It does not guarantee commitments of other people's time.
Please be respectful and manage your expectations.

View file

@ -184,7 +184,12 @@ function copy(query) {
function injectBundle() {
return src(options.injectBundle.query, { base: './src/' })
.pipe(inject(
src(['src/scripts/apploader.js'], { read: false }, { base: './src/' }), { relative: true }
src(['src/scripts/apploader.js'], { read: false }, { base: './src/' }), {
relative: true,
transform: function (filepath) {
return `<script src="${filepath}" defer></script>`;
}
}
))
.pipe(dest('dist/'))
.pipe(browserSync.stream());

View file

@ -5,16 +5,16 @@
"repository": "https://github.com/jellyfin/jellyfin-web",
"license": "GPL-2.0-or-later",
"devDependencies": {
"@babel/core": "^7.10.3",
"@babel/core": "^7.10.5",
"@babel/plugin-proposal-class-properties": "^7.10.1",
"@babel/plugin-proposal-private-methods": "^7.10.1",
"@babel/plugin-transform-modules-amd": "^7.9.6",
"@babel/plugin-transform-modules-amd": "^7.10.5",
"@babel/polyfill": "^7.8.7",
"@babel/preset-env": "^7.10.3",
"autoprefixer": "^9.8.2",
"autoprefixer": "^9.8.5",
"babel-eslint": "^11.0.0-beta.2",
"babel-loader": "^8.0.6",
"browser-sync": "^2.26.7",
"browser-sync": "^2.26.10",
"copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.6.0",
"cssnano": "^4.1.10",
@ -57,16 +57,15 @@
"blurhash": "^1.1.3",
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
"core-js": "^3.6.5",
"date-fns": "^2.14.0",
"document-register-element": "^1.14.3",
"date-fns": "^2.15.0",
"epubjs": "^0.3.85",
"fast-text-encoding": "^1.0.3",
"flv.js": "^1.5.0",
"headroom.js": "^0.11.0",
"hls.js": "^0.13.1",
"hls.js": "^0.14.3",
"howler": "^2.2.0",
"intersection-observer": "^0.10.0",
"jellyfin-apiclient": "^1.2.2",
"intersection-observer": "^0.11.0",
"jellyfin-apiclient": "^1.4.1",
"jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto",
"jquery": "^3.5.1",
"jstree": "^3.3.10",
@ -77,11 +76,11 @@
"query-string": "^6.13.1",
"resize-observer-polyfill": "^1.5.1",
"screenfull": "^5.0.2",
"shaka-player": "^3.0.1",
"shaka-player": "^2.5.13",
"sortablejs": "^1.10.2",
"swiper": "^5.4.5",
"webcomponents.js": "^0.7.24",
"whatwg-fetch": "^3.0.0"
"whatwg-fetch": "^3.2.0"
},
"babel": {
"presets": [
@ -174,6 +173,7 @@
"src/controllers/dashboard/metadataImages.js",
"src/controllers/dashboard/metadatanfo.js",
"src/controllers/dashboard/plugins/repositories.js",
<<<<<<< HEAD
"src/controllers/dashboard/streaming.js",
"src/controllers/dashboard/mediaLibrary.js",
"src/controllers/dashboard/networking.js",
@ -230,6 +230,14 @@
"src/elements/emby-button/paper-icon-button-light.js",
"src/elements/emby-collapse/emby-collapse.js",
"src/elements/emby-input/emby-input.js",
=======
"src/controllers/playback/nowplaying.js",
"src/controllers/playback/videoosd.js",
"src/controllers/user/display.js",
"src/controllers/user/home.js",
"src/controllers/user/playback.js",
"src/controllers/user/subtitles.js",
>>>>>>> upstream/master
"src/plugins/bookPlayer/plugin.js",
"src/plugins/bookPlayer/tableOfContents.js",
"src/plugins/photoPlayer/plugin.js",

View file

@ -235,6 +235,15 @@ div[data-role=controlgroup] a.ui-btn-active {
width: 50%;
}
.localUsers .cardText-secondary {
white-space: pre-wrap;
height: 3em;
}
.customCssContainer textarea {
resize: none;
}
@media all and (min-width: 70em) {
.dashboardSections {
-webkit-flex-wrap: wrap;

View file

@ -30,6 +30,10 @@
align-items: flex-start;
}
.align-items-flex-end {
align-items: flex-end;
}
.justify-content-center {
justify-content: center;
}
@ -38,6 +42,10 @@
justify-content: flex-end;
}
.justify-content-space-between {
justify-content: space-between;
}
.flex-wrap-wrap {
flex-wrap: wrap;
}

View file

@ -1,8 +1,3 @@
html {
font-size: 82% !important;
}
.formDialogFooter {
position: static !important;
margin: 0 -1em !important;
}

View file

@ -24,10 +24,6 @@
padding-top: 7em !important;
}
.layout-mobile .libraryPage {
padding-top: 4em !important;
}
.itemDetailPage {
padding-top: 0 !important;
}
@ -164,6 +160,7 @@
display: flex;
flex-direction: column;
contain: layout style paint;
transition: background ease-in-out 0.5s;
}
.hiddenViewMenuBar .skinHeader {
@ -178,6 +175,10 @@
width: 100%;
}
.layout-tv .sectionTabs {
width: 55%;
}
.selectedMediaFolder {
background-color: #f2f2f2 !important;
}
@ -272,7 +273,7 @@
}
}
@media all and (max-width: 84em) {
@media all and (max-width: 100em) {
.withSectionTabs .headerTop {
padding-bottom: 0.55em;
}
@ -280,9 +281,13 @@
.sectionTabs {
font-size: 83.5%;
}
.layout-tv .sectionTabs {
width: 100%;
}
}
@media all and (min-width: 84em) {
@media all and (min-width: 100em) {
.headerTop {
padding: 0.8em 0.8em;
}
@ -438,12 +443,14 @@
background-repeat: no-repeat;
background-position: center;
background-attachment: fixed;
height: 50vh;
height: 40vh;
position: relative;
animation: backdrop-fadein 800ms ease-in normal both;
}
.layout-mobile .itemBackdrop {
background-attachment: scroll;
height: 26.5vh;
}
.layout-desktop .itemBackdrop::after,
@ -463,10 +470,20 @@
.detailPageContent {
display: flex;
flex-direction: column;
padding-left: 2%;
padding-left: 32.45vw;
padding-right: 2%;
}
.layout-mobile .detailPageContent {
padding-left: 5%;
padding-right: 5%;
}
.layout-desktop .detailPageContent .emby-scroller,
.layout-tv .detailPageContent .emby-scroller {
margin-left: 0;
}
.layout-desktop .noBackdrop .detailPageContent,
.layout-tv .noBackdrop .detailPageContent {
margin-top: 2.5em;
@ -477,6 +494,10 @@
margin-top: 0;
}
.detailSectionContent a {
color: inherit;
}
.personBackdrop {
background-size: contain;
}
@ -495,7 +516,23 @@
.parentName {
display: block;
margin-bottom: 0.5em;
margin: 0 0 0;
}
.layout-mobile .parentName {
margin: 0.6em 0 0;
}
.musicParentName {
margin: 0.15em 0 0.2em;
}
.layout-mobile .musicParentName {
margin: -0.25em 0 0.25em;
}
.layout-mobile .itemExternalLinks {
display: none;
}
.mainDetailButtons {
@ -503,8 +540,6 @@
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
margin: 1em 0;
}
@ -520,6 +555,35 @@
font-weight: 600;
}
.itemName.originalTitle {
margin: 0.2em 0 0.2em;
}
.itemName.parentNameLast {
margin: 0 0 0;
}
.layout-mobile .itemName.parentNameLast {
margin: 0.4em 0 0.4em;
}
.layout-mobile h1.itemName,
.layout-mobile h1.parentName {
font-size: 1.6em;
}
.itemName.parentNameLast.withOriginalTitle {
margin: 0 0 0;
}
.layout-mobile .itemName.parentNameLast.withOriginalTitle {
margin: 0.6em 0 0;
}
.layout-mobile .itemName.originalTitle {
margin: 0.5em 0 0.5em;
}
.nameContainer {
display: flex;
flex-direction: column;
@ -546,6 +610,19 @@
text-align: center;
}
.layout-mobile .mainDetailButtons {
margin-top: 1em;
margin-bottom: 0.5em;
}
.subtitle {
margin: 0.15em 0 0.2em;
}
.layout-mobile .subtitle {
margin: 0.2em 0 0.2em;
}
.detailPagePrimaryContainer {
display: flex;
align-items: center;
@ -556,7 +633,7 @@
.layout-mobile .detailPagePrimaryContainer {
display: block;
position: relative;
top: 0;
padding: 0.5em 3.3% 0.5em;
}
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer,
@ -566,13 +643,14 @@
padding-left: 32.45vw;
}
.layout-desktop .detailSticky,
.layout-tv .detailSticky {
.layout-desktop .detailRibbon,
.layout-tv .detailRibbon {
margin-top: -7.2em;
height: 7.18em;
}
.layout-desktop .noBackdrop .detailSticky,
.layout-tv .noBackdrop .detailSticky {
.layout-desktop .noBackdrop .detailRibbon,
.layout-tv .noBackdrop .detailRibbon {
margin-top: 0;
}
@ -584,6 +662,9 @@
white-space: nowrap;
text-overflow: ellipsis;
text-align: left;
min-width: 0;
max-width: 100%;
overflow: hidden;
}
.layout-mobile .infoText {
@ -594,13 +675,29 @@
margin: 1.25em 0;
}
.detailImageContainer {
position: relative;
margin-top: -25vh;
margin-bottom: 10vh;
.layout-mobile .detailPageSecondaryContainer {
margin: 1em 0;
}
.layout-mobile .detailImageContainer {
display: none;
}
.detailImageContainer .card {
position: absolute;
top: 50%;
float: left;
width: 25vw;
z-index: 3;
transform: translateY(-50%);
}
.detailImageContainer .card.backdropCard {
top: 35%;
}
.detailImageContainer .card.squareCard {
top: 40%;
}
.layout-desktop .noBackdrop .detailImageContainer,
@ -613,11 +710,11 @@
}
.detailLogo {
width: 30vw;
height: 25vh;
width: 25vw;
height: 16vh;
position: absolute;
top: 10vh;
right: 20vw;
right: 25vw;
background-size: contain;
}
@ -657,14 +754,19 @@ div.itemDetailGalleryLink.defaultCardBackground {
position: relative;
}
.layout-desktop .detailPageWrapperContainer,
.layout-tv .detailPageWrapperContainer {
margin-top: 7.2em;
.layout-desktop .itemBackdrop,
.layout-tv .itemBackdrop {
height: 40vh;
}
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer,
.layout-desktop #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer {
padding-left: 3.3%;
.layout-desktop .detailPageWrapperContainer,
.layout-tv .detailPageWrapperContainer {
margin-top: 0.1em;
}
.layout-desktop .detailImageContainer .card,
.layout-tv .detailImageContainer .card {
top: 10%;
}
.btnPlaySimple {
@ -680,12 +782,12 @@ div.itemDetailGalleryLink.defaultCardBackground {
.emby-button.detailFloatingButton {
position: absolute;
background-color: rgba(0, 0, 0, 0.5) !important;
z-index: 1;
top: 50%;
left: 50%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 3;
top: 100%;
left: 90%;
margin: -2.2em 0 0 -2.2em;
padding: 0.4em !important;
padding: 0.4em;
color: rgba(255, 255, 255, 0.76);
}
@ -695,16 +797,12 @@ div.itemDetailGalleryLink.defaultCardBackground {
@media all and (max-width: 62.5em) {
.parentName {
margin-bottom: 1em;
margin-bottom: 0;
}
.itemDetailPage {
padding-top: 0 !important;
}
.detailimg-hidemobile {
display: none;
}
}
@media all and (min-width: 31.25em) {
@ -820,21 +918,7 @@ div.itemDetailGalleryLink.defaultCardBackground {
}
}
@media all and (min-width: 62.5em) {
.headerTop {
padding-left: 0.8em;
padding-right: 0.8em;
}
.headerTabs {
align-self: center;
width: auto;
align-items: center;
justify-content: center;
margin-top: -4.2em;
position: relative;
}
@media all and (min-width: 100em) {
.detailFloatingButton {
display: none !important;
}
@ -868,6 +952,10 @@ div.itemDetailGalleryLink.defaultCardBackground {
}
}
.detailVerticalSection .emby-scrollbuttons {
padding-top: 0.4em;
}
.layout-tv .detailVerticalSection {
margin-bottom: 3.4em !important;
}
@ -956,6 +1044,10 @@ div.itemDetailGalleryLink.defaultCardBackground {
margin-bottom: 2.7em;
}
.layout-mobile .verticalSection-extrabottompadding {
margin-bottom: 1em;
}
.sectionTitleButton,
.sectionTitleIconButton {
margin-right: 0 !important;
@ -981,7 +1073,13 @@ div.itemDetailGalleryLink.defaultCardBackground {
div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
margin: 0;
padding-top: 1.25em;
padding-top: 0.5em;
padding-bottom: 0.2em;
}
.layout-mobile :not(.sectionTitleContainer-cards) > .sectionTitle-cards {
margin: 0;
padding-top: 0.5em;
}
.sectionTitleButton {
@ -1134,6 +1232,12 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
.trackSelections .selectContainer .selectLabel {
margin: 0 0.2em 0 0;
line-height: 1.75;
}
.layout-mobile .detailsGroupItem .label,
.layout-mobile .trackSelections .selectContainer .selectLabel {
flex-basis: 4.5em;
}
.trackSelections .selectContainer .detailTrackSelect {

View file

@ -12,6 +12,7 @@
.hiddenScrollX,
.layout-tv .scrollX {
-ms-overflow-style: none;
scrollbar-width: none;
}
.hiddenScrollX-forced {
@ -40,6 +41,7 @@
.hiddenScrollY,
.layout-tv .smoothScrollY {
-ms-overflow-style: none;
scrollbar-width: none;
/* Can't do this because it not only hides the scrollbar, but also prevents scrolling */

View file

@ -5,6 +5,12 @@ html {
height: 100%;
}
.layout-mobile,
.layout-tv {
-webkit-touch-callout: none;
user-select: none;
}
.clipForScreenReader {
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
@ -18,7 +24,7 @@ html {
.material-icons {
/* Fix font ligatures on older WebOS versions */
-webkit-font-feature-settings: "liga";
font-feature-settings: "liga";
}
.backgroundContainer {
@ -34,16 +40,6 @@ html {
line-height: 1.35;
}
.layout-mobile,
.layout-tv {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
body {
overflow-x: hidden;
background-color: transparent !important;
@ -133,3 +129,7 @@ div[data-role=page] {
.hide-scroll {
overflow-y: hidden;
}
.w-100 {
width: 100%;
}

View file

@ -4,12 +4,6 @@
// Use define from require.js not webpack's define
var _define = window.define;
// document-register-element
var docRegister = require('document-register-element');
_define('document-register-element', function() {
return docRegister;
});
// fetch
var fetch = require('whatwg-fetch');
_define('fetch', function() {
@ -65,12 +59,6 @@ _define('resize-observer-polyfill', function() {
return resize;
});
// shaka
var shaka = require('shaka-player');
_define('shaka', function() {
return shaka;
});
// swiper
var swiper = require('swiper/js/swiper');
require('swiper/css/swiper.min.css');
@ -90,6 +78,12 @@ _define('webcomponents', function() {
return webcomponents;
});
// shaka
var shaka = require('shaka-player');
_define('shaka', function() {
return shaka;
});
// libass-wasm
var libassWasm = require('libass-wasm');
_define('JavascriptSubtitlesOctopus', function() {

View file

@ -16,7 +16,7 @@ function getOffsets(elems) {
return results;
}
for (let elem of elems) {
for (const elem of elems) {
let box = elem.getBoundingClientRect();
results.push({
@ -135,7 +135,7 @@ export function show(options) {
let renderIcon = false;
let icons = [];
let itemIcon;
for (let item of options.items) {
for (const item of options.items) {
itemIcon = item.icon || (item.selected ? 'check' : null);

View file

@ -222,46 +222,13 @@ define(['loading', 'globalize', 'events', 'viewManager', 'skinManager', 'backdro
}
function normalizeImageOptions(options) {
var scaleFactor = browser.tv ? 0.8 : 1;
var setQuality;
if (options.maxWidth) {
options.maxWidth = Math.round(options.maxWidth * scaleFactor);
if (options.maxWidth || options.width || options.maxHeight || options.height) {
setQuality = true;
}
if (options.width) {
options.width = Math.round(options.width * scaleFactor);
setQuality = true;
}
if (options.maxHeight) {
options.maxHeight = Math.round(options.maxHeight * scaleFactor);
setQuality = true;
}
if (options.height) {
options.height = Math.round(options.height * scaleFactor);
setQuality = true;
}
if (setQuality) {
var quality;
var type = options.type || 'Primary';
if (browser.tv || browser.slow) {
// TODO: wtf
if (browser.chrome) {
// webp support
quality = type === 'Primary' ? 40 : 50;
} else {
quality = type === 'Backdrop' ? 60 : 50;
}
} else {
quality = type === 'Backdrop' ? 70 : 90;
}
options.quality = quality;
if (setQuality && !options.quality) {
options.quality = 90;
}
}

View file

@ -167,8 +167,9 @@ button::-moz-focus-inner {
position: relative;
background-clip: content-box !important;
color: inherit;
}
/* This is only needed for scalable cards */
.cardScalable .cardImageContainer {
height: 100%;
contain: strict;
}

View file

@ -1310,7 +1310,7 @@ import 'programStyles';
}
const mediaSourceCount = item.MediaSourceCount || 1;
if (mediaSourceCount > 1) {
if (mediaSourceCount > 1 && options.disableIndicators !== true) {
innerCardFooter += '<div class="mediaSourceIndicator">' + mediaSourceCount + '</div>';
}
@ -1391,34 +1391,44 @@ import 'programStyles';
cardBoxClose = '</div>';
cardScalableClose = '</div>';
let indicatorsHtml = '';
if (options.disableIndicators !== true) {
let indicatorsHtml = '';
if (options.missingIndicator !== false) {
indicatorsHtml += indicators.getMissingIndicator(item);
}
if (options.missingIndicator !== false) {
indicatorsHtml += indicators.getMissingIndicator(item);
}
indicatorsHtml += indicators.getSyncIndicator(item);
indicatorsHtml += indicators.getTimerIndicator(item);
indicatorsHtml += indicators.getSyncIndicator(item);
indicatorsHtml += indicators.getTimerIndicator(item);
indicatorsHtml += indicators.getTypeIndicator(item);
indicatorsHtml += indicators.getTypeIndicator(item);
if (options.showGroupCount) {
if (options.showGroupCount) {
indicatorsHtml += indicators.getChildCountIndicatorHtml(item, {
minCount: 1
});
} else {
indicatorsHtml += indicators.getPlayedIndicatorHtml(item);
}
indicatorsHtml += indicators.getChildCountIndicatorHtml(item, {
minCount: 1
});
} else {
indicatorsHtml += indicators.getPlayedIndicatorHtml(item);
}
<<<<<<< HEAD
if (item.Type === 'CollectionFolder' || item.CollectionType) {
const refreshClass = item.RefreshProgress ? '' : ' class="hide"';
indicatorsHtml += '<div is="emby-itemrefreshindicator"' + refreshClass + ' data-progress="' + (item.RefreshProgress || 0) + '" data-status="' + item.RefreshStatus + '"></div>';
importRefreshIndicator();
}
=======
if (item.Type === 'CollectionFolder' || item.CollectionType) {
const refreshClass = item.RefreshProgress ? '' : ' class="hide"';
indicatorsHtml += '<div is="emby-itemrefreshindicator"' + refreshClass + ' data-progress="' + (item.RefreshProgress || 0) + '" data-status="' + item.RefreshStatus + '"></div>';
requireRefreshIndicator();
}
>>>>>>> upstream/master
if (indicatorsHtml) {
cardImageContainerOpen += '<div class="cardIndicators">' + indicatorsHtml + '</div>';
if (indicatorsHtml) {
cardImageContainerOpen += '<div class="cardIndicators">' + indicatorsHtml + '</div>';
}
}
if (!imgUrl) {
@ -1466,8 +1476,8 @@ import 'programStyles';
let additionalCardContent = '';
if (layoutManager.desktop) {
additionalCardContent += getHoverMenuHtml(item, action);
if (layoutManager.desktop && !options.disableHoverMenu) {
additionalCardContent += getHoverMenuHtml(item, action, options);
}
return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + ' data-prefix="' + prefix + '" class="' + className + '">' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + '</' + tagName + '>';
@ -1477,9 +1487,10 @@ import 'programStyles';
* Generates HTML markup for the card overlay.
* @param {object} item - Item used to generate the card overlay.
* @param {string} action - Action assigned to the overlay.
* @param {Array} options - Card builder options.
* @returns {string} HTML markup of the card overlay.
*/
function getHoverMenuHtml(item, action) {
function getHoverMenuHtml(item, action, options) {
let html = '';
html += '<div class="cardOverlayContainer itemAction" data-action="' + action + '">';
@ -1508,7 +1519,6 @@ import 'programStyles';
}
html += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover more_vert"></span></button>';
html += '</div>';
html += '</div>';
@ -1532,6 +1542,8 @@ import 'programStyles';
case 'MusicArtist':
case 'Person':
return '<span class="cardImageIcon material-icons person"></span>';
case 'Audio':
return '<span class="cardImageIcon material-icons audiotrack"></span>';
case 'Movie':
return '<span class="cardImageIcon material-icons movie"></span>';
case 'Series':
@ -1540,6 +1552,12 @@ import 'programStyles';
return '<span class="cardImageIcon material-icons book"></span>';
case 'Folder':
return '<span class="cardImageIcon material-icons folder"></span>';
case 'BoxSet':
return '<span class="cardImageIcon material-icons collections"></span>';
case 'Playlist':
return '<span class="cardImageIcon material-icons view_list"></span>';
case 'PhotoAlbum':
return '<span class="cardImageIcon material-icons photo_album"></span>';
}
if (options && options.defaultCardImageIcon) {

View file

@ -25,7 +25,7 @@ import connectionManager from 'connectionManager';
return void appRouter.showItem(items[0]);
}
var url = 'itemdetails.html?id=' + itemId + '&serverId=' + serverId;
var url = 'details?id=' + itemId + '&serverId=' + serverId;
Dashboard.navigate(url);
});
e.stopPropagation();

View file

@ -1,4 +1,4 @@
define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', 'scrollHelper', 'serverNotifications', 'loading', 'datetime', 'focusManager', 'playbackManager', 'userSettings', 'imageLoader', 'events', 'layoutManager', 'itemShortcuts', 'dom', 'css!./guide.css', 'programStyles', 'material-icons', 'scrollStyles', 'emby-programcell', 'emby-button', 'paper-icon-button-light', 'emby-tabs', 'emby-scroller', 'flexStyles', 'registerElement'], function (require, inputManager, browser, globalize, connectionManager, scrollHelper, serverNotifications, loading, datetime, focusManager, playbackManager, userSettings, imageLoader, events, layoutManager, itemShortcuts, dom) {
define(['require', 'inputManager', 'browser', 'globalize', 'connectionManager', 'scrollHelper', 'serverNotifications', 'loading', 'datetime', 'focusManager', 'playbackManager', 'userSettings', 'imageLoader', 'events', 'layoutManager', 'itemShortcuts', 'dom', 'css!./guide.css', 'programStyles', 'material-icons', 'scrollStyles', 'emby-programcell', 'emby-button', 'paper-icon-button-light', 'emby-tabs', 'emby-scroller', 'flexStyles', 'webcomponents'], function (require, inputManager, browser, globalize, connectionManager, scrollHelper, serverNotifications, loading, datetime, focusManager, playbackManager, userSettings, imageLoader, events, layoutManager, itemShortcuts, dom) {
'use strict';
function showViewSettings(instance) {

View file

@ -12,7 +12,7 @@ import 'css!./style';
fillImageElement(elem, source);
}
async function itemBlurhashing(target, blurhashstr) {
function itemBlurhashing(target, blurhashstr) {
if (blurhash.isBlurhashValid(blurhashstr)) {
// Although the default values recommended by Blurhash developers is 32x32, a size of 18x18 seems to be the sweet spot for us,
// improving the performance and reducing the memory usage, while retaining almost full blur quality.
@ -36,24 +36,18 @@ import 'css!./style';
imgData.data.set(pixels);
ctx.putImageData(imgData, 0, 0);
let child = target.appendChild(canvas);
child.classList.add('blurhash-canvas');
child.style.opacity = 1;
if (userSettings.enableFastFadein()) {
child.classList.add('lazy-blurhash-fadein-fast');
} else {
child.classList.add('lazy-blurhash-fadein');
}
requestAnimationFrame(() => {
canvas.classList.add('blurhash-canvas');
if (userSettings.enableFastFadein()) {
canvas.classList.add('lazy-blurhash-fadein-fast');
} else {
canvas.classList.add('lazy-blurhash-fadein');
}
target.classList.add('blurhashed');
target.removeAttribute('data-blurhash');
}
}
function switchCanvas(elem) {
let child = elem.getElementsByClassName('blurhash-canvas')[0];
if (child) {
child.style.opacity = elem.getAttribute('data-src') ? 1 : 0;
target.parentNode.insertBefore(canvas, target);
target.classList.add('blurhashed');
target.removeAttribute('data-blurhash');
});
}
}
@ -66,23 +60,16 @@ import 'css!./style';
if (target) {
source = target.getAttribute('data-src');
var blurhashstr = target.getAttribute('data-blurhash');
} else {
source = entry;
}
if (userSettings.enableBlurhash()) {
if (!target.classList.contains('blurhashed', 'non-blurhashable') && blurhashstr) {
itemBlurhashing(target, blurhashstr);
} else if (!blurhashstr && !target.classList.contains('blurhashed')) {
target.classList.add('non-blurhashable');
}
}
if (entry.intersectionRatio > 0) {
if (source) fillImageElement(target, source);
} else if (!source) {
emptyImageElement(target);
requestAnimationFrame(() => {
emptyImageElement(target);
});
}
}
@ -94,29 +81,24 @@ import 'css!./style';
let preloaderImg = new Image();
preloaderImg.src = url;
// This is necessary here, so changing blurhash settings without reloading the page works
if (!userSettings.enableBlurhash() || elem.classList.contains('non-blurhashable')) {
elem.classList.add('lazy-hidden');
}
elem.classList.add('lazy-hidden');
preloaderImg.addEventListener('load', () => {
if (elem.tagName !== 'IMG') {
elem.style.backgroundImage = "url('" + url + "')";
} else {
elem.setAttribute('src', url);
}
elem.removeAttribute('data-src');
requestAnimationFrame(() => {
if (elem.tagName !== 'IMG') {
elem.style.backgroundImage = "url('" + url + "')";
} else {
elem.setAttribute('src', url);
}
elem.removeAttribute('data-src');
if (elem.classList.contains('non-blurhashable') || !userSettings.enableBlurhash()) {
elem.classList.remove('lazy-hidden');
if (userSettings.enableFastFadein()) {
elem.classList.add('lazy-image-fadein-fast');
} else {
elem.classList.add('lazy-image-fadein');
}
} else {
switchCanvas(elem);
}
});
});
}
@ -132,15 +114,21 @@ import 'css!./style';
}
elem.setAttribute('data-src', url);
if (elem.classList.contains('non-blurhashable') || !userSettings.enableBlurhash()) {
elem.classList.remove('lazy-image-fadein-fast', 'lazy-image-fadein');
elem.classList.add('lazy-hidden');
} else {
switchCanvas(elem);
}
elem.classList.remove('lazy-image-fadein-fast', 'lazy-image-fadein');
elem.classList.add('lazy-hidden');
}
export function lazyChildren(elem) {
if (userSettings.enableBlurhash()) {
for (const lazyElem of elem.querySelectorAll('.lazy')) {
const blurhashstr = lazyElem.getAttribute('data-blurhash');
if (!lazyElem.classList.contains('blurhashed', 'non-blurhashable') && blurhashstr) {
itemBlurhashing(lazyElem, blurhashstr);
} else if (!blurhashstr && !lazyElem.classList.contains('blurhashed')) {
lazyElem.classList.add('non-blurhashable');
}
}
}
lazyLoader.lazyChildren(elem, fillImage);
}
@ -212,8 +200,15 @@ import 'css!./style';
}
}
export function setLazyImage(element, url) {
element.classList.add('lazy');
element.setAttribute('data-src', url);
lazyImage(element);
}
/* eslint-enable indent */
export default {
serLazyImage: setLazyImage,
fillImages: fillImages,
fillImage: fillImage,
lazyImage: lazyImage,

View file

@ -12,12 +12,22 @@
opacity: 0;
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.lazy-blurhash-fadein-fast {
transition: opacity 0.2s;
animation: fadein 0.1s;
}
.lazy-blurhash-fadein {
transition: opacity 0.7s;
animation: fadein 0.4s;
}
.blurhash-canvas {
@ -28,6 +38,5 @@
left: 0;
width: 100%;
height: 100%;
z-index: 100;
pointer-events: none;
}

View file

@ -38,6 +38,23 @@ import actionsheet from 'actionsheet';
}
}
if (playbackManager.getCurrentPlayer() !== null) {
if (options.stopPlayback) {
commands.push({
name: globalize.translate('StopPlayback'),
id: 'stopPlayback',
icon: 'stop'
});
}
if (options.clearQueue) {
commands.push({
name: globalize.translate('ClearQueue'),
id: 'clearQueue',
icon: 'clear_all'
});
}
}
if (playbackManager.canQueue(item)) {
if (options.queue !== false) {
commands.push({
@ -54,13 +71,6 @@ import actionsheet from 'actionsheet';
icon: 'playlist_add'
});
}
//if (options.queueAllFromHere) {
// commands.push({
// name: globalize.translate("QueueAllFromHere"),
// id: "queueallfromhere"
// });
//}
}
if (item.IsFolder || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') {
@ -298,10 +308,11 @@ import actionsheet from 'actionsheet';
icon: 'album'
});
}
if (options.openArtist !== false && item.ArtistItems && item.ArtistItems.length) {
// Show Album Artist by default, as a song can have multiple artists, which specific one would this option refer to?
// Although some albums can have multiple artists, it's not as common as songs.
if (options.openArtist !== false && item.AlbumArtists && item.AlbumArtists.length) {
commands.push({
name: globalize.translate('ViewArtist'),
name: globalize.translate('ViewAlbumArtist'),
id: 'artist',
icon: 'person'
});
@ -441,6 +452,12 @@ import actionsheet from 'actionsheet';
play(item, false, true, true);
getResolveFunction(resolve, id)();
break;
case 'stopPlayback':
playbackManager.stop();
break;
case 'clearQueue':
playbackManager.clearQueue();
break;
case 'record':
import('recordingCreator').then(({default: recordingCreator}) => {
recordingCreator.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id));
@ -469,7 +486,7 @@ import actionsheet from 'actionsheet';
getResolveFunction(resolve, id)();
break;
case 'artist':
appRouter.showItem(item.ArtistItems[0].Id, item.ServerId);
appRouter.showItem(item.AlbumArtists[0].Id, item.ServerId);
getResolveFunction(resolve, id)();
break;
case 'playallfromhere':

View file

@ -1,14 +1,12 @@
define(['apphost', 'globalize'], function (appHost, globalize) {
'use strict';
function getDisplayName(item, options) {
function getDisplayName(item, options = {}) {
if (!item) {
throw new Error('null item passed into getDisplayName');
}
options = options || {};
if (item.Type === 'Timer') {
item = item.ProgramInfo || item;
}

View file

@ -240,13 +240,19 @@ import 'cardStyle';
html += '</div>';
html += '</div>';
<<<<<<< HEAD
let numLines = 2;
=======
var numLines = 3;
>>>>>>> upstream/master
if (currentItemType === 'MusicAlbum') {
numLines++;
}
const lines = [result.Name];
lines.push(result.SearchProviderName);
if (result.AlbumArtist) {
lines.push(result.AlbumArtist.Name);
}

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
/* eslint-disable indent */
/**
@ -15,6 +16,10 @@ import datetime from 'datetime';
import 'css!./listview';
import 'emby-ratingbutton';
import 'emby-playstatebutton';
=======
define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutManager', 'globalize', 'datetime', 'cardBuilder', 'css!./listview', 'emby-ratingbutton', 'emby-playstatebutton'], function (itemHelper, mediaInfo, indicators, connectionManager, layoutManager, globalize, datetime, cardBuilder) {
'use strict';
>>>>>>> upstream/master
function getIndex(item, options) {
@ -106,11 +111,8 @@ import 'emby-playstatebutton';
itemId = item.ParentPrimaryImageItemId;
}
let blurHashes = item.ImageBlurHashes || {};
let blurhashstr = (blurHashes[options.type] || {})[options.tag];
if (itemId) {
return { url: apiClient.getScaledImageUrl(itemId, options), blurhash: blurhashstr };
return apiClient.getScaledImageUrl(itemId, options);
}
return null;
}
@ -126,24 +128,30 @@ import 'emby-playstatebutton';
if (item.ChannelId && item.ChannelPrimaryImageTag) {
options.tag = item.ChannelPrimaryImageTag;
}
let blurHashes = item.ImageBlurHashes || {};
let blurhashstr = (blurHashes[options.type])[options.tag];
if (item.ChannelId) {
return { url: apiClient.getScaledImageUrl(item.ChannelId, options), blurhash: blurhashstr };
return apiClient.getScaledImageUrl(item.ChannelId, options);
}
}
function getTextLinesHtml(textlines, isLargeStyle) {
<<<<<<< HEAD
let html = '';
=======
var html = '';
>>>>>>> upstream/master
const largeTitleTagName = layoutManager.tv ? 'h2' : 'div';
<<<<<<< HEAD
for (let i = 0, length = textlines.length; i < length; i++) {
const text = textlines[i];
=======
for (const [i, text] of textlines.entries()) {
>>>>>>> upstream/master
if (!text) {
continue;
}
@ -284,10 +292,8 @@ import 'emby-playstatebutton';
}
if (options.image !== false) {
let imgData = options.imageSource === 'channel' ? getChannelImageUrl(item, downloadWidth) : getImageUrl(item, downloadWidth);
let imgUrl = imgData.url;
let blurhash = imgData.blurhash;
let imageClass = isLargeStyle ? 'listItemImage listItemImage-large' : 'listItemImage';
var imgUrl = options.imageSource === 'channel' ? getChannelImageUrl(item, downloadWidth) : getImageUrl(item, downloadWidth);
var imageClass = isLargeStyle ? 'listItemImage listItemImage-large' : 'listItemImage';
if (isLargeStyle && layoutManager.tv) {
imageClass += ' listItemImage-large-tv';
@ -299,6 +305,7 @@ import 'emby-playstatebutton';
imageClass += ' itemAction';
}
<<<<<<< HEAD
const imageAction = playOnImageClick ? 'resume' : action;
let blurhashAttrib = '';
@ -310,6 +317,14 @@ import 'emby-playstatebutton';
html += `<div data-action="${imageAction}" class="${imageClass} lazy" data-src="${imgUrl}" ${blurhashAttrib} item-icon>`;
} else {
html += `<div class="${imageClass}">`;
=======
var imageAction = playOnImageClick ? 'link' : action;
if (imgUrl) {
html += '<div data-action="' + imageAction + '" class="' + imageClass + ' lazy" data-src="' + imgUrl + '" item-icon>';
} else {
html += '<div class="' + imageClass + ' cardImageContainer ' + cardBuilder.getDefaultBackgroundClass(item.Name) + '">' + cardBuilder.getDefaultText(item, options);
>>>>>>> upstream/master
}
let indicatorsHtml = '';
@ -449,8 +464,6 @@ import 'emby-playstatebutton';
html += `<div class="${cssClass}">`;
const moreIcon = 'more_vert';
html += getTextLinesHtml(textlines, isLargeStyle);
if (options.mediaInfo !== false) {
@ -505,10 +518,13 @@ import 'emby-playstatebutton';
html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="addtoplaylist"><span class="material-icons playlist_add"></span></button>';
}
<<<<<<< HEAD
if (options.moreButton !== false) {
html += `<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="menu"><span class="material-icons ${moreIcon}"></span></button>`;
}
=======
>>>>>>> upstream/master
if (options.infoButton) {
html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="link"><span class="material-icons info_outline"></span></button>';
}
@ -522,13 +538,26 @@ import 'emby-playstatebutton';
const userData = item.UserData || {};
const likes = userData.Likes == null ? '' : userData.Likes;
<<<<<<< HEAD
if (itemHelper.canMarkPlayed(item)) {
html += `<button is="emby-playstatebutton" type="button" class="listItemButton paper-icon-button-light" data-id="${item.Id}" data-serverid="${item.ServerId}" data-itemtype="${item.Type}" data-played="${userData.Played}"><span class="material-icons check"></span></button>`;
}
if (itemHelper.canRate(item)) {
html += `<button is="emby-ratingbutton" type="button" class="listItemButton paper-icon-button-light" data-id="${item.Id}" data-serverid="${item.ServerId}" data-itemtype="${item.Type}" data-likes="${likes}" data-isfavorite="${userData.IsFavorite}"><span class="material-icons favorite"></span></button>`;
=======
if (itemHelper.canMarkPlayed(item) && options.enablePlayedButton !== false) {
html += '<button is="emby-playstatebutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-played="' + (userData.Played) + '"><span class="material-icons check"></span></button>';
}
if (itemHelper.canRate(item) && options.enableRatingButton !== false) {
html += '<button is="emby-ratingbutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons favorite"></span></button>';
>>>>>>> upstream/master
}
}
if (options.moreButton !== false) {
html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="menu"><span class="material-icons more_vert"></span></button>';
}
}
html += '</div>';

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,10 +1,6 @@
define(['components/loading/loadingLegacy', 'browser', 'css!./loading'], function (loadingLegacy, browser) {
define(['css!./loading'], function () {
'use strict';
if (browser.tizen || browser.operaTv || browser.chromecast || browser.orsay || browser.web0s || browser.ps4) {
return loadingLegacy;
}
var loadingElem;
var layer1;
var layer2;

View file

@ -1,10 +0,0 @@
.loading-spinner {
margin-top: -3em;
margin-left: -3em;
width: 6em;
height: 6em;
position: fixed;
top: 50%;
left: 50%;
z-index: 9999999;
}

View file

@ -1,28 +0,0 @@
define(['require', 'css!./loadingLegacy'], function (require) {
'use strict';
var loadingElem;
return {
show: function () {
var elem = loadingElem;
if (!elem) {
elem = document.createElement('img');
elem.src = require.toUrl('.').split('?')[0] + '/loader.gif';
loadingElem = elem;
elem.classList.add('loading-spinner');
document.body.appendChild(elem);
}
elem.classList.remove('hide');
},
hide: function () {
var elem = loadingElem;
if (elem) {
elem.classList.add('hide');
}
}
};
});

View file

@ -41,6 +41,8 @@
width: auto !important;
height: auto !important;
font-size: 1.4em;
margin-right: 0.125em;
color: #f2b01e;
}
.mediaInfoCriticRating {

View file

@ -266,6 +266,43 @@ import 'flexStyles';
});
}
function afterDeleted(context, item) {
var parentId = item.ParentId || item.SeasonId || item.SeriesId;
if (parentId) {
reload(context, parentId, item.ServerId);
} else {
require(['appRouter'], function (appRouter) {
appRouter.goHome();
});
}
}
function showMoreMenu(context, button, user) {
require(['itemContextMenu'], function (itemContextMenu) {
var item = currentItem;
itemContextMenu.show({
item: item,
positionTo: button,
edit: false,
editImages: true,
editSubtitles: true,
sync: false,
share: false,
play: false,
queue: false,
user: user
}).then(function (result) {
if (result.deleted) {
afterDeleted(context, item);
} else if (result.updated) {
reload(context, item.Id, item.ServerId);
}
});
});
}
function onEditorClick(e) {
const btnRemoveFromEditorList = dom.parentWithClass(e.target, 'btnRemoveFromEditorList');
@ -291,7 +328,6 @@ import 'flexStyles';
}
function init(context, apiClient) {
context.querySelector('.externalIds').addEventListener('click', function (e) {
const btnOpenExternalId = dom.parentWithClass(e.target, 'btnOpenExternalId');
if (btnOpenExternalId) {
@ -315,13 +351,17 @@ import 'flexStyles';
closeDialog(false);
});
context.querySelector('.btnHeaderSave').addEventListener('click', function (e) {
context.querySelector('.btnMore').addEventListener('click', function (e) {
getApiClient().getCurrentUser().then(function (user) {
showMoreMenu(context, e.target, user);
});
});
context.querySelector('.btnHeaderSave').addEventListener('click', function (e) {
context.querySelector('.btnSave').click();
});
context.querySelector('#chkLockData').addEventListener('click', function (e) {
if (!e.target.checked) {
showElement('.providerSettingsContainer');
} else {
@ -1107,6 +1147,7 @@ import 'flexStyles';
elem.innerHTML = globalize.translateHtml(template, 'core');
elem.querySelector('.formDialogFooter').classList.remove('formDialogFooter');
elem.querySelector('.btnClose').classList.add('hide');
elem.querySelector('.btnHeaderSave').classList.remove('hide');
elem.querySelector('.btnCancel').classList.add('hide');

View file

@ -8,6 +8,9 @@
<span class="material-icons check"></span>
<span>${Save}</span>
</button>
<button is="paper-icon-button-light" class="btnMore autoSize" tabindex="-1">
<span class="material-icons more_vert"></span>
</button>
<button is="paper-icon-button-light" class="btnCancel btnClose autoSize" tabindex="-1">
<span class="material-icons close"></span>
</button>

View file

@ -56,8 +56,8 @@
text-align: left;
flex-grow: 1;
font-size: 92%;
margin-right: 2.4em;
margin-left: 1em;
margin-right: 1em;
margin-left: 0.5em;
}
.nowPlayingBarCenter {
@ -114,8 +114,6 @@
.nowPlayingBarUserDataButtons {
display: inline-block;
margin-left: 1em;
margin-right: 1em;
}
.nowPlayingBarPositionSlider::-webkit-slider-thumb {
@ -133,33 +131,50 @@
.toggleRepeatButton {
display: none !important;
}
}
@media all and (max-width: 62em) {
.nowPlayingBarCenter .nowPlayingBarCurrentTime {
.nowPlayingBar .btnShuffleQueue {
display: none !important;
}
}
@media all and (max-width: 80em) {
.nowPlayingBarCenter .nowPlayingBarCurrentTime,
.nowPlayingBarCenter .stopButton {
display: none !important;
}
.nowPlayingBarInfoContainer {
width: 45%;
}
}
.layout-mobile .nowPlayingBarRight button:not(.playPauseButton, .nextTrackButton) {
display: none;
}
.layout-desktop .nowPlayingBarRight .playPauseButton,
.layout-tv .nowPlayingBarRight .playPauseButton {
display: none;
}
.layout-mobile .nowPlayingBarRight input,
.layout-mobile .nowPlayingBarRight div {
display: none;
}
@media all and (max-width: 56em) {
.nowPlayingBarCenter {
display: none !important;
}
}
@media all and (min-width: 56em) {
.nowPlayingBarRight .playPauseButton {
display: none;
}
}
@media all and (max-width: 36em) {
@media all and (max-width: 60em) {
.nowPlayingBarRight .nowPlayingBarVolumeSliderContainer {
display: none !important;
}
.nowPlayingBarInfoContainer {
width: 70%;
width: 100%;
}
}

View file

@ -62,7 +62,9 @@ import 'emby-ratingbutton';
html += '<button is="paper-icon-button-light" class="playPauseButton mediaButton"><span class="material-icons pause"></span></button>';
html += '<button is="paper-icon-button-light" class="stopButton mediaButton"><span class="material-icons stop"></span></button>';
html += '<button is="paper-icon-button-light" class="nextTrackButton mediaButton"><span class="material-icons skip_next"></span></button>';
if (!layoutManager.mobile) {
html += '<button is="paper-icon-button-light" class="nextTrackButton mediaButton"><span class="material-icons skip_next"></span></button>';
}
html += '<div class="nowPlayingBarCurrentTime"></div>';
html += '</div>';
@ -76,12 +78,17 @@ import 'emby-ratingbutton';
html += '</div>';
html += '<button is="paper-icon-button-light" class="toggleRepeatButton mediaButton"><span class="material-icons repeat"></span></button>';
html += '<button is="paper-icon-button-light" class="btnShuffleQueue mediaButton"><span class="material-icons shuffle"></span></button>';
html += '<div class="nowPlayingBarUserDataButtons">';
html += '</div>';
html += '<button is="paper-icon-button-light" class="playPauseButton mediaButton"><span class="material-icons pause"></span></button>';
html += '<button is="paper-icon-button-light" class="btnToggleContextMenu"><span class="material-icons more_vert"></span></button>';
if (layoutManager.mobile) {
html += '<button is="paper-icon-button-light" class="nextTrackButton mediaButton"><span class="material-icons skip_next"></span></button>';
} else {
html += '<button is="paper-icon-button-light" class="btnToggleContextMenu mediaButton"><span class="material-icons more_vert"></span></button>';
}
html += '</div>';
html += '</div>';
@ -132,8 +139,13 @@ import 'emby-ratingbutton';
nowPlayingImageElement = elem.querySelector('.nowPlayingImage');
nowPlayingTextElement = elem.querySelector('.nowPlayingBarText');
nowPlayingUserData = elem.querySelector('.nowPlayingBarUserDataButtons');
positionSlider = elem.querySelector('.nowPlayingBarPositionSlider');
muteButton = elem.querySelector('.muteButton');
playPauseButtons = elem.querySelectorAll('.playPauseButton');
toggleRepeatButton = elem.querySelector('.toggleRepeatButton');
volumeSlider = elem.querySelector('.nowPlayingBarVolumeSlider');
volumeSliderContainer = elem.querySelector('.nowPlayingBarVolumeSliderContainer');
muteButton.addEventListener('click', function () {
if (currentPlayer) {
@ -149,7 +161,6 @@ import 'emby-ratingbutton';
}
});
playPauseButtons = elem.querySelectorAll('.playPauseButton');
playPauseButtons.forEach((button) => {
button.addEventListener('click', onPlayPauseClick);
});
@ -161,54 +172,59 @@ import 'emby-ratingbutton';
}
});
elem.querySelector('.previousTrackButton').addEventListener('click', function () {
elem.querySelector('.previousTrackButton').addEventListener('click', function (e) {
if (currentPlayer) {
if (lastPlayerState.NowPlayingItem.MediaType === 'Audio' && (currentPlayer._currentTime >= 5 || !playbackManager.previousTrack(currentPlayer))) {
// Cancel this event if doubleclick is fired
if (e.detail > 1 && playbackManager.previousTrack(currentPlayer)) {
return;
}
playbackManager.seekPercent(0, currentPlayer);
// This is done automatically by playbackManager, however, setting this here gives instant visual feedback.
// TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround.
positionSlider.value = 0;
} else {
playbackManager.previousTrack(currentPlayer);
}
}
});
elem.querySelector('.previousTrackButton').addEventListener('dblclick', function () {
if (currentPlayer) {
playbackManager.previousTrack(currentPlayer);
}
});
elem.querySelector('.btnShuffleQueue').addEventListener('click', function () {
if (currentPlayer) {
playbackManager.toggleQueueShuffleMode();
}
});
toggleRepeatButton = elem.querySelector('.toggleRepeatButton');
toggleRepeatButton.addEventListener('click', function () {
if (currentPlayer) {
switch (playbackManager.getRepeatMode(currentPlayer)) {
case 'RepeatAll':
playbackManager.setRepeatMode('RepeatOne', currentPlayer);
break;
case 'RepeatOne':
playbackManager.setRepeatMode('RepeatNone', currentPlayer);
break;
default:
playbackManager.setRepeatMode('RepeatAll', currentPlayer);
break;
}
switch (playbackManager.getRepeatMode()) {
case 'RepeatAll':
playbackManager.setRepeatMode('RepeatOne');
break;
case 'RepeatOne':
playbackManager.setRepeatMode('RepeatNone');
break;
case 'RepeatNone':
playbackManager.setRepeatMode('RepeatAll');
}
});
toggleRepeatButtonIcon = toggleRepeatButton.querySelector('.material-icons');
volumeSlider = elem.querySelector('.nowPlayingBarVolumeSlider');
volumeSliderContainer = elem.querySelector('.nowPlayingBarVolumeSliderContainer');
volumeSliderContainer.classList.toggle('hide', appHost.supports('physicalvolumecontrol'));
if (appHost.supports('physicalvolumecontrol')) {
volumeSliderContainer.classList.add('hide');
} else {
volumeSliderContainer.classList.remove('hide');
}
function setVolume() {
volumeSlider.addEventListener('input', (e) => {
if (currentPlayer) {
currentPlayer.setVolume(this.value);
currentPlayer.setVolume(e.target.value);
}
}
});
volumeSlider.addEventListener('change', setVolume);
volumeSlider.addEventListener('mousemove', setVolume);
volumeSlider.addEventListener('touchmove', setVolume);
positionSlider = elem.querySelector('.nowPlayingBarPositionSlider');
positionSlider.addEventListener('change', function () {
if (currentPlayer) {
@ -277,6 +293,11 @@ import 'emby-ratingbutton';
parentContainer.insertAdjacentHTML('afterbegin', getNowPlayingBarHtml());
nowPlayingBarElement = parentContainer.querySelector('.nowPlayingBar');
if (layoutManager.mobile) {
hideButton(nowPlayingBarElement.querySelector('.btnShuffleQueue'));
hideButton(nowPlayingBarElement.querySelector('.nowPlayingBarCenter'));
}
if (browser.safari && browser.slow) {
// Not handled well here. The wrong elements receive events, bar doesn't update quickly enough, etc.
nowPlayingBarElement.classList.add('noMediaProgress');
@ -329,7 +350,8 @@ import 'emby-ratingbutton';
toggleRepeatButton.classList.remove('hide');
}
updateRepeatModeDisplay(playState.RepeatMode);
updateRepeatModeDisplay(playbackManager.getRepeatMode());
onQueueShuffleModeChange();
updatePlayerVolumeState(playState.IsMuted, playState.VolumeLevel);
@ -349,32 +371,39 @@ import 'emby-ratingbutton';
function updateRepeatModeDisplay(repeatMode) {
toggleRepeatButtonIcon.classList.remove('repeat', 'repeat_one');
const cssClass = 'buttonActive';
if (repeatMode === 'RepeatAll') {
toggleRepeatButtonIcon.classList.add('repeat');
toggleRepeatButton.classList.add('repeatButton-active');
} else if (repeatMode === 'RepeatOne') {
toggleRepeatButtonIcon.classList.add('repeat_one');
toggleRepeatButton.classList.add('repeatButton-active');
} else {
toggleRepeatButtonIcon.classList.add('repeat');
toggleRepeatButton.classList.remove('repeatButton-active');
switch (repeatMode) {
case 'RepeatAll':
toggleRepeatButtonIcon.classList.add('repeat');
toggleRepeatButton.classList.add(cssClass);
break;
case 'RepeatOne':
toggleRepeatButtonIcon.classList.add('repeat_one');
toggleRepeatButton.classList.add(cssClass);
break;
case 'RepeatNone':
default:
toggleRepeatButtonIcon.classList.add('repeat');
toggleRepeatButton.classList.remove(cssClass);
break;
}
}
function updateTimeDisplay(positionTicks, runtimeTicks, bufferedRanges) {
// See bindEvents for why this is necessary
if (positionSlider && !positionSlider.dragging) {
if (runtimeTicks) {
<<<<<<< HEAD
let pct = positionTicks / runtimeTicks;
=======
var pct = positionTicks / runtimeTicks;
>>>>>>> upstream/master
pct *= 100;
positionSlider.value = pct;
} else {
positionSlider.value = 0;
}
}
@ -384,9 +413,13 @@ import 'emby-ratingbutton';
}
if (currentTimeElement) {
<<<<<<< HEAD
let timeText = positionTicks == null ? '--:--' : datetime.getDisplayRunningTime(positionTicks);
=======
var timeText = positionTicks == null ? '--:--' : datetime.getDisplayRunningTime(positionTicks);
>>>>>>> upstream/master
if (runtimeTicks) {
timeText += ' / ' + datetime.getDisplayRunningTime(runtimeTicks);
}
@ -428,11 +461,7 @@ import 'emby-ratingbutton';
// See bindEvents for why this is necessary
if (volumeSlider) {
if (showVolumeSlider) {
volumeSliderContainer.classList.remove('hide');
} else {
volumeSliderContainer.classList.add('hide');
}
volumeSliderContainer.classList.toggle('hide', !showVolumeSlider);
if (!volumeSlider.dragging) {
volumeSlider.value = volumeLevel || 0;
@ -440,15 +469,6 @@ import 'emby-ratingbutton';
}
}
function getTextActionButton(item, text) {
if (!text) {
text = itemHelper.getDisplayName(item);
}
return `<a>${text}</a>`;
}
function seriesImageUrl(item, options) {
if (!item) {
@ -520,6 +540,7 @@ import 'emby-ratingbutton';
const nowPlayingItem = state.NowPlayingItem;
<<<<<<< HEAD
const textLines = nowPlayingItem ? nowPlayingHelper.getNowPlayingNames(nowPlayingItem) : [];
if (textLines.length > 1) {
textLines[1].secondary = true;
@ -536,6 +557,31 @@ import 'emby-ratingbutton';
return `<div ${cssClass}>${nowPlayingText}</div>`;
}).join('');
=======
var textLines = nowPlayingItem ? nowPlayingHelper.getNowPlayingNames(nowPlayingItem) : [];
nowPlayingTextElement.innerHTML = '';
if (textLines) {
let itemText = document.createElement('div');
let secondaryText = document.createElement('div');
secondaryText.classList.add('nowPlayingBarSecondaryText');
if (textLines.length > 1) {
textLines[1].secondary = true;
if (textLines[1].text) {
let text = document.createElement('a');
text.innerHTML = textLines[1].text;
secondaryText.appendChild(text);
}
}
if (textLines[0].text) {
let text = document.createElement('a');
text.innerHTML = textLines[0].text;
itemText.appendChild(text);
}
nowPlayingTextElement.appendChild(itemText);
nowPlayingTextElement.appendChild(secondaryText);
}
>>>>>>> upstream/master
const imgHeight = 70;
@ -553,8 +599,12 @@ import 'emby-ratingbutton';
if (url) {
imageLoader.lazyImage(nowPlayingImageElement, url);
nowPlayingImageElement.style.display = null;
nowPlayingTextElement.style.marginLeft = null;
} else {
nowPlayingImageElement.style.backgroundImage = '';
nowPlayingImageElement.style.display = 'none';
nowPlayingTextElement.style.marginLeft = '1em';
}
}
@ -563,6 +613,7 @@ import 'emby-ratingbutton';
const apiClient = connectionManager.getApiClient(nowPlayingItem.ServerId);
apiClient.getItem(apiClient.getCurrentUserId(), nowPlayingItem.Id).then(function (item) {
<<<<<<< HEAD
const userData = item.UserData || {};
const likes = userData.Likes == null ? '' : userData.Likes;
const contextButton = document.querySelector('.btnToggleContextMenu');
@ -578,8 +629,32 @@ import 'emby-ratingbutton';
item: item,
user: user
}, options ));
=======
var userData = item.UserData || {};
var likes = userData.Likes == null ? '' : userData.Likes;
if (!layoutManager.mobile) {
let contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu');
// We remove the previous event listener by replacing the item in each update event
let contextButtonClone = contextButton.cloneNode(true);
contextButton.parentNode.replaceChild(contextButtonClone, contextButton);
contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu');
let options = {
play: false,
queue: false,
clearQueue: true,
positionTo: contextButton
};
apiClient.getCurrentUser().then(function (user) {
contextButton.addEventListener('click', function () {
itemContextMenu.show(Object.assign({
item: item,
user: user
}, options));
});
>>>>>>> upstream/master
});
});
}
nowPlayingUserData.innerHTML = '<button is="emby-ratingbutton" type="button" class="listItemButton mediaButton paper-icon-button-light" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons favorite"></span></button>';
});
}
} else {
@ -589,25 +664,49 @@ import 'emby-ratingbutton';
function onPlaybackStart(e, state) {
console.debug('nowplaying event: ' + e.type);
<<<<<<< HEAD
const player = this;
=======
var player = this;
>>>>>>> upstream/master
onStateChanged.call(player, e, state);
}
function onRepeatModeChange(e) {
function onRepeatModeChange() {
if (!isEnabled) {
return;
}
<<<<<<< HEAD
const player = this;
=======
updateRepeatModeDisplay(playbackManager.getRepeatMode());
}
updateRepeatModeDisplay(playbackManager.getRepeatMode(player));
function onQueueShuffleModeChange() {
if (!isEnabled) {
return;
}
>>>>>>> upstream/master
let shuffleMode = playbackManager.getQueueShuffleMode();
let context = nowPlayingBarElement;
const cssClass = 'buttonActive';
let toggleShuffleButton = context.querySelector('.btnShuffleQueue');
switch (shuffleMode) {
case 'Shuffle':
toggleShuffleButton.classList.add(cssClass);
break;
case 'Sorted':
default:
toggleShuffleButton.classList.remove(cssClass);
break;
}
}
function showNowPlayingBar() {
if (!isVisibilityAllowed) {
hideNowPlayingBar();
return;
@ -711,6 +810,7 @@ import 'emby-ratingbutton';
events.off(player, 'playbackstart', onPlaybackStart);
events.off(player, 'statechange', onPlaybackStart);
events.off(player, 'repeatmodechange', onRepeatModeChange);
events.off(player, 'shufflequeuemodechange', onQueueShuffleModeChange);
events.off(player, 'playbackstop', onPlaybackStopped);
events.off(player, 'volumechange', onVolumeChanged);
events.off(player, 'pause', onPlayPauseStateChanged);
@ -759,6 +859,7 @@ import 'emby-ratingbutton';
events.on(player, 'playbackstart', onPlaybackStart);
events.on(player, 'statechange', onPlaybackStart);
events.on(player, 'repeatmodechange', onRepeatModeChange);
events.on(player, 'shufflequeuemodechange', onQueueShuffleModeChange);
events.on(player, 'playbackstop', onPlaybackStopped);
events.on(player, 'volumechange', onVolumeChanged);
events.on(player, 'pause', onPlayPauseStateChanged);

View file

@ -1,9 +1,6 @@
define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'playQueueManager', 'userSettings', 'globalize', 'connectionManager', 'loading', 'apphost', 'screenfull'], function (events, datetime, appSettings, itemHelper, pluginManager, PlayQueueManager, userSettings, globalize, connectionManager, loading, apphost, screenfull) {
'use strict';
/** Delay time in ms for reportPlayback logging */
const reportPlaybackLogDelay = 1e3;
function enableLocalPlaylistManagement(player) {
if (player.getPlaylist) {
@ -43,12 +40,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
events.trigger(playbackManagerInstance, 'playerchange', [newPlayer, newTarget, previousPlayer]);
}
/** Last invoked method */
let reportPlaybackLastMethod;
/** Last invoke time of method */
let reportPlaybackLastTime;
function reportPlayback(playbackManagerInstance, state, player, reportPlaylist, serverId, method, progressEventName) {
if (!serverId) {
@ -69,14 +60,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
addPlaylistToPlaybackReport(playbackManagerInstance, info, player, serverId);
}
const now = (new Date).getTime();
if (method !== reportPlaybackLastMethod || now - (reportPlaybackLastTime || 0) >= reportPlaybackLogDelay) {
console.debug(method + '-' + JSON.stringify(info));
reportPlaybackLastMethod = method;
reportPlaybackLastTime = now;
}
var apiClient = connectionManager.getApiClient(serverId);
var reportPlaybackPromise = apiClient[method](info);
// Notify that report has been sent
@ -2097,6 +2080,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
state.PlayState.IsMuted = player.isMuted();
state.PlayState.IsPaused = player.paused();
state.PlayState.RepeatMode = self.getRepeatMode(player);
state.PlayState.ShuffleMode = self.getQueueShuffleMode(player);
state.PlayState.MaxStreamingBitrate = self.getMaxStreamingBitrate(player);
state.PlayState.PositionTicks = getCurrentTicks(player);
@ -2877,11 +2861,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
}
};
self.queue = function (options, player) {
self.queue = function (options, player = this._currentPlayer) {
queue(options, '', player);
};
self.queueNext = function (options, player) {
self.queueNext = function (options, player = this._currentPlayer) {
queue(options, 'next', player);
};
@ -2969,6 +2953,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
} else {
self._playQueueManager.queue(items);
}
events.trigger(player, 'playlistitemadd');
}
function onPlayerProgressInterval() {
@ -3304,6 +3289,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
sendProgressUpdate(player, 'repeatmodechange');
}
function onShuffleQueueModeChange() {
var player = this;
sendProgressUpdate(player, 'shufflequeuemodechange');
}
function onPlaylistItemMove(e) {
var player = this;
sendProgressUpdate(player, 'playlistitemmove', true);
@ -3358,6 +3348,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
events.on(player, 'unpause', onPlaybackUnpause);
events.on(player, 'volumechange', onPlaybackVolumeChange);
events.on(player, 'repeatmodechange', onRepeatModeChange);
events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange);
events.on(player, 'playlistitemmove', onPlaylistItemMove);
events.on(player, 'playlistitemremove', onPlaylistItemRemove);
events.on(player, 'playlistitemadd', onPlaylistItemAdd);
@ -3370,6 +3361,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
events.on(player, 'unpause', onPlaybackUnpause);
events.on(player, 'volumechange', onPlaybackVolumeChange);
events.on(player, 'repeatmodechange', onRepeatModeChange);
events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange);
events.on(player, 'playlistitemmove', onPlaylistItemMove);
events.on(player, 'playlistitemremove', onPlaylistItemRemove);
events.on(player, 'playlistitemadd', onPlaylistItemAdd);
@ -3655,6 +3647,14 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
this.seek(parseInt(ticks), player);
};
PlaybackManager.prototype.seekMs = function (ms, player) {
player = player || this._currentPlayer;
var ticks = ms * 10000;
this.seek(ticks, player);
};
PlaybackManager.prototype.playTrailers = function (item) {
var player = this._currentPlayer;
@ -3702,7 +3702,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
};
PlaybackManager.prototype.stop = function (player) {
player = player || this._currentPlayer;
if (player) {
@ -3811,7 +3810,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
});
};
PlaybackManager.prototype.shuffle = function (shuffleItem, player, queryOptions) {
PlaybackManager.prototype.shuffle = function (shuffleItem, player) {
player = player || this._currentPlayer;
if (player && player.shuffle) {
@ -3878,6 +3877,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
'GoToSearch',
'DisplayMessage',
'SetRepeatMode',
'SetShuffleQueue',
'PlayMediaSource',
'PlayTrailers'
];
@ -3911,9 +3911,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
return info ? info.supportedCommands : [];
};
PlaybackManager.prototype.setRepeatMode = function (value, player) {
player = player || this._currentPlayer;
PlaybackManager.prototype.setRepeatMode = function (value, player = this._currentPlayer) {
if (player && !enableLocalPlaylistManagement(player)) {
return player.setRepeatMode(value);
}
@ -3922,9 +3920,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
events.trigger(player, 'repeatmodechange');
};
PlaybackManager.prototype.getRepeatMode = function (player) {
player = player || this._currentPlayer;
PlaybackManager.prototype.getRepeatMode = function (player = this._currentPlayer) {
if (player && !enableLocalPlaylistManagement(player)) {
return player.getRepeatMode();
}
@ -3932,6 +3928,52 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
return this._playQueueManager.getRepeatMode();
};
PlaybackManager.prototype.setQueueShuffleMode = function (value, player = this._currentPlayer) {
if (player && !enableLocalPlaylistManagement(player)) {
return player.setQueueShuffleMode(value);
}
this._playQueueManager.setShuffleMode(value);
events.trigger(player, 'shufflequeuemodechange');
};
PlaybackManager.prototype.getQueueShuffleMode = function (player = this._currentPlayer) {
if (player && !enableLocalPlaylistManagement(player)) {
return player.getQueueShuffleMode();
}
return this._playQueueManager.getShuffleMode();
};
PlaybackManager.prototype.toggleQueueShuffleMode = function (player = this._currentPlayer) {
let currentvalue;
if (player && !enableLocalPlaylistManagement(player)) {
currentvalue = player.getQueueShuffleMode();
switch (currentvalue) {
case 'Shuffle':
player.setQueueShuffleMode('Sorted');
break;
case 'Sorted':
player.setQueueShuffleMode('Shuffle');
break;
default:
throw new TypeError('current value for shufflequeue is invalid');
}
} else {
this._playQueueManager.toggleShuffleMode();
}
events.trigger(player, 'shufflequeuemodechange');
};
PlaybackManager.prototype.clearQueue = function (clearCurrentItem = false, player = this._currentPlayer) {
if (player && !enableLocalPlaylistManagement(player)) {
return player.clearQueue(clearCurrentItem);
}
this._playQueueManager.clearPlaylist(clearCurrentItem);
events.trigger(player, 'playlistitemremove');
};
PlaybackManager.prototype.trySetActiveDeviceName = function (name) {
name = normalizeName(name);
@ -4000,6 +4042,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
case 'SetRepeatMode':
this.setRepeatMode(cmd.Arguments.RepeatMode, player);
break;
case 'SetShuffleQueue':
this.setQueueShuffleMode(cmd.Arguments.ShuffleMode, player);
break;
case 'VolumeUp':
this.volumeUp(player);
break;

View file

@ -24,8 +24,10 @@ define([], function () {
function PlayQueueManager() {
this._sortedPlaylist = [];
this._playlist = [];
this._repeatMode = 'RepeatNone';
this._shuffleMode = 'Sorted';
}
PlayQueueManager.prototype.getPlaylist = function () {
@ -56,6 +58,40 @@ define([], function () {
}
};
PlayQueueManager.prototype.shufflePlaylist = function () {
this._sortedPlaylist = [];
for (const item of this._playlist) {
this._sortedPlaylist.push(item);
}
const currentPlaylistItem = this._playlist.splice(this.getCurrentPlaylistIndex(), 1)[0];
for (let i = this._playlist.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * i);
const temp = this._playlist[i];
this._playlist[i] = this._playlist[j];
this._playlist[j] = temp;
}
this._playlist.unshift(currentPlaylistItem);
this._shuffleMode = 'Shuffle';
};
PlayQueueManager.prototype.sortShuffledPlaylist = function () {
this._playlist = [];
for (let item of this._sortedPlaylist) {
this._playlist.push(item);
}
this._sortedPlaylist = [];
this._shuffleMode = 'Sorted';
};
PlayQueueManager.prototype.clearPlaylist = function (clearCurrentItem = false) {
const currentPlaylistItem = this._playlist.splice(this.getCurrentPlaylistIndex(), 1)[0];
this._playlist = [];
if (!clearCurrentItem) {
this._playlist.push(currentPlaylistItem);
}
};
function arrayInsertAt(destArray, pos, arrayToInsert) {
var args = [];
args.push(pos); // where to insert
@ -116,9 +152,7 @@ define([], function () {
PlayQueueManager.prototype.removeFromPlaylist = function (playlistItemIds) {
var playlist = this.getPlaylist();
if (playlist.length <= playlistItemIds.length) {
if (this._playlist.length <= playlistItemIds.length) {
return {
result: 'empty'
};
@ -127,8 +161,12 @@ define([], function () {
var currentPlaylistItemId = this.getCurrentPlaylistItemId();
var isCurrentIndex = playlistItemIds.indexOf(currentPlaylistItemId) !== -1;
this._playlist = playlist.filter(function (item) {
return playlistItemIds.indexOf(item.PlaylistItemId) === -1;
this._sortedPlaylist = this._sortedPlaylist.filter(function (item) {
return !playlistItemIds.includes(item.PlaylistItemId);
});
this._playlist = this._playlist.filter(function (item) {
return !playlistItemIds.includes(item.PlaylistItemId);
});
return {
@ -176,21 +214,56 @@ define([], function () {
PlayQueueManager.prototype.reset = function () {
this._sortedPlaylist = [];
this._playlist = [];
this._currentPlaylistItemId = null;
this._repeatMode = 'RepeatNone';
this._shuffleMode = 'Sorted';
};
PlayQueueManager.prototype.setRepeatMode = function (value) {
this._repeatMode = value;
const repeatModes = ['RepeatOne', 'RepeatAll', 'RepeatNone'];
if (repeatModes.includes(value)) {
this._repeatMode = value;
} else {
throw new TypeError('invalid value provided for setRepeatMode');
}
};
PlayQueueManager.prototype.getRepeatMode = function () {
return this._repeatMode;
};
PlayQueueManager.prototype.setShuffleMode = function (value) {
switch (value) {
case 'Shuffle':
this.shufflePlaylist();
break;
case 'Sorted':
this.sortShuffledPlaylist();
break;
default:
throw new TypeError('invalid value provided to setShuffleMode');
}
};
PlayQueueManager.prototype.toggleShuffleMode = function () {
switch (this._shuffleMode) {
case 'Shuffle':
this.setShuffleMode('Sorted');
break;
case 'Sorted':
this.setShuffleMode('Shuffle');
break;
default:
throw new TypeError('current value for shufflequeue is invalid');
}
};
PlayQueueManager.prototype.getShuffleMode = function () {
return this._shuffleMode;
};
PlayQueueManager.prototype.getNextItemInfo = function () {
var newIndex;

View file

@ -415,7 +415,8 @@ import 'css!./playerstats';
name: 'Original Media Info'
});
if (syncPlayManager.isSyncPlayEnabled()) {
var apiClient = connectionManager.getApiClient(playbackManager.currentItem(player).ServerId);
if (syncPlayManager.isSyncPlayEnabled() && apiClient.isMinServerVersion('10.6.0')) {
categories.push({
stats: getSyncPlayStats(),
name: 'SyncPlay Info'

View file

@ -157,43 +157,110 @@
}
.nowPlayingSecondaryButtons {
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-pack: end;
-webkit-justify-content: flex-end;
justify-content: flex-end;
z-index: 0;
}
.layout-mobile .playlistSectionButtonTransparent {
background: rgba(0, 0, 0, 0) !important;
}
.layout-mobile .playlistSection .playlist,
.layout-mobile .playlistSection .contextMenu {
position: absolute;
top: 12.2em;
bottom: 4.2em;
overflow: scroll;
padding: 0 1em;
display: inline-block;
left: 0;
right: 0;
z-index: 1000;
}
.layout-mobile .playlistSectionButton {
position: fixed;
bottom: 0;
left: 0;
height: 4.2em;
right: 0;
padding-left: 7.3%;
padding-right: 7.3%;
}
.layout-desktop .playlistSectionButton,
.layout-tv .playlistSectionButton {
background: none;
}
.layout-desktop .nowPlayingPlaylist,
.layout-tv .nowPlayingPlaylist {
background: none;
}
.layout-mobile .playlistSectionButton .btnTogglePlaylist {
font-size: larger;
margin: 0;
}
.layout-mobile .playlistSectionButton .btnSavePlaylist {
margin: 0;
border-radius: 0;
}
.layout-mobile .playlistSectionButton .volumecontrol {
margin: 0;
padding-right: 0;
border-radius: 0;
}
.layout-mobile .playlistSectionButton .btnToggleContextMenu {
font-size: larger;
margin: 0;
}
.layout-mobile .nowPlayingSecondaryButtons .btnShuffleQueue {
display: none;
}
.layout-mobile .nowPlayingSecondaryButtons .volumecontrol {
display: none;
}
.layout-mobile .nowPlayingSecondaryButtons .btnRepeat {
display: none;
}
.layout-desktop .nowPlayingInfoButtons .btnRepeat,
.layout-tv .nowPlayingInfoButtons .btnRepeat {
display: none;
}
.layout-desktop .nowPlayingInfoButtons .btnShuffleQueue,
.layout-tv .nowPlayingInfoButtons .btnShuffleQueue {
display: none;
}
.layout-desktop .playlistSectionButton .volumecontrol,
.layout-tv .playlistSectionButton .volumecontrol {
display: none;
}
.nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle {
display: none;
}
.layout-mobile .nowPlayingPageUserDataButtons {
display: none;
}
@media all and (min-width: 63em) {
.nowPlayingPage {
padding: 8em 0 0 0 !important;
}
.nowPlayingSecondaryButtons {
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
-webkit-box-pack: end;
-webkit-justify-content: flex-end;
justify-content: flex-end;
}
.nowPlayingPageUserDataButtonsTitle {
display: none !important;
}
.playlistSectionButton,
.nowPlayingPlaylist,
.nowPlayingContextMenu {
background: unset !important;
}
}
@media all and (min-width: 80em) {
@ -202,7 +269,7 @@
}
}
@media all and (orientation: portrait) and (max-width: 47em) {
@media all and (orientation: portrait) and (max-width: 43em) {
.remoteControlContent {
padding-left: 7.3% !important;
padding-right: 7.3% !important;
@ -211,6 +278,10 @@
flex-direction: column;
}
.layout-desktop .nowPlayingPageUserDataButtons {
display: none;
}
.nowPlayingInfoContainer {
-webkit-box-orient: vertical !important;
-webkit-box-direction: normal !important;
@ -280,6 +351,7 @@
.nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle {
width: 20%;
font-size: large;
display: unset;
}
.nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle button {
@ -290,7 +362,7 @@
border-radius: 0;
}
.nowPlayingInfoButtons .btnRewind {
.nowPlayingInfoButtons .btnRepeat {
position: absolute;
left: 0;
margin-left: 0;
@ -298,7 +370,7 @@
font-size: smaller;
}
.nowPlayingInfoButtons .btnFastForward {
.nowPlayingInfoButtons .btnShuffleQueue {
position: absolute;
right: 0;
margin-right: 0;
@ -342,250 +414,6 @@
width: auto;
}
#nowPlayingPage .playlistSection .playlist,
#nowPlayingPage .playlistSection .contextMenu {
position: absolute;
top: 12.2em;
bottom: 4.2em;
overflow: scroll;
padding: 0 1em;
display: inline-block;
left: 0;
right: 0;
z-index: 1000;
}
.playlistSectionButton {
position: fixed;
bottom: 0;
left: 0;
height: 4.2em;
right: 0;
padding-left: 7.3%;
padding-right: 7.3%;
}
.playlistSectionButton .btnTogglePlaylist {
font-size: larger;
margin: 0;
padding-left: 0;
}
.playlistSectionButton .btnSavePlaylist {
margin: 0;
padding-right: 0;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
-webkit-box-pack: end;
-webkit-justify-content: flex-end;
justify-content: flex-end;
border-radius: 0;
}
.playlistSectionButton .btnToggleContextMenu {
font-size: larger;
margin: 0;
padding-right: 0;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
-webkit-box-pack: end;
-webkit-justify-content: flex-end;
justify-content: flex-end;
border-radius: 0;
}
.playlistSectionButton .volumecontrol {
width: 100%;
}
.remoteControlSection {
margin: 0;
padding: 0 0 4.2em 0;
}
.nowPlayingButtonsContainer {
display: flex;
height: 100%;
flex-direction: column;
}
}
@media all and (orientation: landscape) and (max-width: 63em) {
.remoteControlContent {
padding-left: 4.3% !important;
padding-right: 4.3% !important;
display: flex;
height: 100%;
flex-direction: column;
}
.nowPlayingInfoContainer {
-webkit-box-orient: horizontal !important;
-webkit-box-direction: normal !important;
-webkit-flex-direction: row !important;
flex-direction: row !important;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
width: 100%;
height: calc(100% - 4.2em);
}
.nowPlayingPageTitle {
/* text-align: center; */
margin: 0;
}
.nowPlayingInfoContainerMedia {
text-align: left !important;
width: 80%;
}
.nowPlayingPositionSliderContainer {
margin: 0.2em 1em 0.2em 1em;
}
.nowPlayingInfoButtons {
/* margin: 1.5em 0 0 0; */
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
font-size: x-large;
height: 100%;
}
.nowPlayingPageImageContainer {
width: 30%;
margin: auto 1em auto auto;
}
.nowPlayingPageImageContainerNoAlbum .cardImageContainer .cardImageIcon {
font-size: 12em;
color: inherit;
}
.nowPlayingInfoControls {
margin: 0.5em 0 1em 0;
width: 100%;
-webkit-box-pack: start !important;
-webkit-justify-content: start !important;
justify-content: start !important;
}
.nowPlayingSecondaryButtons {
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
}
.nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle {
width: 20%;
font-size: large;
}
.nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle button {
padding-top: 0;
padding-right: 0;
margin-right: 0;
float: right;
border-radius: 0;
}
.paper-icon-button-light:hover {
color: #fff !important;
background-color: transparent !important;
}
.btnPlayPause {
padding: 0;
margin: 0;
font-size: 1.7em;
}
.btnPlayPause:hover {
background-color: transparent !important;
}
.nowPlayingPageImage {
/* width: inherit; */
overflow-y: hidden;
overflow: hidden;
margin: 0 auto;
}
.nowPlayingPageImage.nowPlayingPageImageAudio {
width: 100%;
}
.nowPlayingPageImageContainer.nowPlayingPageImagePoster {
height: 100%;
overflow: hidden;
}
.nowPlayingPageImageContainer.nowPlayingPageImagePoster img {
height: 100%;
width: auto;
}
#nowPlayingPage .playlistSection .playlist,
#nowPlayingPage .playlistSection .contextMenu {
position: absolute;
top: 7.2em;
bottom: 4.2em;
overflow: scroll;
padding: 0 1em;
display: inline-block;
left: 0;
right: 0;
z-index: 1000;
}
.playlistSectionButton {
position: fixed;
bottom: 0;
left: 0;
height: 4.2em;
right: 0;
padding-left: 4.3%;
padding-right: 4.3%;
}
.playlistSectionButton .btnTogglePlaylist {
font-size: larger;
margin: 0;
padding-left: 0;
}
.playlistSectionButton .btnSavePlaylist {
margin: 0;
padding-right: 0;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
-webkit-box-pack: end;
-webkit-justify-content: flex-end;
justify-content: flex-end;
border-radius: 0;
}
.playlistSectionButton .btnToggleContextMenu {
font-size: larger;
margin: 0;
padding-right: 0;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
-webkit-box-pack: end;
-webkit-justify-content: flex-end;
justify-content: flex-end;
border-radius: 0;
}
.playlistSectionButton .volumecontrol {
width: 100%;
}
@ -627,6 +455,10 @@
background-image: url(../../assets/img/equalizer.gif) !important;
}
.playlistIndexIndicatorImage > * {
display: none;
}
.hideVideoButtons .videoButton {
display: none;
}
@ -636,7 +468,6 @@
}
@media all and (max-width: 63em) {
.nowPlayingSecondaryButtons .nowPlayingPageUserDataButtons,
.nowPlayingSecondaryButtons .repeatToggleButton,
.nowPlayingInfoButtons .playlist .listItemMediaInfo,
.nowPlayingInfoButtons .btnStop {

View file

@ -1,5 +1,7 @@
define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageLoader', 'playbackManager', 'nowPlayingHelper', 'events', 'connectionManager', 'apphost', 'globalize', 'layoutManager', 'userSettings', 'cardBuilder', 'cardStyle', 'emby-itemscontainer', 'css!./remotecontrol.css', 'emby-ratingbutton'], function (browser, datetime, backdrop, libraryBrowser, listView, imageLoader, playbackManager, nowPlayingHelper, events, connectionManager, appHost, globalize, layoutManager, userSettings, cardBuilder) {
define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageLoader', 'playbackManager', 'nowPlayingHelper', 'events', 'connectionManager', 'apphost', 'globalize', 'layoutManager', 'userSettings', 'cardBuilder', 'itemContextMenu', 'cardStyle', 'emby-itemscontainer', 'css!./remotecontrol.css', 'emby-ratingbutton'], function (browser, datetime, backdrop, libraryBrowser, listView, imageLoader, playbackManager, nowPlayingHelper, events, connectionManager, appHost, globalize, layoutManager, userSettings, cardBuilder, itemContextMenu) {
'use strict';
var showMuteButton = true;
var showVolumeSlider = true;
function showAudioMenu(context, player, button, item) {
var currentIndex = playbackManager.getAudioStreamIndex(player);
@ -118,30 +120,41 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
if (item.Type == 'Audio' || item.MediaStreams[0].Type == 'Audio') {
var songName = item.Name;
if (item.Album != null && item.Artists != null) {
var artistsSeries = '';
var albumName = item.Album;
var artistName;
if (item.ArtistItems != null) {
artistName = item.ArtistItems[0].Name;
context.querySelector('.nowPlayingAlbum').innerHTML = '<a class="button-link emby-button" is="emby-linkbutton" href="itemdetails.html?id=' + item.AlbumId + `&amp;serverId=${nowPlayingServerId}">${albumName}</a>`;
context.querySelector('.nowPlayingArtist').innerHTML = '<a class="button-link emby-button" is="emby-linkbutton" href="itemdetails.html?id=' + item.ArtistItems[0].Id + `&amp;serverId=${nowPlayingServerId}">${artistName}</a>`;
context.querySelector('.contextMenuAlbum').innerHTML = '<a class="button-link emby-button" is="emby-linkbutton" href="itemdetails.html?id=' + item.AlbumId + `&amp;serverId=${nowPlayingServerId}"><span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons album"></span> ` + globalize.translate('ViewAlbum') + '</a>';
context.querySelector('.contextMenuArtist').innerHTML = '<a class="button-link emby-button" is="emby-linkbutton" href="itemdetails.html?id=' + item.ArtistItems[0].Id + `&amp;serverId=${nowPlayingServerId}"><span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons person"></span> ` + globalize.translate('ViewArtist') + '</a>';
} else {
artistName = item.Artists;
context.querySelector('.nowPlayingAlbum').innerHTML = albumName;
context.querySelector('.nowPlayingArtist').innerHTML = artistName;
for (const artist of item.ArtistItems) {
let artistName = artist.Name;
let artistId = artist.Id;
artistsSeries += `<a class="button-link emby-button" is="emby-linkbutton" href="details?id=${artistId}&serverId=${nowPlayingServerId}">${artistName}</a>`;
if (artist !== item.ArtistItems.slice(-1)[0]) {
artistsSeries += ', ';
}
}
} else if (item.Artists) {
// For some reason, Chromecast Player doesn't return a item.ArtistItems object, so we need to fallback
// to normal item.Artists item.
// TODO: Normalise fields returned by all the players
for (const artist of item.Artists) {
artistsSeries += `<a>${artist}</a>`;
if (artist !== item.Artists.slice(-1)[0]) {
artistsSeries += ', ';
}
}
}
context.querySelector('.nowPlayingArtist').innerHTML = artistsSeries;
context.querySelector('.nowPlayingAlbum').innerHTML = '<a class="button-link emby-button" is="emby-linkbutton" href="details?id=' + item.AlbumId + `&serverId=${nowPlayingServerId}">${albumName}</a>`;
}
context.querySelector('.nowPlayingSongName').innerHTML = songName;
} else if (item.Type == 'Episode') {
if (item.SeasonName != null) {
var seasonName = item.SeasonName;
context.querySelector('.nowPlayingSeason').innerHTML = '<a class="button-link emby-button" is="emby-linkbutton" href="itemdetails.html?id=' + item.SeasonId + `&amp;serverId=${nowPlayingServerId}">${seasonName}</a>`;
context.querySelector('.nowPlayingSeason').innerHTML = '<a class="button-link emby-button" is="emby-linkbutton" href="details?id=' + item.SeasonId + `&serverId=${nowPlayingServerId}">${seasonName}</a>`;
}
if (item.SeriesName != null) {
var seriesName = item.SeriesName;
if (item.SeriesId != null) {
context.querySelector('.nowPlayingSerie').innerHTML = '<a class="button-link emby-button" is="emby-linkbutton" href="itemdetails.html?id=' + item.SeriesId + `&amp;serverId=${nowPlayingServerId}">${seriesName}</a>`;
context.querySelector('.nowPlayingSerie').innerHTML = '<a class="button-link emby-button" is="emby-linkbutton" href="details?id=' + item.SeriesId + `&serverId=${nowPlayingServerId}">${seriesName}</a>`;
} else {
context.querySelector('.nowPlayingSerie').innerHTML = seriesName;
}
@ -163,11 +176,38 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
maxHeight: 300 * 2
}) : null;
console.debug('updateNowPlayingInfo');
let contextButton = context.querySelector('.btnToggleContextMenu');
// We remove the previous event listener by replacing the item in each update event
const autoFocusContextButton = document.activeElement === contextButton;
let contextButtonClone = contextButton.cloneNode(true);
contextButton.parentNode.replaceChild(contextButtonClone, contextButton);
contextButton = context.querySelector('.btnToggleContextMenu');
if (autoFocusContextButton) {
contextButton.focus();
}
const stopPlayback = !!layoutManager.mobile;
var options = {
play: false,
queue: false,
stopPlayback: stopPlayback,
clearQueue: true,
openAlbum: false,
positionTo: contextButton
};
var apiClient = connectionManager.getApiClient(item.ServerId);
apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) {
apiClient.getCurrentUser().then(function (user) {
contextButton.addEventListener('click', function () {
itemContextMenu.show(Object.assign({
item: fullItem,
user: user
}, options));
});
});
});
setImageUrl(context, state, url);
if (item) {
backdrop.setBackdrops([item]);
var apiClient = connectionManager.getApiClient(item.ServerId);
apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) {
var userData = fullItem.UserData || {};
var likes = null == userData.Likes ? '' : userData.Likes;
@ -219,20 +259,16 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
var currentImgUrl;
return function () {
function toggleRepeat(player) {
if (player) {
switch (playbackManager.getRepeatMode(player)) {
case 'RepeatNone':
playbackManager.setRepeatMode('RepeatAll', player);
break;
case 'RepeatAll':
playbackManager.setRepeatMode('RepeatOne', player);
break;
case 'RepeatOne':
playbackManager.setRepeatMode('RepeatNone', player);
}
function toggleRepeat() {
switch (playbackManager.getRepeatMode()) {
case 'RepeatAll':
playbackManager.setRepeatMode('RepeatOne');
break;
case 'RepeatOne':
playbackManager.setRepeatMode('RepeatNone');
break;
case 'RepeatNone':
playbackManager.setRepeatMode('RepeatAll');
}
}
@ -275,8 +311,13 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
buttonVisible(context.querySelector('.btnStop'), null != item);
buttonVisible(context.querySelector('.btnNextTrack'), null != item);
buttonVisible(context.querySelector('.btnPreviousTrack'), null != item);
buttonVisible(context.querySelector('.btnRewind'), null != item);
buttonVisible(context.querySelector('.btnFastForward'), null != item);
if (layoutManager.mobile) {
buttonVisible(context.querySelector('.btnRewind'), false);
buttonVisible(context.querySelector('.btnFastForward'), false);
} else {
buttonVisible(context.querySelector('.btnRewind'), null != item);
buttonVisible(context.querySelector('.btnFastForward'), null != item);
}
var positionSlider = context.querySelector('.nowPlayingPositionSlider');
if (positionSlider && item && item.RunTimeTicks) {
@ -300,7 +341,8 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
context.classList.add('hideVideoButtons');
}
updateRepeatModeDisplay(playState.RepeatMode);
updateRepeatModeDisplay(playbackManager.getRepeatMode());
onShuffleQueueModeChange(false);
updateNowPlayingInfo(context, state);
}
@ -316,25 +358,32 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
function updateRepeatModeDisplay(repeatMode) {
var context = dlg;
var toggleRepeatButton = context.querySelector('.repeatToggleButton');
let toggleRepeatButtons = context.querySelectorAll('.repeatToggleButton');
const cssClass = 'buttonActive';
let innHtml = '<span class="material-icons repeat"></span>';
let repeatOn = true;
if ('RepeatAll' == repeatMode) {
toggleRepeatButton.innerHTML = "<span class='material-icons repeat'></span>";
toggleRepeatButton.classList.add('repeatButton-active');
} else if ('RepeatOne' == repeatMode) {
toggleRepeatButton.innerHTML = "<span class='material-icons repeat_one'></span>";
toggleRepeatButton.classList.add('repeatButton-active');
} else {
toggleRepeatButton.innerHTML = "<span class='material-icons repeat'></span>";
toggleRepeatButton.classList.remove('repeatButton-active');
switch (repeatMode) {
case 'RepeatAll':
break;
case 'RepeatOne':
innHtml = '<span class="material-icons repeat_one"></span>';
break;
case 'RepeatNone':
default:
repeatOn = false;
break;
}
for (const toggleRepeatButton of toggleRepeatButtons) {
toggleRepeatButton.classList.toggle(cssClass, repeatOn);
toggleRepeatButton.innerHTML = innHtml;
}
}
function updatePlayerVolumeState(context, isMuted, volumeLevel) {
var view = context;
var supportedCommands = currentPlayerSupportedCommands;
var showMuteButton = true;
var showVolumeSlider = true;
if (-1 === supportedCommands.indexOf('Mute')) {
showMuteButton = false;
@ -362,24 +411,21 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
buttonMuteIcon.classList.add('volume_up');
}
if (showMuteButton) {
buttonMute.classList.remove('hide');
if (!showMuteButton && !showVolumeSlider) {
context.querySelector('.volumecontrol').classList.add('hide');
} else {
buttonMute.classList.add('hide');
}
buttonMute.classList.toggle('hide', !showMuteButton);
var nowPlayingVolumeSlider = context.querySelector('.nowPlayingVolumeSlider');
var nowPlayingVolumeSliderContainer = context.querySelector('.nowPlayingVolumeSliderContainer');
var nowPlayingVolumeSlider = context.querySelector('.nowPlayingVolumeSlider');
var nowPlayingVolumeSliderContainer = context.querySelector('.nowPlayingVolumeSliderContainer');
if (nowPlayingVolumeSlider) {
if (showVolumeSlider) {
nowPlayingVolumeSliderContainer.classList.remove('hide');
} else {
nowPlayingVolumeSliderContainer.classList.add('hide');
}
if (nowPlayingVolumeSlider) {
if (!nowPlayingVolumeSlider.dragging) {
nowPlayingVolumeSlider.value = volumeLevel || 0;
nowPlayingVolumeSliderContainer.classList.toggle('hide', !showVolumeSlider);
if (!nowPlayingVolumeSlider.dragging) {
nowPlayingVolumeSlider.value = volumeLevel || 0;
}
}
}
}
@ -420,11 +466,21 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
function loadPlaylist(context, player) {
getPlaylistItems(player).then(function (items) {
var html = '';
let favoritesEnabled = true;
if (layoutManager.mobile) {
if (items.length > 0) {
context.querySelector('.btnTogglePlaylist').classList.remove('hide');
} else {
context.querySelector('.btnTogglePlaylist').classList.add('hide');
}
favoritesEnabled = false;
}
html += listView.getListViewHtml({
items: items,
smallIcon: true,
action: 'setplaylistindex',
enableUserDataButtons: false,
enableUserDataButtons: favoritesEnabled,
rightButtons: [{
icon: 'remove_circle_outline',
title: globalize.translate('ButtonRemove'),
@ -433,18 +489,21 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
dragHandle: true
});
if (items.length) {
context.querySelector('.btnTogglePlaylist').classList.remove('hide');
} else {
context.querySelector('.btnTogglePlaylist').classList.add('hide');
var itemsContainer = context.querySelector('.playlist');
let focusedItemPlaylistId = itemsContainer.querySelector('button:focus');
itemsContainer.innerHTML = html;
if (focusedItemPlaylistId !== null) {
focusedItemPlaylistId = focusedItemPlaylistId.getAttribute('data-playlistitemid');
const newFocusedItem = itemsContainer.querySelector(`button[data-playlistitemid="${focusedItemPlaylistId}"]`);
if (newFocusedItem !== null) {
newFocusedItem.focus();
}
}
var itemsContainer = context.querySelector('.playlist');
itemsContainer.innerHTML = html;
var playlistItemId = playbackManager.getCurrentPlaylistItemId(player);
if (playlistItemId) {
var img = itemsContainer.querySelector('.listItem[data-playlistItemId="' + playlistItemId + '"] .listItemImage');
var img = itemsContainer.querySelector(`.listItem[data-playlistItemId="${playlistItemId}"] .listItemImage`);
if (img) {
img.classList.remove('lazy');
@ -453,9 +512,6 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
}
imageLoader.lazyChildren(itemsContainer);
context.querySelector('.playlist').classList.add('hide');
context.querySelector('.contextMenu').classList.add('hide');
context.querySelector('.btnSavePlaylist').classList.add('hide');
});
}
@ -465,9 +521,31 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
onStateChanged.call(player, e, state);
}
function onRepeatModeChange(e) {
var player = this;
updateRepeatModeDisplay(playbackManager.getRepeatMode(player));
function onRepeatModeChange() {
updateRepeatModeDisplay(playbackManager.getRepeatMode());
}
function onShuffleQueueModeChange(updateView = true) {
let shuffleMode = playbackManager.getQueueShuffleMode(this);
let context = dlg;
const cssClass = 'buttonActive';
let shuffleButtons = context.querySelectorAll('.btnShuffleQueue');
for (let shuffleButton of shuffleButtons) {
switch (shuffleMode) {
case 'Shuffle':
shuffleButton.classList.add(cssClass);
break;
case 'Sorted':
default:
shuffleButton.classList.remove(cssClass);
break;
}
}
if (updateView) {
onPlaylistUpdate();
}
}
function onPlaylistUpdate(e) {
@ -476,14 +554,18 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
function onPlaylistItemRemoved(e, info) {
var context = dlg;
var playlistItemIds = info.playlistItemIds;
if (info !== undefined) {
var playlistItemIds = info.playlistItemIds;
for (var i = 0, length = playlistItemIds.length; i < length; i++) {
var listItem = context.querySelector('.listItem[data-playlistItemId="' + playlistItemIds[i] + '"]');
for (var i = 0, length = playlistItemIds.length; i < length; i++) {
var listItem = context.querySelector('.listItem[data-playlistItemId="' + playlistItemIds[i] + '"]');
if (listItem) {
listItem.parentNode.removeChild(listItem);
if (listItem) {
listItem.parentNode.removeChild(listItem);
}
}
} else {
onPlaylistUpdate();
}
}
@ -493,7 +575,6 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
if (!state.NextMediaType) {
updatePlayerState(player, dlg, {});
loadPlaylist(dlg);
Emby.Page.back();
}
}
@ -505,7 +586,7 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
function onStateChanged(event, state) {
var player = this;
updatePlayerState(player, dlg, state);
loadPlaylist(dlg, player);
onPlaylistUpdate();
}
function onTimeUpdate(e) {
@ -531,8 +612,10 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
events.off(player, 'playbackstart', onPlaybackStart);
events.off(player, 'statechange', onStateChanged);
events.off(player, 'repeatmodechange', onRepeatModeChange);
events.off(player, 'playlistitemremove', onPlaylistUpdate);
events.off(player, 'shufflequeuemodechange', onShuffleQueueModeChange);
events.off(player, 'playlistitemremove', onPlaylistItemRemoved);
events.off(player, 'playlistitemmove', onPlaylistUpdate);
events.off(player, 'playlistitemadd', onPlaylistUpdate);
events.off(player, 'playbackstop', onPlaybackStopped);
events.off(player, 'volumechange', onVolumeChanged);
events.off(player, 'pause', onPlayPauseStateChanged);
@ -551,8 +634,10 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
events.on(player, 'playbackstart', onPlaybackStart);
events.on(player, 'statechange', onStateChanged);
events.on(player, 'repeatmodechange', onRepeatModeChange);
events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange);
events.on(player, 'playlistitemremove', onPlaylistItemRemoved);
events.on(player, 'playlistitemmove', onPlaylistUpdate);
events.on(player, 'playlistitemadd', onPlaylistUpdate);
events.on(player, 'playbackstop', onPlaybackStopped);
events.on(player, 'volumechange', onVolumeChanged);
events.on(player, 'pause', onPlayPauseStateChanged);
@ -568,7 +653,7 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
function onBtnCommandClick() {
if (currentPlayer) {
if (this.classList.contains('repeatToggleButton')) {
toggleRepeat(currentPlayer);
toggleRepeat();
} else {
playbackManager.sendCommand({
Name: this.getAttribute('data-command')
@ -603,6 +688,7 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
function bindEvents(context) {
var btnCommand = context.querySelectorAll('.btnCommand');
var positionSlider = context.querySelector('.nowPlayingPositionSlider');
for (var i = 0, length = btnCommand.length; i < length; i++) {
btnCommand[i].addEventListener('click', onBtnCommandClick);
@ -650,12 +736,37 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
playbackManager.fastForward(currentPlayer);
}
});
context.querySelector('.btnPreviousTrack').addEventListener('click', function () {
for (const shuffleButton of context.querySelectorAll('.btnShuffleQueue')) {
shuffleButton.addEventListener('click', function () {
if (currentPlayer) {
playbackManager.toggleQueueShuffleMode(currentPlayer);
}
});
}
context.querySelector('.btnPreviousTrack').addEventListener('click', function (e) {
if (currentPlayer) {
if (lastPlayerState.NowPlayingItem.MediaType === 'Audio' && (currentPlayer._currentTime >= 5 || !playbackManager.previousTrack(currentPlayer))) {
// Cancel this event if doubleclick is fired
if (e.detail > 1 && playbackManager.previousTrack(currentPlayer)) {
return;
}
playbackManager.seekPercent(0, currentPlayer);
// This is done automatically by playbackManager. However, setting this here gives instant visual feedback.
// TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround.
positionSlider.value = 0;
} else {
playbackManager.previousTrack(currentPlayer);
}
}
});
context.querySelector('.btnPreviousTrack').addEventListener('dblclick', function () {
if (currentPlayer) {
playbackManager.previousTrack(currentPlayer);
}
});
context.querySelector('.nowPlayingPositionSlider').addEventListener('change', function () {
positionSlider.addEventListener('change', function () {
var value = this.value;
if (currentPlayer) {
@ -664,7 +775,7 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
}
});
context.querySelector('.nowPlayingPositionSlider').getBubbleText = function (value) {
positionSlider.getBubbleText = function (value) {
var state = lastPlayerState;
if (!state || !state.NowPlayingItem || !currentRuntimeTicks) {
@ -677,13 +788,10 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
return datetime.getDisplayRunningTime(ticks);
};
function setVolume() {
playbackManager.setVolume(this.value, currentPlayer);
}
context.querySelector('.nowPlayingVolumeSlider').addEventListener('input', (e) => {
playbackManager.setVolume(e.target.value, currentPlayer);
});
context.querySelector('.nowPlayingVolumeSlider').addEventListener('change', setVolume);
context.querySelector('.nowPlayingVolumeSlider').addEventListener('mousemove', setVolume);
context.querySelector('.nowPlayingVolumeSlider').addEventListener('touchmove', setVolume);
context.querySelector('.buttonMute').addEventListener('click', function () {
playbackManager.toggleMute(currentPlayer);
});
@ -701,21 +809,19 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
if (context.querySelector('.playlist').classList.contains('hide')) {
context.querySelector('.playlist').classList.remove('hide');
context.querySelector('.btnSavePlaylist').classList.remove('hide');
context.querySelector('.contextMenu').classList.add('hide');
context.querySelector('.volumecontrol').classList.add('hide');
if (layoutManager.mobile) {
context.querySelector('.playlistSectionButton').classList.remove('playlistSectionButtonTransparent');
}
} else {
context.querySelector('.playlist').classList.add('hide');
context.querySelector('.btnSavePlaylist').classList.add('hide');
context.querySelector('.volumecontrol').classList.remove('hide');
}
});
context.querySelector('.btnToggleContextMenu').addEventListener('click', function () {
if (context.querySelector('.contextMenu').classList.contains('hide')) {
context.querySelector('.contextMenu').classList.remove('hide');
context.querySelector('.btnSavePlaylist').classList.add('hide');
context.querySelector('.playlist').classList.add('hide');
} else {
context.querySelector('.contextMenu').classList.add('hide');
if (showMuteButton || showVolumeSlider) {
context.querySelector('.volumecontrol').classList.remove('hide');
}
if (layoutManager.mobile) {
context.querySelector('.playlistSectionButton').classList.add('playlistSectionButtonTransparent');
}
}
});
}
@ -764,16 +870,24 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
}
function init(ownerView, context) {
let contextmenuHtml = `<button id="toggleContextMenu" is="paper-icon-button-light" class="btnToggleContextMenu" title=${globalize.translate('ButtonToggleContextMenu')}><span class="material-icons more_vert"></span></button>`;
let volumecontrolHtml = '<div class="volumecontrol flex align-items-center flex-wrap-wrap justify-content-center">';
volumecontrolHtml += `<button is="paper-icon-button-light" class="buttonMute autoSize" title=${globalize.translate('Mute')}><span class="xlargePaperIconButton material-icons volume_up"></span></button>`;
volumecontrolHtml += '<div class="sliderContainer nowPlayingVolumeSliderContainer"><input is="emby-slider" type="range" step="1" min="0" max="100" value="0" class="nowPlayingVolumeSlider"/></div>';
volumecontrolHtml += '</div>';
let optionsSection = context.querySelector('.playlistSectionButton');
if (!layoutManager.mobile) {
context.querySelector('.nowPlayingSecondaryButtons').innerHTML += volumecontrolHtml;
context.querySelector('.playlistSectionButton').innerHTML += contextmenuHtml;
context.querySelector('.nowPlayingSecondaryButtons').insertAdjacentHTML('beforeend', volumecontrolHtml);
optionsSection.classList.remove('align-items-center', 'justify-content-center');
optionsSection.classList.add('align-items-right', 'justify-content-flex-end');
context.querySelector('.playlist').classList.remove('hide');
context.querySelector('.btnSavePlaylist').classList.remove('hide');
context.classList.add('padded-bottom');
} else {
context.querySelector('.playlistSectionButton').innerHTML += volumecontrolHtml + contextmenuHtml;
optionsSection.querySelector('.btnTogglePlaylist').insertAdjacentHTML('afterend', volumecontrolHtml);
optionsSection.classList.add('playlistSectionButtonTransparent');
context.querySelector('.btnTogglePlaylist').classList.remove('hide');
context.querySelector('.playlistSectionButton').classList.remove('justify-content-center');
context.querySelector('.playlistSectionButton').classList.add('justify-content-space-between');
}
bindEvents(context);

View file

@ -474,7 +474,7 @@ import 'emby-button';
showTitle: true,
overlayText: false,
centerText: true,
action: 'play'
overlayPlayButton: true
});

View file

@ -224,6 +224,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
});
inputManager.on(window, onInputCommand);
/* eslint-disable-next-line compat/compat */
document.addEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove);
dialog.addEventListener('close', onDialogClosed);
@ -489,6 +490,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f
}
inputManager.off(window, onInputCommand);
/* eslint-disable-next-line compat/compat */
document.removeEventListener((window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove);
// Shows page scrollbar
document.body.classList.remove('hide-scroll');

View file

@ -1,12 +1,18 @@
.subtitleSync {
position: absolute;
width: 100%;
}
.subtitleSyncContainer {
width: 40%;
margin-left: 30%;
margin-right: 30%;
min-width: 18em;
margin-left: auto;
margin-right: auto;
height: 4.2em;
background: rgba(28, 28, 28, 0.8);
border-radius: 0.3em;
color: #fff;
position: absolute;
position: relative;
}
.subtitleSync-closeButton {

View file

@ -65,6 +65,9 @@ define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html',
event.preventDefault();
}
}
// FIXME: TV layout will require special handling for navigation keys. But now field is not focusable
event.stopPropagation();
});
subtitleSyncTextField.blur = function() {
@ -87,14 +90,6 @@ define(['playbackManager', 'layoutManager', 'text!./subtitlesync.template.html',
getOffsetFromPercentage(this.value));
});
subtitleSyncSlider.addEventListener('touchmove', function () {
// set new offset
playbackManager.setSubtitleOffset(getOffsetFromPercentage(this.value), player);
// synchronize with textField value
subtitleSyncTextField.updateOffset(
getOffsetFromPercentage(this.value));
});
subtitleSyncSlider.getBubbleHtml = function (value) {
var newOffset = getOffsetFromPercentage(value);
return '<h1 class="sliderBubbleText">' +

View file

@ -1,7 +1,9 @@
<div class="subtitleSyncContainer">
<button type="button" is="paper-icon-button-light" class="subtitleSync-closeButton"><span class="material-icons close"></span></button>
<div class="subtitleSyncTextField" contenteditable="true" spellcheck="false">0s</div>
<div class="sliderContainer subtitleSyncSliderContainer">
<input is="emby-slider" type="range" step="1" min="0" max="100" value="50" class="subtitleSyncSlider" />
<div class="subtitleSync">
<div class="subtitleSyncContainer">
<button type="button" is="paper-icon-button-light" class="subtitleSync-closeButton"><span class="material-icons close"></span></button>
<div class="subtitleSyncTextField" contenteditable="true" spellcheck="false">0s</div>
<div class="sliderContainer subtitleSyncSliderContainer">
<input is="emby-slider" type="range" step="1" min="0" max="100" value="50" class="subtitleSyncSlider" data-slider-keep-progress="true" />
</div>
</div>
</div>

View file

@ -37,7 +37,7 @@ function showNewJoinGroupSelection (button, user, apiClient) {
console.debug('No item is currently playing.');
}
apiClient.sendSyncPlayCommand(sessionId, 'ListGroups').then(function (response) {
apiClient.getSyncPlayGroups().then(function (response) {
response.json().then(function (groups) {
var menuItems = groups.map(function (group) {
return {
@ -83,9 +83,9 @@ function showNewJoinGroupSelection (button, user, apiClient) {
actionsheet.show(menuOptions).then(function (id) {
if (id == 'new-group') {
apiClient.sendSyncPlayCommand(sessionId, 'NewGroup');
} else {
apiClient.sendSyncPlayCommand(sessionId, 'JoinGroup', {
apiClient.createSyncPlayGroup();
} else if (id) {
apiClient.joinSyncPlayGroup({
GroupId: id,
PlayingItemId: playingItemId
});
@ -140,7 +140,7 @@ function showLeaveGroupSelection (button, user, apiClient) {
actionsheet.show(menuOptions).then(function (id) {
if (id == 'leave-group') {
apiClient.sendSyncPlayCommand(sessionId, 'LeaveGroup');
apiClient.leaveSyncPlayGroup();
}
}).catch((error) => {
console.error('SyncPlay: unexpected error showing group menu:', error);

View file

@ -139,7 +139,7 @@ class SyncPlayManager {
return;
}
apiClient.sendSyncPlayCommand(sessionId, 'UpdatePing', {
apiClient.sendSyncPlayPing({
Ping: ping
});
}
@ -212,6 +212,7 @@ class SyncPlayManager {
if (!this.lastPlaybackWaiting) {
this.lastPlaybackWaiting = new Date();
}
events.trigger(this, 'waiting');
}
@ -288,6 +289,7 @@ class SyncPlayManager {
player.setPlaybackRate(this.localPlayerPlaybackRate);
this.localPlayerPlaybackRate = 1.0;
}
this.currentPlayer = null;
this.playbackRateSupported = false;
}
@ -433,6 +435,7 @@ class SyncPlayManager {
});
return;
}
// Get playing item id
let playingItemId;
try {
@ -447,7 +450,7 @@ class SyncPlayManager {
if (!success) {
console.warning('Error reporting playback state to server. Joining group will fail.');
}
apiClient.sendSyncPlayCommand(sessionId, 'JoinGroup', {
apiClient.joinSyncPlayGroup({
GroupId: groupId,
PlayingItemId: playingItemId
});
@ -619,6 +622,7 @@ class SyncPlayManager {
if (this.currentPlayer) {
this.currentPlayer.setPlaybackRate(1);
}
this.clearSyncIcon();
}
@ -658,8 +662,7 @@ class SyncPlayManager {
*/
playRequest (player) {
var apiClient = connectionManager.currentApiClient();
var sessionId = getActivePlayerId();
apiClient.sendSyncPlayCommand(sessionId, 'PlayRequest');
apiClient.requestSyncPlayStart();
}
/**
@ -667,8 +670,7 @@ class SyncPlayManager {
*/
pauseRequest (player) {
var apiClient = connectionManager.currentApiClient();
var sessionId = getActivePlayerId();
apiClient.sendSyncPlayCommand(sessionId, 'PauseRequest');
apiClient.requestSyncPlayPause();
// Pause locally as well, to give the user some little control
playbackManager._localUnpause(player);
}
@ -678,8 +680,7 @@ class SyncPlayManager {
*/
seekRequest (PositionTicks, player) {
var apiClient = connectionManager.currentApiClient();
var sessionId = getActivePlayerId();
apiClient.sendSyncPlayCommand(sessionId, 'SeekRequest', {
apiClient.requestSyncPlaySeek({
PositionTicks: PositionTicks
});
}

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
import appHost from 'apphost';
import appSettings from 'appSettings';
import dom from 'dom';
@ -8,6 +9,10 @@ import browser from 'browser';
import globalize from 'globalize';
import 'cardStyle';
import 'emby-checkbox';
=======
define(['apphost', 'appSettings', 'dom', 'connectionManager', 'loading', 'layoutManager', 'libraryMenu', 'browser', 'globalize', 'cardStyle', 'emby-checkbox'], function (appHost, appSettings, dom, connectionManager, loading, layoutManager, libraryMenu, browser, globalize) {
'use strict';
>>>>>>> upstream/master
/* eslint-disable indent */
@ -16,6 +21,7 @@ import 'emby-checkbox';
function authenticateUserByName(page, apiClient, username, password) {
loading.show();
apiClient.authenticateUserByName(username, password).then(function (result) {
<<<<<<< HEAD
const user = result.User;
const serverId = getParameterByName('serverid');
let newUrl;
@ -26,9 +32,13 @@ import 'emby-checkbox';
newUrl = 'home.html';
}
=======
var user = result.User;
>>>>>>> upstream/master
loading.hide();
Dashboard.onServerChanged(user.Id, result.AccessToken, apiClient);
Dashboard.navigate(newUrl);
Dashboard.navigate('home.html');
}, function (response) {
page.querySelector('#txtManualName').value = '';
page.querySelector('#txtManualPassword').value = '';
@ -204,6 +214,7 @@ import 'emby-checkbox';
});
view.addEventListener('viewshow', function (e) {
loading.show();
libraryMenu.setTransparentMenu(true);
if (!appHost.supports('multiserver')) {
view.querySelector('.btnSelectServer').classList.add('hide');
@ -225,6 +236,14 @@ import 'emby-checkbox';
view.querySelector('.disclaimer').textContent = options.LoginDisclaimer || '';
});
});
<<<<<<< HEAD
}
/* eslint-enable indent */
=======
view.addEventListener('viewhide', function (e) {
libraryMenu.setTransparentMenu(false);
});
};
});
>>>>>>> upstream/master

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
import loading from 'loading';
import appRouter from 'appRouter';
import layoutManager from 'layoutManager';
@ -19,6 +20,12 @@ import 'emby-button';
/* eslint-disable indent */
const enableFocusTransform = !browser.slow && !browser.edge;
=======
define(['loading', 'appRouter', 'layoutManager', 'libraryMenu', 'appSettings', 'apphost', 'focusManager', 'connectionManager', 'globalize', 'actionsheet', 'dom', 'browser', 'material-icons', 'flexStyles', 'emby-scroller', 'emby-itemscontainer', 'cardStyle', 'emby-button'], function (loading, appRouter, layoutManager, libraryMenu, appSettings, appHost, focusManager, connectionManager, globalize, actionSheet, dom, browser) {
'use strict';
var enableFocusTransform = !browser.slow && !browser.edge;
>>>>>>> upstream/master
function renderSelectServerItems(view, servers) {
const items = servers.map(function (server) {
@ -200,6 +207,7 @@ import 'emby-button';
view.addEventListener('viewshow', function (e) {
const isRestored = e.detail.isRestored;
appRouter.setTitle(null);
libraryMenu.setTransparentMenu(true);
if (!isRestored) {
loadServers();

View file

@ -189,6 +189,7 @@ import 'emby-itemscontainer';
function reloadSystemInfo(view, apiClient) {
apiClient.getSystemInfo().then(function (systemInfo) {
view.querySelector('#serverName').innerHTML = globalize.translate('DashboardServerName', systemInfo.ServerName);
<<<<<<< HEAD
let localizedVersion = globalize.translate('DashboardVersionNumber', systemInfo.Version);
if (systemInfo.SystemUpdateLevel !== 'Release') {
@ -196,6 +197,9 @@ import 'emby-itemscontainer';
}
view.querySelector('#versionNumber').innerHTML = localizedVersion;
=======
view.querySelector('#versionNumber').innerHTML = globalize.translate('DashboardVersionNumber', systemInfo.Version);
>>>>>>> upstream/master
view.querySelector('#operatingSystem').innerHTML = globalize.translate('DashboardOperatingSystem', systemInfo.OperatingSystem);
view.querySelector('#architecture').innerHTML = globalize.translate('DashboardArchitecture', systemInfo.SystemArchitecture);

View file

@ -170,7 +170,7 @@ import 'emby-itemrefreshindicator';
showType: false,
showLocations: false,
showMenu: false,
showNameWithIcon: true
showNameWithIcon: false
});
for (let i = 0; i < virtualFolders.length; i++) {
@ -185,7 +185,7 @@ import 'emby-itemrefreshindicator';
$('.btnCardMenu', divVirtualFolders).on('click', function () {
showCardMenu(page, this, virtualFolders);
});
divVirtualFolders.querySelector('.addLibrary').addEventListener('click', function () {
divVirtualFolders.querySelector('#addLibrary').addEventListener('click', function () {
addVirtualFolder(page);
});
$('.editLibrary', divVirtualFolders).on('click', function () {
@ -256,7 +256,12 @@ import 'emby-itemrefreshindicator';
style += 'min-width:33.3%;';
}
html += '<div class="card backdropCard scalableCard backdropCard-scalable" style="' + style + '" data-index="' + index + '" data-id="' + virtualFolder.ItemId + '">';
if (virtualFolder.Locations.length == 0) {
html += '<div id="addLibrary" class="card backdropCard scalableCard backdropCard-scalable" style="' + style + '" data-index="' + index + '" data-id="' + virtualFolder.ItemId + '">';
} else {
html += '<div class="card backdropCard scalableCard backdropCard-scalable" style="' + style + '" data-index="' + index + '" data-id="' + virtualFolder.ItemId + '">';
}
html += '<div class="cardBox visualCardBox">';
html += '<div class="cardScalable visualCardBox-cardScalable">';
html += '<div class="cardPadder cardPadder-backdrop"></div>';

View file

@ -105,7 +105,9 @@ import globalize from 'globalize';
$('#chkEnableSharing', page).prop('checked', user.Policy.EnablePublicSharing);
$('#txtRemoteClientBitrateLimit', page).val(user.Policy.RemoteClientBitrateLimit / 1e6 || '');
$('#txtLoginAttemptsBeforeLockout', page).val(user.Policy.LoginAttemptsBeforeLockout || '0');
$('#selectSyncPlayAccess').val(user.Policy.SyncPlayAccess);
if (ApiClient.isMinServerVersion('10.6.0')) {
$('#selectSyncPlayAccess').val(user.Policy.SyncPlayAccess);
}
loading.hide();
}
@ -147,7 +149,9 @@ import globalize from 'globalize';
}).map(function (c) {
return c.getAttribute('data-id');
});
user.Policy.SyncPlayAccess = page.querySelector('#selectSyncPlayAccess').value;
if (ApiClient.isMinServerVersion('10.6.0')) {
user.Policy.SyncPlayAccess = page.querySelector('#selectSyncPlayAccess').value;
}
ApiClient.updateUser(user).then(function () {
ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
onSaveComplete(page, user);

View file

@ -1,8 +1,5 @@
<div id="itemDetailPage" data-role="page" class="page libraryPage itemDetailPage noSecondaryNavPage selfBackdropPage noBackdrop" data-backbutton="true">
<div id="itemDetailPage" data-role="page" class="page libraryPage itemDetailPage noSecondaryNavPage selfBackdropPage" data-backbutton="true">
<div id="itemBackdrop" class="itemBackdrop">
<button is="emby-button" type="button" class="btnPlay detailFloatingButton hide fab autoSize" title="${ButtonPlay}" data-mode="resume">
<span class="material-icons play_arrow"></span>
</button>
</div>
<div class="detailLogo"></div>
@ -15,94 +12,75 @@
</div>
<div class="mainDetailButtons">
<button is="emby-button" type="button" class="button-flat btnResume hide detailButton detailButtonHideonMobile" data-mode="resume">
<button is="emby-button" type="button" class="button-flat btnResume hide detailButton" title="${ButtonResume}" data-mode="resume">
<div class="detailButton-content">
<span class="material-icons detailButton-icon play_arrow"></span>
<div class="detailButton-text">${ButtonResume}</div>
</div>
</button>
<button is="emby-button" type="button" class="button-flat btnPlay hide detailButton detailButtonHideonMobile" data-mode="play">
<button is="emby-button" type="button" class="button-flat btnPlay hide detailButton" title="${ButtonPlay}" data-mode="play">
<div class="detailButton-content">
<span class="material-icons detailButton-icon play_arrow"></span>
<div class="detailButton-text">${ButtonPlay}</div>
</div>
</button>
<button is="emby-button" type="button" class="button-flat btnDownload hide detailButton">
<button is="emby-button" type="button" class="button-flat btnDownload hide detailButton" title="${ButtonDownload}">
<div class="detailButton-content">
<span class="material-icons detailButton-icon get_app"></span>
<div class="detailButton-text">${ButtonDownload}</div>
</div>
</button>
<button is="emby-button" type="button" class="button-flat btnPlayTrailer hide detailButton">
<button is="emby-button" type="button" class="button-flat btnPlayTrailer hide detailButton" title="${ButtonTrailer}">
<div class="detailButton-content">
<span class="material-icons detailButton-icon theaters"></span>
<div class="detailButton-text">${ButtonTrailer}</div>
</div>
</button>
<button is="emby-button" type="button" class="button-flat btnInstantMix hide detailButton">
<button is="emby-button" type="button" class="button-flat btnInstantMix hide detailButton" title="${HeaderInstantMix}">
<div class="detailButton-content">
<span class="material-icons detailButton-icon explore"></span>
<div class="detailButton-text">${HeaderInstantMix}</div>
</div>
</button>
<button is="emby-button" type="button" class="button-flat btnShuffle hide detailButton">
<button is="emby-button" type="button" class="button-flat btnShuffle hide detailButton" title="${ButtonShuffle}">
<div class="detailButton-content">
<span class="material-icons detailButton-icon shuffle"></span>
<div class="detailButton-text">${ButtonShuffle}</div>
</div>
</button>
<button is="emby-button" type="button" class="button-flat btnCancelSeriesTimer hide detailButton">
<button is="emby-button" type="button" class="button-flat btnCancelSeriesTimer hide detailButton" title="${CancelSeries}">
<div class="detailButton-content">
<span class="material-icons detailButton-icon delete"></span>
<div class="detailButton-text">${CancelSeries}</div>
</div>
</button>
<button is="emby-button" type="button" class="button-flat btnCancelTimer hide detailButton">
<button is="emby-button" type="button" class="button-flat btnCancelTimer hide detailButton" title="${StopRecording}">
<div class="detailButton-content">
<span class="material-icons detailButton-icon stop"></span>
<div class="detailButton-text">${StopRecording}</div>
</div>
</button>
<button is="emby-button" type="button" class="button-flat btnDeleteItem hide detailButton">
<div class="detailButton-content">
<span class="material-icons detailButton-icon delete"></span>
<div class="detailButton-text">${Delete}</div>
</div>
</button>
<button is="emby-playstatebutton" type="button" class="button-flat btnPlaystate hide detailButton">
<button is="emby-playstatebutton" type="button" class="button-flat btnPlaystate hide detailButton" title="">
<div class="detailButton-content">
<span class="material-icons detailButton-icon check"></span>
<div class="detailButton-text button-text"></div>
</div>
</button>
<button is="emby-ratingbutton" type="button" class="button-flat btnUserRating hide detailButton">
<button is="emby-ratingbutton" type="button" class="button-flat btnUserRating hide detailButton" title="${Rate}">
<div class="detailButton-content">
<span class="material-icons detailButton-icon favorite"></span>
<div class="detailButton-text button-text">${Rate}</div>
</div>
</button>
<button is="emby-button" type="button" class="button-flat btnSplitVersions hide detailButton">
<button is="emby-button" type="button" class="button-flat btnSplitVersions hide detailButton" title="${ButtonSplit}">
<div class="detailButton-content">
<span class="material-icons detailButton-icon call_split"></span>
<div class="detailButton-text">${ButtonSplit}</div>
</div>
</button>
<button is="emby-button" type="button" class="button-flat btnMoreCommands hide detailButton">
<button is="emby-button" type="button" class="button-flat btnMoreCommands hide detailButton" title="${ButtonMore}">
<div class="detailButton-content">
<span class="material-icons detailButton-icon more_vert"></span>
<div class="detailButton-text">${ButtonMore}</div>
</div>
</button>
</div>
@ -110,7 +88,7 @@
<div class="detailPageSecondaryContainer">
<div class="detailImageContainer padded-left"></div>
<div class="detailPageContent">
<div class="detailPagePrimaryContent padded-left padded-right">
<div class="detailPagePrimaryContent padded-right">
<div class="detailSection">
<div class="itemMiscInfo nativeName hide"></div>
@ -124,6 +102,11 @@
<div class="directorsLabel label"></div>
<div class="directors content"></div>
</div>
<div class="detailsGroupItem writersGroup hide">
<div class="writersLabel label"></div>
<div class="writers content"></div>
</div>
</div>
<form class="trackSelections hide focuscontainer-x">
@ -164,15 +147,15 @@
</div>
<div class="seriesTimerScheduleSection verticalSection detailVerticalSection hide" style="margin-top:-3em;">
<h2 class="sectionTitle padded-left">${HeaderSchedule}</h2>
<div class="seriesTimerSchedule padded-left padded-right"></div>
<h2 class="sectionTitle">${HeaderSchedule}</h2>
<div class="seriesTimerSchedule padded-right"></div>
</div>
<div class="collectionItems"></div>
<div class="collectionItems hide"></div>
<div class="nextUpSection verticalSection detailVerticalSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderNextUp}</h2>
<div is="emby-itemscontainer" class="nextUpItems vertical-wrap padded-left padded-right"></div>
<h2 class="sectionTitle sectionTitle-cards">${HeaderNextUp}</h2>
<div is="emby-itemscontainer" class="nextUpItems vertical-wrap padded-right"></div>
</div>
<div class="programGuideSection hide verticalSection detailVerticalSection">
@ -180,64 +163,64 @@
</div>
<div id="childrenCollapsible" class="hide verticalSection detailVerticalSection">
<h2 class="childrenSectionHeader sectionTitle sectionTitle-cards padded-left">
<h2 class="childrenSectionHeader sectionTitle sectionTitle-cards hide">
<span id="childrenTitle"></span>
</h2>
<div id="childrenContent">
<div is="emby-itemscontainer" class="childrenItemsContainer itemsContainer padded-left padded-right" style="text-align: left;"></div>
<div is="emby-itemscontainer" class="childrenItemsContainer itemsContainer padded-right" style="text-align: left;"></div>
</div>
</div>
<div id="additionalPartsCollapsible" class="verticalSection detailVerticalSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderAdditionalParts}</h2>
<div id="additionalPartsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right"></div>
<h2 class="sectionTitle sectionTitle-cards padded-right">${HeaderAdditionalParts}</h2>
<div id="additionalPartsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-right"></div>
</div>
<div class="verticalSection itemVerticalSection moreFromSeasonSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right"></h2>
<div class="verticalSection detailVerticalSection moreFromSeasonSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-right"></h2>
<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">
<div is="emby-itemscontainer" class="scrollSlider focuscontainer-x itemsContainer"></div>
</div>
</div>
<div class="verticalSection itemVerticalSection moreFromArtistSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right"></h2>
<div class="verticalSection detailVerticalSection moreFromArtistSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-right"></h2>
<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">
<div is="emby-itemscontainer" class="scrollSlider focuscontainer-x itemsContainer"></div>
</div>
</div>
<div id="castCollapsible" class="verticalSection detailVerticalSection hide">
<h2 id="peopleHeader" class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderCastCrew}</h2>
<h2 id="peopleHeader" class="sectionTitle sectionTitle-cards padded-right">${HeaderCastCrew}</h2>
<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">
<div id="castContent" is="emby-itemscontainer" class="scrollSlider focuscontainer-x itemsContainer"></div>
</div>
</div>
<div id="seriesScheduleSection" class="verticalSection detailVerticalSection hide">
<h2 class="sectionTitle padded-left padded-right">${HeaderUpcomingOnTV}</h2>
<div id="seriesScheduleList" is="emby-itemscontainer" class="itemsContainer vertical-list padded-left padded-right"></div>
<h2 class="sectionTitle padded-right">${HeaderUpcomingOnTV}</h2>
<div id="seriesScheduleList" is="emby-itemscontainer" class="itemsContainer vertical-list padded-right"></div>
</div>
<div id="specialsCollapsible" class="verticalSection detailVerticalSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderSpecialFeatures}</h2>
<div id="specialsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right"></div>
<h2 class="sectionTitle sectionTitle-cards padded-right">${HeaderSpecialFeatures}</h2>
<div id="specialsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-right"></div>
</div>
<div id="musicVideosCollapsible" class="verticalSection detailVerticalSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderMusicVideos}</h2>
<div id="musicVideosContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right"></div>
<h2 class="sectionTitle sectionTitle-cards padded-right">${HeaderMusicVideos}</h2>
<div id="musicVideosContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-right"></div>
</div>
<div id="scenesCollapsible" class="verticalSection itemVerticalSection verticalSection-extrabottompadding hide">
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderScenes}</h2>
<div id="scenesCollapsible" class="verticalSection detailVerticalSection verticalSection-extrabottompadding hide">
<h2 class="sectionTitle sectionTitle-cards padded-right">${HeaderScenes}</h2>
<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">
<div id="scenesContent" is="emby-itemscontainer" class="scrollSlider focuscontainer-x itemsContainer"></div>
</div>
</div>
<div id="similarCollapsible" class="verticalSection itemVerticalSection verticalSection-extrabottompadding hide">
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderMoreLikeThis}</h2>
<div id="similarCollapsible" class="verticalSection detailVerticalSection verticalSection-extrabottompadding hide">
<h2 class="sectionTitle sectionTitle-cards padded-right">${HeaderMoreLikeThis}</h2>
<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">
<div is="emby-itemscontainer" class="scrollSlider focuscontainer-x itemsContainer similarContent"></div>
</div>

View file

@ -28,15 +28,11 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
}
function hideAll(page, className, show) {
var i;
var length;
var elems = page.querySelectorAll('.' + className);
for (i = 0, length = elems.length; i < length; i++) {
for (const elem of page.querySelectorAll('.' + className)) {
if (show) {
elems[i].classList.remove('hide');
elem.classList.remove('hide');
} else {
elems[i].classList.add('hide');
elem.classList.add('hide');
}
}
}
@ -51,17 +47,19 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
positionTo: button,
cancelTimer: false,
record: false,
deleteItem: true === item.IsFolder,
deleteItem: item.CanDelete === true,
shuffle: false,
instantMix: false,
user: user,
share: true
};
return options;
}
function getProgramScheduleHtml(items) {
var html = '';
html += '<div is="emby-itemscontainer" class="itemsContainer vertical-list" data-contextmenu="false">';
html += listView.getListViewHtml({
items: items,
@ -75,7 +73,10 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
moreButton: false,
recordButton: false
});
return html += '</div>';
html += '</div>';
return html;
}
function renderSeriesTimerSchedule(page, apiClient, seriesTimerId) {
@ -143,9 +144,12 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
var mediaSources = item.MediaSources;
instance._currentPlaybackMediaSources = mediaSources;
page.querySelector('.trackSelections').classList.remove('hide');
select.setLabel(globalize.translate('LabelVersion'));
var currentValue = select.value;
var selectedId = mediaSources[0].Id;
select.innerHTML = mediaSources.map(function (v) {
var selected = v.Id === selectedId ? ' selected' : '';
@ -163,7 +167,6 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
renderAudioSelections(page, mediaSources);
renderSubtitleSelections(page, mediaSources);
}
}
function renderVideoSelections(page, mediaSources) {
@ -171,9 +174,11 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
var mediaSource = mediaSources.filter(function (m) {
return m.Id === mediaSourceId;
})[0];
var tracks = mediaSource.MediaStreams.filter(function (m) {
return 'Video' === m.Type;
return m.Type === 'Video';
});
var select = page.querySelector('.selectVideo');
select.setLabel(globalize.translate('LabelVideo'));
var selectedId = tracks.length ? tracks[0].Index : -1;
@ -242,12 +247,24 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
select.setLabel(globalize.translate('LabelSubtitles'));
var selectedId = null == mediaSource.DefaultSubtitleStreamIndex ? -1 : mediaSource.DefaultSubtitleStreamIndex;
if (tracks.length) {
var videoTracks = mediaSource.MediaStreams.filter(function (m) {
return 'Video' === m.Type;
});
// This only makes sense on Video items
if (videoTracks.length) {
var selected = -1 === selectedId ? ' selected' : '';
select.innerHTML = '<option value="-1">' + globalize.translate('Off') + '</option>' + tracks.map(function (v) {
selected = v.Index === selectedId ? ' selected' : '';
return '<option value="' + v.Index + '" ' + selected + '>' + v.DisplayTitle + '</option>';
}).join('');
if (tracks.length > 0) {
select.removeAttribute('disabled');
} else {
select.setAttribute('disabled', 'disabled');
}
page.querySelector('.selectSubtitlesContainer').classList.remove('hide');
} else {
select.innerHTML = '';
@ -278,7 +295,15 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
var enableShuffle = item.IsFolder || -1 !== ['MusicAlbum', 'MusicGenre', 'MusicArtist'].indexOf(item.Type);
hideAll(page, 'btnShuffle', enableShuffle);
canPlay = true;
hideAll(page, 'btnResume', item.UserData && item.UserData.PlaybackPositionTicks > 0);
const isResumable = item.UserData && item.UserData.PlaybackPositionTicks > 0;
hideAll(page, 'btnResume', isResumable);
if (isResumable) {
for (const elem of page.querySelectorAll('.btnPlay')) {
elem.querySelector('.detailButton-icon').classList.replace('play_arrow', 'replay');
}
}
} else {
hideAll(page, 'btnPlay');
hideAll(page, 'btnResume');
@ -324,8 +349,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
function getArtistLinksHtml(artists, serverId, context) {
var html = [];
for (var i = 0, length = artists.length; i < length; i++) {
var artist = artists[i];
for (const artist of artists) {
var href = appRouter.getRouteUrl(artist, {
context: context,
itemType: 'MusicArtist',
@ -333,10 +357,18 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
});
html.push('<a style="color:inherit;" class="button-link" is="emby-linkbutton" href="' + href + '">' + artist.Name + '</a>');
}
html = html.join(' / ');
return html = html.join(' / ');
return html;
}
function renderName(item, container, isStatic, context) {
/**
* Renders the item's name block
* @param {Object} item - Item used to render the name.
* @param {HTMLDivElement} container - Container to render the information into.
* @param {Object} context - Application context.
*/
function renderName(item, container, context) {
var parentRoute;
var parentNameHtml = [];
var parentNameLast = false;
@ -410,16 +442,12 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
if (parentNameLast) {
// Music
if (layoutManager.mobile) {
html = '<h3 class="parentName" style="margin: .25em 0;">' + parentNameHtml.join('</br>') + '</h3>';
html = '<h3 class="parentName musicParentName">' + parentNameHtml.join('</br>') + '</h3>';
} else {
html = '<h3 class="parentName" style="margin: .25em 0;">' + parentNameHtml.join(' - ') + '</h3>';
html = '<h3 class="parentName musicParentName">' + parentNameHtml.join(' - ') + '</h3>';
}
} else {
if (layoutManager.mobile) {
html = '<h1 class="parentName" style="margin: 0.2em 0 0">' + parentNameHtml.join('</br>') + '</h1>';
} else {
html = '<h1 class="parentName" style="margin: 0.2em 0 0">' + tvShowHtml + '</h1>';
}
html = '<h1 class="parentName">' + tvShowHtml + '</h1>';
}
}
@ -428,17 +456,19 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
});
if (html && !parentNameLast) {
if (!layoutManager.mobile && tvSeasonHtml) {
html += '<h3 class="itemName infoText" style="margin: 0.2em 0 0">' + tvSeasonHtml + ' - ' + name + '</h3>';
if (tvSeasonHtml) {
html += '<h3 class="itemName infoText subtitle">' + tvSeasonHtml + ' - ' + name + '</h3>';
} else {
html += '<h3 class="itemName infoText" style="margin: 0.2em 0 0">' + name + '</h3>';
html += '<h3 class="itemName infoText subtitle">' + name + '</h3>';
}
} else if (item.OriginalTitle && item.OriginalTitle != item.Name) {
html = '<h1 class="itemName infoText parentNameLast withOriginalTitle">' + name + '</h1>' + html;
} else {
html = '<h1 class="itemName infoText" style="margin: 0.4em 0 0">' + name + '</h1>' + html;
html = '<h1 class="itemName infoText parentNameLast">' + name + '</h1>' + html;
}
if (item.OriginalTitle && item.OriginalTitle != item.Name) {
html += '<h4 class="itemName infoText" style="margin: 0 0 0;">' + item.OriginalTitle + '</h4>';
html += '<h4 class="itemName infoText originalTitle">' + item.OriginalTitle + '</h4>';
}
container.innerHTML = html;
@ -470,43 +500,18 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
var imgUrl;
var hasbackdrop = false;
var itemBackdropElement = page.querySelector('#itemBackdrop');
var usePrimaryImage = item.MediaType === 'Video' && item.Type !== 'Movie' && item.Type !== 'Trailer' ||
item.MediaType && item.MediaType !== 'Video' ||
item.Type === 'MusicAlbum' ||
item.Type === 'Person';
if (!layoutManager.mobile && !userSettings.detailsBanner()) {
return false;
}
if ('Program' === item.Type && item.ImageTags && item.ImageTags.Thumb) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Thumb',
maxWidth: dom.getScreenWidth(),
index: 0,
tag: item.ImageTags.Thumb
});
page.classList.remove('noBackdrop');
imageLoader.lazyImage(itemBackdropElement, imgUrl);
hasbackdrop = true;
} else if (usePrimaryImage && item.ImageTags && item.ImageTags.Primary) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Primary',
maxWidth: dom.getScreenWidth(),
index: 0,
tag: item.ImageTags.Primary
});
page.classList.remove('noBackdrop');
imageLoader.lazyImage(itemBackdropElement, imgUrl);
hasbackdrop = true;
} else if (item.BackdropImageTags && item.BackdropImageTags.length) {
if (item.BackdropImageTags && item.BackdropImageTags.length) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Backdrop',
maxWidth: dom.getScreenWidth(),
index: 0,
tag: item.BackdropImageTags[0]
});
page.classList.remove('noBackdrop');
imageLoader.lazyImage(itemBackdropElement, imgUrl);
hasbackdrop = true;
} else if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) {
@ -516,50 +521,35 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
index: 0,
tag: item.ParentBackdropImageTags[0]
});
page.classList.remove('noBackdrop');
imageLoader.lazyImage(itemBackdropElement, imgUrl);
hasbackdrop = true;
} else if (item.ImageTags && item.ImageTags.Thumb) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Thumb',
maxWidth: dom.getScreenWidth(),
index: 0,
tag: item.ImageTags.Thumb
});
page.classList.remove('noBackdrop');
imageLoader.lazyImage(itemBackdropElement, imgUrl);
hasbackdrop = true;
} else {
itemBackdropElement.style.backgroundImage = '';
}
if ('Person' === item.Type) {
// FIXME: This hides the backdrop on all persons to fix a margin issue. Ideally, a proper fix should be made.
page.classList.add('noBackdrop');
itemBackdropElement.classList.add('personBackdrop');
} else {
itemBackdropElement.classList.remove('personBackdrop');
}
return hasbackdrop;
}
function reloadFromItem(instance, page, params, item, user) {
var context = params.context;
page.querySelector('.detailPagePrimaryContainer').classList.add('detailSticky');
const apiClient = connectionManager.getApiClient(item.ServerId);
renderName(item, page.querySelector('.nameContainer'), false, context);
var apiClient = connectionManager.getApiClient(item.ServerId);
renderSeriesTimerEditor(page, item, apiClient, user);
renderTimerEditor(page, item, apiClient, user);
renderImage(page, item, apiClient, user);
renderLogo(page, item, apiClient);
Emby.Page.setTitle('');
setInitialCollapsibleState(page, item, apiClient, context, user);
renderDetails(page, item, apiClient, context);
renderTrackSelections(page, instance, item);
// Start rendering the artwork first
renderImage(page, item);
renderLogo(page, item, apiClient);
renderBackdrop(item);
renderDetailPageBackdrop(page, item, apiClient);
// Render the main information for the item
page.querySelector('.detailPagePrimaryContainer').classList.add('detailRibbon');
renderName(item, page.querySelector('.nameContainer'), params.context);
renderDetails(page, item, apiClient, params.context);
renderTrackSelections(page, instance, item);
renderSeriesTimerEditor(page, item, apiClient, user);
renderTimerEditor(page, item, apiClient, user);
setInitialCollapsibleState(page, item, apiClient, params.context, user);
var canPlay = reloadPlayButtons(page, item);
if ((item.LocalTrailerCount || item.RemoteTrailers && item.RemoteTrailers.length) && -1 !== playbackManager.getSupportedCommands().indexOf('PlayTrailers')) {
@ -570,12 +560,6 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
setTrailerButtonVisibility(page, item);
if (item.CanDelete && !item.IsFolder) {
hideAll(page, 'btnDeleteItem', true);
} else {
hideAll(page, 'btnDeleteItem');
}
if ('Program' !== item.Type || canPlay) {
hideAll(page, 'mainDetailButtons', true);
} else {
@ -667,18 +651,15 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
}
function renderLogo(page, item, apiClient) {
var url = logoImageUrl(item, apiClient, {
maxWidth: 400
});
var detailLogo = page.querySelector('.detailLogo');
var url = logoImageUrl(item, apiClient, {});
if (!layoutManager.mobile && !userSettings.enableBackdrops()) {
detailLogo.classList.add('hide');
} else if (url) {
detailLogo.classList.remove('hide');
detailLogo.classList.add('lazy');
detailLogo.setAttribute('data-src', url);
imageLoader.lazyImage(detailLogo);
imageLoader.setLazyImage(detailLogo, url);
} else {
detailLogo.classList.add('hide');
}
@ -704,172 +685,59 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
}
}
function renderLinks(linksElem, item) {
var html = [];
function renderLinks(page, item) {
var externalLinksElem = page.querySelector('.itemExternalLinks');
var links = [];
if (!layoutManager.tv && item.HomePageUrl) {
links.push('<a style="color:inherit;" is="emby-linkbutton" class="button-link" href="' + item.HomePageUrl + '" target="_blank">' + globalize.translate('ButtonWebsite') + '</a>');
links.push(`<a is="emby-linkbutton" class="button-link" href="${item.HomePageUrl}" target="_blank">${globalize.translate('ButtonWebsite')}</a>`);
}
if (item.ExternalUrls) {
for (var i = 0, length = item.ExternalUrls.length; i < length; i++) {
var url = item.ExternalUrls[i];
links.push('<a style="color:inherit;" is="emby-linkbutton" class="button-link" href="' + url.Url + '" target="_blank">' + url.Name + '</a>');
for (const url of item.ExternalUrls) {
links.push(`<a is="emby-linkbutton" class="button-link" href="${url.Url}" target="_blank">${url.Name}</a>`);
}
}
var html = [];
if (links.length) {
html.push(links.join(', '));
}
linksElem.innerHTML = html.join(', ');
externalLinksElem.innerHTML = html.join(', ');
if (html.length) {
linksElem.classList.remove('hide');
externalLinksElem.classList.remove('hide');
} else {
linksElem.classList.add('hide');
externalLinksElem.classList.add('hide');
}
}
function renderDetailImage(page, elem, item, apiClient, editable, imageLoader, indicators) {
if ('SeriesTimer' === item.Type || 'Program' === item.Type) {
editable = false;
}
function renderDetailImage(elem, item, imageLoader) {
const itemArray = [];
itemArray.push(item);
const cardHtml = cardBuilder.getCardsHtml(itemArray, {
shape: 'auto',
showTitle: false,
centerText: true,
overlayText: false,
transition: false,
disableIndicators: true,
disableHoverMenu: true,
overlayPlayButton: true,
width: dom.getWindowSize().innerWidth * 0.5
});
elem.classList.add('detailimg-hidemobile');
var imageTags = item.ImageTags || {};
if (item.PrimaryImageTag) {
imageTags.Primary = item.PrimaryImageTag;
}
var url;
var html = '';
var shape = 'portrait';
var detectRatio = false;
/* In the following section, getScreenWidth() is multiplied by 0.5 as the posters
are 25vw and we need double the resolution to counter Skia's scaling. */
// TODO: Find a reliable way to get the poster width
if (imageTags.Primary) {
url = apiClient.getScaledImageUrl(item.Id, {
type: 'Primary',
maxWidth: Math.round(dom.getScreenWidth() * 0.5),
tag: item.ImageTags.Primary
});
detectRatio = true;
} else if (item.BackdropImageTags && item.BackdropImageTags.length) {
url = apiClient.getScaledImageUrl(item.Id, {
type: 'Backdrop',
maxWidth: Math.round(dom.getScreenWidth() * 0.5),
tag: item.BackdropImageTags[0]
});
shape = 'thumb';
} else if (imageTags.Thumb) {
url = apiClient.getScaledImageUrl(item.Id, {
type: 'Thumb',
maxWidth: Math.round(dom.getScreenWidth() * 0.5),
tag: item.ImageTags.Thumb
});
shape = 'thumb';
} else if (imageTags.Disc) {
url = apiClient.getScaledImageUrl(item.Id, {
type: 'Disc',
maxWidth: Math.round(dom.getScreenWidth() * 0.5),
tag: item.ImageTags.Disc
});
shape = 'square';
} else if (item.AlbumId && item.AlbumPrimaryImageTag) {
url = apiClient.getScaledImageUrl(item.AlbumId, {
type: 'Primary',
maxWidth: Math.round(dom.getScreenWidth() * 0.5),
tag: item.AlbumPrimaryImageTag
});
shape = 'square';
} else if (item.SeriesId && item.SeriesPrimaryImageTag) {
url = apiClient.getScaledImageUrl(item.SeriesId, {
type: 'Primary',
maxWidth: Math.round(dom.getScreenWidth() * 0.5),
tag: item.SeriesPrimaryImageTag
});
} else if (item.ParentPrimaryImageItemId && item.ParentPrimaryImageTag) {
url = apiClient.getScaledImageUrl(item.ParentPrimaryImageItemId, {
type: 'Primary',
maxWidth: Math.round(dom.getScreenWidth() * 0.5),
tag: item.ParentPrimaryImageTag
});
}
if (editable && url === undefined) {
html += "<a class='itemDetailGalleryLink itemDetailImage defaultCardBackground defaultCardBackground" + cardBuilder.getDefaultBackgroundClass(item.Name) + "' is='emby-linkbutton' style='display:block;margin:0;padding:0;' href='#'>";
} else if (!editable && url === undefined) {
html += "<div class='itemDetailGalleryLink itemDetailImage defaultCardBackground defaultCardBackground" + cardBuilder.getDefaultBackgroundClass(item.Name) + "' is='emby-linkbutton' style='display:block;margin:0;padding:0;' href='#'>";
} else if (editable) {
html += "<a class='itemDetailGalleryLink' is='emby-linkbutton' style='display:block;margin:0;padding:0;' href='#'>";
}
if (url) {
html += "<img class='itemDetailImage lazy' src='data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' />";
}
if (url === undefined) {
html += cardBuilder.getDefaultText(item);
}
if (editable) {
html += '</a>';
} else if (!editable && url === undefined) {
html += '</div>';
}
var progressHtml = item.IsFolder || !item.UserData ? '' : indicators.getProgressBarHtml(item);
html += '<div class="detailImageProgressContainer">';
if (progressHtml) {
html += progressHtml;
}
html += '</div>';
elem.innerHTML = html;
if (detectRatio && item.PrimaryImageAspectRatio) {
if (item.PrimaryImageAspectRatio >= 1.48) {
shape = 'thumb';
} else if (item.PrimaryImageAspectRatio >= 0.85 && item.PrimaryImageAspectRatio <= 1.34) {
shape = 'square';
}
}
if ('thumb' == shape) {
elem.classList.add('thumbDetailImageContainer');
elem.classList.remove('portraitDetailImageContainer');
elem.classList.remove('squareDetailImageContainer');
} else if ('square' == shape) {
elem.classList.remove('thumbDetailImageContainer');
elem.classList.remove('portraitDetailImageContainer');
elem.classList.add('squareDetailImageContainer');
} else {
elem.classList.remove('thumbDetailImageContainer');
elem.classList.add('portraitDetailImageContainer');
elem.classList.remove('squareDetailImageContainer');
}
if (url) {
imageLoader.lazyImage(elem.querySelector('img'), url);
}
elem.innerHTML = cardHtml;
imageLoader.lazyChildren(elem);
}
function renderImage(page, item, apiClient, user) {
function renderImage(page, item) {
renderDetailImage(
page,
page.querySelector('.detailImageContainer'),
item,
apiClient,
user.Policy.IsAdministrator && 'Photo' != item.MediaType,
imageLoader,
indicators
imageLoader
);
}
@ -985,54 +853,41 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
}
}
function renderOverview(elems, item) {
for (var i = 0, length = elems.length; i < length; i++) {
var elem = elems[i];
function renderOverview(page, item) {
for (const overviewElemnt of page.querySelectorAll('.overview')) {
var overview = item.Overview || '';
if (overview) {
elem.innerHTML = overview;
elem.classList.remove('hide');
elem.classList.add('detail-clamp-text');
overviewElemnt.innerHTML = overview;
overviewElemnt.classList.remove('hide');
overviewElemnt.classList.add('detail-clamp-text');
// Grab the sibling element to control the expand state
var expandButton = elem.parentElement.querySelector('.overview-expand');
var expandButton = overviewElemnt.parentElement.querySelector('.overview-expand');
// Detect if we have overflow of text. Based on this StackOverflow answer
// https://stackoverflow.com/a/35157976
if (Math.abs(elem.scrollHeight - elem.offsetHeight) > 2) {
if (Math.abs(overviewElemnt.scrollHeight - overviewElemnt.offsetHeight) > 2) {
expandButton.classList.remove('hide');
} else {
expandButton.classList.add('hide');
}
expandButton.addEventListener('click', toggleLineClamp.bind(null, elem));
expandButton.addEventListener('click', toggleLineClamp.bind(null, overviewElemnt));
var anchors = elem.querySelectorAll('a');
for (var j = 0, length2 = anchors.length; j < length2; j++) {
anchors[j].setAttribute('target', '_blank');
for (const anchor of overviewElemnt.querySelectorAll('a')) {
anchor.setAttribute('target', '_blank');
}
} else {
elem.innerHTML = '';
elem.classList.add('hide');
overviewElemnt.innerHTML = '';
overviewElemnt.classList.add('hide');
}
}
}
function renderGenres(page, item, context) {
context = context || inferContext(item);
var type;
function renderGenres(page, item, context = inferContext(item)) {
var genres = item.GenreItems || [];
switch (context) {
case 'music':
type = 'MusicGenre';
break;
default:
type = 'Genre';
}
var type = context === 'music' ? 'MusicGenre' : 'Genre';
var html = genres.map(function (p) {
return '<a style="color:inherit;" class="button-link" is="emby-linkbutton" href="' + appRouter.getRouteUrl({
@ -1058,19 +913,49 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
}
}
function renderDirector(page, item, context) {
var directors = (item.People || []).filter(function (p) {
return 'Director' === p.Type;
function renderWriter(page, item, context) {
var writers = (item.People || []).filter(function (person) {
return person.Type === 'Writer';
});
var html = directors.map(function (p) {
var html = writers.map(function (person) {
return '<a style="color:inherit;" class="button-link" is="emby-linkbutton" href="' + appRouter.getRouteUrl({
Name: p.Name,
Name: person.Name,
Type: 'Person',
ServerId: item.ServerId,
Id: p.Id
Id: person.Id
}, {
context: context
}) + '">' + p.Name + '</a>';
}) + '">' + person.Name + '</a>';
}).join(', ');
var writersLabel = page.querySelector('.writersLabel');
writersLabel.innerHTML = globalize.translate(writers.length > 1 ? 'Writers' : 'Writer');
var writersValue = page.querySelector('.writers');
writersValue.innerHTML = html;
var writersGroup = page.querySelector('.writersGroup');
if (writers.length) {
writersGroup.classList.remove('hide');
} else {
writersGroup.classList.add('hide');
}
}
function renderDirector(page, item, context) {
var directors = (item.People || []).filter(function (person) {
return person.Type === 'Director';
});
var html = directors.map(function (person) {
return '<a style="color:inherit;" class="button-link" is="emby-linkbutton" href="' + appRouter.getRouteUrl({
Name: person.Name,
Type: 'Person',
ServerId: item.ServerId,
Id: person.Id
}, {
context: context
}) + '">' + person.Name + '</a>';
}).join(', ');
var directorsLabel = page.querySelector('.directorsLabel');
@ -1086,13 +971,39 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
}
}
function renderDetails(page, item, apiClient, context, isStatic) {
renderSimilarItems(page, item, context);
renderMoreFromSeason(page, item, apiClient);
renderMoreFromArtist(page, item, apiClient);
renderDirector(page, item, context);
renderGenres(page, item, context);
renderChannelGuide(page, apiClient, item);
function renderMiscInfo(page, item) {
const primaryItemMiscInfo = page.querySelectorAll('.itemMiscInfo-primary');
for (const miscInfo of primaryItemMiscInfo) {
mediaInfo.fillPrimaryMediaInfo(miscInfo, item, {
interactive: true,
episodeTitle: false,
subtitles: false
});
if (miscInfo.innerHTML && 'SeriesTimer' !== item.Type) {
miscInfo.classList.remove('hide');
} else {
miscInfo.classList.add('hide');
}
}
const secondaryItemMiscInfo = page.querySelectorAll('.itemMiscInfo-secondary');
for (const miscInfo of secondaryItemMiscInfo) {
mediaInfo.fillSecondaryMediaInfo(miscInfo, item, {
interactive: true
});
if (miscInfo.innerHTML && 'SeriesTimer' !== item.Type) {
miscInfo.classList.remove('hide');
} else {
miscInfo.classList.add('hide');
}
}
}
function renderTagline(page, item) {
var taglineElement = page.querySelector('.tagline');
if (item.Taglines && item.Taglines.length) {
@ -1101,44 +1012,21 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
} else {
taglineElement.classList.add('hide');
}
}
var overview = page.querySelector('.overview');
var externalLinksElem = page.querySelector('.itemExternalLinks');
renderOverview([overview], item);
var i;
var itemMiscInfo;
itemMiscInfo = page.querySelectorAll('.itemMiscInfo-primary');
for (i = 0; i < itemMiscInfo.length; i++) {
mediaInfo.fillPrimaryMediaInfo(itemMiscInfo[i], item, {
interactive: true,
episodeTitle: false,
subtitles: false
});
if (itemMiscInfo[i].innerHTML && 'SeriesTimer' !== item.Type) {
itemMiscInfo[i].classList.remove('hide');
} else {
itemMiscInfo[i].classList.add('hide');
}
}
itemMiscInfo = page.querySelectorAll('.itemMiscInfo-secondary');
for (i = 0; i < itemMiscInfo.length; i++) {
mediaInfo.fillSecondaryMediaInfo(itemMiscInfo[i], item, {
interactive: true
});
if (itemMiscInfo[i].innerHTML && 'SeriesTimer' !== item.Type) {
itemMiscInfo[i].classList.remove('hide');
} else {
itemMiscInfo[i].classList.add('hide');
}
}
function renderDetails(page, item, apiClient, context, isStatic) {
renderSimilarItems(page, item, context);
renderMoreFromSeason(page, item, apiClient);
renderMoreFromArtist(page, item, apiClient);
renderDirector(page, item, context);
renderWriter(page, item, context);
renderGenres(page, item, context);
renderChannelGuide(page, apiClient, item);
renderTagline(page, item);
renderOverview(page, item);
renderMiscInfo(page, item);
reloadUserDataButtons(page, item);
renderLinks(externalLinksElem, item);
renderLinks(page, item);
renderTags(page, item);
renderSeriesAirTime(page, item, isStatic);
}
@ -1426,8 +1314,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
action: 'playallfromhere',
image: false,
artist: 'auto',
containerAlbumArtists: item.AlbumArtists,
addToListButton: true
containerAlbumArtists: item.AlbumArtists
});
isList = true;
} else if ('Series' == item.Type) {
@ -1469,11 +1356,12 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
items: result.Items,
showIndexNumber: false,
enableOverview: true,
enablePlayedButton: layoutManager.mobile ? false : true,
infoButton: layoutManager.mobile ? false : true,
imageSize: 'large',
enableSideMediaInfo: false,
highlight: false,
action: layoutManager.tv ? 'resume' : 'none',
infoButton: true,
action: !layoutManager.desktop ? 'link' : 'none',
imagePlayButton: true,
includeParentInfoInTitle: false
});
@ -1500,6 +1388,9 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
childrenItemsContainer.classList.remove('vertical-list');
}
}
if (layoutManager.mobile) {
childrenItemsContainer.classList.remove('padded-right');
}
childrenItemsContainer.innerHTML = html;
imageLoader.lazyChildren(childrenItemsContainer);
if ('BoxSet' == item.Type) {
@ -1701,12 +1592,10 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
}
function renderCollectionItems(page, parentItem, types, items) {
page.querySelector('.collectionItems').classList.remove('hide');
page.querySelector('.collectionItems').innerHTML = '';
var i;
var length;
for (i = 0, length = types.length; i < length; i++) {
var type = types[i];
for (const type of types) {
var typeItems = filterItemsByCollectionItemType(items, type);
if (typeItems.length) {
@ -1739,8 +1628,8 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
renderChildren(page, parentItem);
};
for (i = 0, length = containers.length; i < length; i++) {
containers[i].notifyRefreshNeeded = notifyRefreshNeeded;
for (const container of containers) {
container.notifyRefreshNeeded = notifyRefreshNeeded;
}
// if nothing in the collection can be played hide play and shuffle buttons
@ -1876,7 +1765,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
function renderCast(page, item) {
var people = (item.People || []).filter(function (p) {
return 'Director' !== p.Type;
return p.Type === 'Actor';
});
if (!people.length) {
@ -1905,12 +1794,10 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
}
function bindAll(view, selector, eventName, fn) {
var i;
var length;
var elems = view.querySelectorAll(selector);
for (i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener(eventName, fn);
for (const elem of elems) {
elem.addEventListener(eventName, fn);
}
}
@ -1923,13 +1810,14 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
return function (view, params) {
function reload(instance, page, params) {
loading.show();
var apiClient = params.serverId ? connectionManager.getApiClient(params.serverId) : ApiClient;
var promises = [getPromise(apiClient, params), apiClient.getCurrentUser()];
Promise.all(promises).then(function (responses) {
var item = responses[0];
var user = responses[1];
Promise.all([getPromise(apiClient, params), apiClient.getCurrentUser()]).then(([item, user]) => {
currentItem = item;
reloadFromItem(instance, page, params, item, user);
}).catch((error) => {
console.error('failed to get item or current user: ', error);
});
}
@ -1980,7 +1868,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
});
}
playItem(item, item.UserData && 'resume' === mode ? item.UserData.PlaybackPositionTicks : 0);
playItem(item, item.UserData && mode === 'resume' ? item.UserData.PlaybackPositionTicks : 0);
}
function onPlayClick() {
@ -1995,15 +1883,6 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
playbackManager.shuffle(currentItem);
}
function onDeleteClick() {
require(['deleteHelper'], function (deleteHelper) {
deleteHelper.deleteItem({
item: currentItem,
navigate: true
});
});
}
function onCancelSeriesTimerClick() {
require(['recordingHelper'], function (recordingHelper) {
recordingHelper.cancelSeriesTimerWithConfirmation(currentItem.Id, currentItem.ServerId).then(function () {
@ -2092,7 +1971,6 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
bindAll(view, '.btnPlayTrailer', 'click', onPlayTrailerClick);
bindAll(view, '.btnCancelSeriesTimer', 'click', onCancelSeriesTimerClick);
bindAll(view, '.btnCancelTimer', 'click', onCancelTimerClick);
bindAll(view, '.btnDeleteItem', 'click', onDeleteClick);
bindAll(view, '.btnDownload', 'click', onDownloadClick);
view.querySelector('.trackSelections').addEventListener('submit', onTrackSelectionsSubmit);
view.querySelector('.btnSplitVersions').addEventListener('click', function () {
@ -2125,9 +2003,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
view.addEventListener('viewshow', function (e) {
var page = this;
if (layoutManager.mobile) {
libraryMenu.setTransparentMenu(true);
}
libraryMenu.setTransparentMenu(true);
if (e.detail.isRestored) {
if (currentItem) {

View file

@ -1,22 +1,22 @@
define(['components/remotecontrol/remotecontrol', 'libraryMenu', 'emby-button'], function (remotecontrolFactory, libraryMenu) {
'use strict';
import remotecontrolFactory from 'components/remotecontrol/remotecontrol';
import libraryMenu from 'libraryMenu';
import 'emby-button';
return function (view, params) {
var remoteControl = new remotecontrolFactory();
remoteControl.init(view, view.querySelector('.remoteControlContent'));
view.addEventListener('viewshow', function (e) {
libraryMenu.setTransparentMenu(true);
export default function (view, params) {
const remoteControl = new remotecontrolFactory();
remoteControl.init(view, view.querySelector('.remoteControlContent'));
view.addEventListener('viewshow', function (e) {
libraryMenu.setTransparentMenu(true);
if (remoteControl) {
remoteControl.onShow();
}
});
view.addEventListener('viewbeforehide', function (e) {
libraryMenu.setTransparentMenu(false);
if (remoteControl) {
remoteControl.onShow();
}
});
view.addEventListener('viewbeforehide', function (e) {
libraryMenu.setTransparentMenu(false);
if (remoteControl) {
remoteControl.destroy();
}
});
};
});
if (remoteControl) {
remoteControl.destroy();
}
});
}

View file

@ -1,5 +1,26 @@
define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'mediaInfo', 'focusManager', 'imageLoader', 'scrollHelper', 'events', 'connectionManager', 'browser', 'globalize', 'apphost', 'layoutManager', 'userSettings', 'keyboardnavigation', 'scrollStyles', 'emby-slider', 'paper-icon-button-light', 'css!assets/css/videoosd'], function (playbackManager, dom, inputManager, datetime, itemHelper, mediaInfo, focusManager, imageLoader, scrollHelper, events, connectionManager, browser, globalize, appHost, layoutManager, userSettings, keyboardnavigation) {
'use strict';
import playbackManager from 'playbackManager';
import dom from 'dom';
import inputManager from 'inputManager';
import datetime from 'datetime';
import itemHelper from 'itemHelper';
import mediaInfo from 'mediaInfo';
import focusManager from 'focusManager';
import imageLoader from 'imageLoader';
import scrollHelper from 'scrollHelper';
import events from 'events';
import connectionManager from 'connectionManager';
import browser from 'browser';
import globalize from 'globalize';
import appHost from 'apphost';
import layoutManager from 'layoutManager';
import * as userSettings from 'userSettings';
import keyboardnavigation from 'keyboardnavigation';
import 'scrollStyles';
import 'emby-slider';
import 'paper-icon-button-light';
import 'css!assets/css/videoosd';
/* eslint-disable indent */
function seriesImageUrl(item, options) {
if ('Episode' !== item.Type) {
@ -45,13 +66,13 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
return null;
}
return function (view, params) {
export default function (view, params) {
function onVerticalSwipe(e, elem, data) {
var player = currentPlayer;
const player = currentPlayer;
if (player) {
var deltaY = data.currentDeltaY;
var windowSize = dom.getWindowSize();
const deltaY = data.currentDeltaY;
const windowSize = dom.getWindowSize();
if (supportsBrightnessChange && data.clientX < windowSize.innerWidth / 2) {
return void doBrightnessTouch(deltaY, player, windowSize.innerHeight);
@ -62,23 +83,23 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function doBrightnessTouch(deltaY, player, viewHeight) {
var delta = -deltaY / viewHeight * 100;
var newValue = playbackManager.getBrightness(player) + delta;
const delta = -deltaY / viewHeight * 100;
let newValue = playbackManager.getBrightness(player) + delta;
newValue = Math.min(newValue, 100);
newValue = Math.max(newValue, 0);
playbackManager.setBrightness(newValue, player);
}
function doVolumeTouch(deltaY, player, viewHeight) {
var delta = -deltaY / viewHeight * 100;
var newValue = playbackManager.getVolume(player) + delta;
const delta = -deltaY / viewHeight * 100;
let newValue = playbackManager.getVolume(player) + delta;
newValue = Math.min(newValue, 100);
newValue = Math.max(newValue, 0);
playbackManager.setVolume(newValue, player);
}
function onDoubleClick(e) {
var clientX = e.clientX;
const clientX = e.clientX;
if (null != clientX) {
if (clientX < dom.getWindowSize().innerWidth / 2) {
@ -94,7 +115,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
function getDisplayItem(item) {
if ('TvChannel' === item.Type) {
var apiClient = connectionManager.getApiClient(item.ServerId);
const apiClient = connectionManager.getApiClient(item.ServerId);
return apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (refreshedItem) {
return {
originalItem: refreshedItem,
@ -120,7 +141,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
connectionManager.getApiClient(item.ServerId).getCurrentUser().then(function (user) {
if (user.Policy.EnableLiveTvManagement) {
require(['recordingButton'], function (RecordingButton) {
import('recordingButton').then(({default: RecordingButton}) => {
if (recordingButtonManager) {
return void recordingButtonManager.refreshItem(item);
}
@ -136,22 +157,22 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function updateDisplayItem(itemInfo) {
var item = itemInfo.originalItem;
const item = itemInfo.originalItem;
currentItem = item;
var displayItem = itemInfo.displayItem || item;
const displayItem = itemInfo.displayItem || item;
updateRecordingButton(displayItem);
setPoster(displayItem, item);
var parentName = displayItem.SeriesName || displayItem.Album;
let parentName = displayItem.SeriesName || displayItem.Album;
if (displayItem.EpisodeTitle || displayItem.IsSeries) {
parentName = displayItem.Name;
}
setTitle(displayItem, parentName);
var titleElement;
var osdTitle = view.querySelector('.osdTitle');
let titleElement;
const osdTitle = view.querySelector('.osdTitle');
titleElement = osdTitle;
var displayName = itemHelper.getDisplayName(displayItem, {
let displayName = itemHelper.getDisplayName(displayItem, {
includeParentInfo: 'Program' !== displayItem.Type,
includeIndexNumber: 'Program' !== displayItem.Type
});
@ -168,7 +189,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
titleElement.classList.add('hide');
}
var mediaInfoHtml = mediaInfo.getPrimaryMediaInfoHtml(displayItem, {
const mediaInfoHtml = mediaInfo.getPrimaryMediaInfoHtml(displayItem, {
runtime: false,
subtitles: false,
tomatoes: false,
@ -178,7 +199,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
episodeTitleIndexNumber: 'Program' !== displayItem.Type,
programIndicator: false
});
var osdMediaInfo = view.querySelector('.osdMediaInfo');
const osdMediaInfo = view.querySelector('.osdMediaInfo');
osdMediaInfo.innerHTML = mediaInfoHtml;
if (mediaInfoHtml) {
@ -187,8 +208,8 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
osdMediaInfo.classList.add('hide');
}
var secondaryMediaInfo = view.querySelector('.osdSecondaryMediaInfo');
var secondaryMediaInfoHtml = mediaInfo.getSecondaryMediaInfoHtml(displayItem, {
const secondaryMediaInfo = view.querySelector('.osdSecondaryMediaInfo');
const secondaryMediaInfoHtml = mediaInfo.getSecondaryMediaInfoHtml(displayItem, {
startDate: false,
programTime: false
});
@ -236,7 +257,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function setDisplayTime(elem, date) {
var html;
let html;
if (date) {
date = datetime.parseISO8601Date(date);
@ -251,7 +272,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function updateNowPlayingInfo(player, state) {
var item = state.NowPlayingItem;
const item = state.NowPlayingItem;
currentItem = item;
if (!item) {
@ -294,7 +315,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
function setTitle(item, parentName) {
Emby.Page.setTitle(parentName || '');
var documentTitle = parentName || (item ? item.Name : null);
const documentTitle = parentName || (item ? item.Name : null);
if (documentTitle) {
document.title = documentTitle;
@ -302,10 +323,10 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function setPoster(item, secondaryItem) {
var osdPoster = view.querySelector('.osdPoster');
const osdPoster = view.querySelector('.osdPoster');
if (item) {
var imgUrl = seriesImageUrl(item, {
let imgUrl = seriesImageUrl(item, {
maxWidth: osdPoster.clientWidth * 2,
type: 'Primary'
}) || seriesImageUrl(item, {
@ -333,13 +354,21 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
osdPoster.innerHTML = '';
}
let osdLockCount = 0;
function showOsd() {
slideDownToShow(headerElement);
showMainOsdControls();
startOsdHideTimer();
if (!osdLockCount) {
startOsdHideTimer();
}
}
function hideOsd() {
if (osdLockCount) {
return;
}
slideUpToHide(headerElement);
hideMainOsdControls();
}
@ -352,6 +381,19 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
}
function lockOsd() {
osdLockCount++;
stopOsdHideTimer();
}
function unlockOsd() {
osdLockCount--;
// Restart hide timer if OSD is currently visible
if (currentVisibleMenu && !osdLockCount) {
startOsdHideTimer();
}
}
function startOsdHideTimer() {
stopOsdHideTimer();
osdHideTimeout = setTimeout(hideOsd, 3e3);
@ -379,7 +421,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function onHideAnimationComplete(e) {
var elem = e.target;
const elem = e.target;
if (elem != osdBottomElement)
return;
elem.classList.add('hide');
@ -390,7 +432,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
function showMainOsdControls() {
if (!currentVisibleMenu) {
var elem = osdBottomElement;
const elem = osdBottomElement;
currentVisibleMenu = 'osd';
clearHideAnimationEventListeners(elem);
elem.classList.remove('hide');
@ -407,7 +449,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
function hideMainOsdControls() {
if ('osd' === currentVisibleMenu) {
var elem = osdBottomElement;
const elem = osdBottomElement;
clearHideAnimationEventListeners(elem);
elem.classList.add('videoOsdBottom-hidden');
dom.addEventListener(elem, transitionEndEventName, onHideAnimationComplete, {
@ -425,9 +467,9 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
function onPointerMove(e) {
if ('mouse' === (e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'))) {
var eventX = e.screenX || 0;
var eventY = e.screenY || 0;
var obj = lastPointerMoveData;
const eventX = e.screenX || 0;
const eventY = e.screenY || 0;
const obj = lastPointerMoveData;
if (!obj) {
lastPointerMoveData = {
@ -448,7 +490,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function onInputCommand(e) {
var player = currentPlayer;
const player = currentPlayer;
switch (e.detail.command) {
case 'left':
@ -507,7 +549,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function onRecordingCommand() {
var btnRecord = view.querySelector('.btnRecord');
const btnRecord = view.querySelector('.btnRecord');
if (!btnRecord.classList.contains('hide')) {
btnRecord.click();
@ -534,7 +576,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function onStateChanged(event, state) {
var player = this;
const player = this;
if (state.NowPlayingItem) {
isEnabled = true;
@ -552,21 +594,21 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
function onVolumeChanged(e) {
if (isEnabled) {
var player = this;
const player = this;
updatePlayerVolumeState(player, player.isMuted(), player.getVolume());
}
}
function onPlaybackStart(e, state) {
console.debug('nowplaying event: ' + e.type);
var player = this;
const player = this;
onStateChanged.call(player, e, state);
resetUpNextDialog();
}
function resetUpNextDialog() {
comingUpNextDisplayed = false;
var dlg = currentUpNextDialog;
const dlg = currentUpNextDialog;
if (dlg) {
dlg.destroy();
@ -586,8 +628,8 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function onMediaStreamsChanged(e) {
var player = this;
var state = playbackManager.getPlayerState(player);
const player = this;
const state = playbackManager.getPlayerState(player);
onStateChanged.call(player, {
type: 'init'
}, state);
@ -607,7 +649,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
currentPlayer = player;
if (!player) return;
}
var state = playbackManager.getPlayerState(player);
const state = playbackManager.getPlayerState(player);
onStateChanged.call(player, {
type: 'init'
}, state);
@ -632,7 +674,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
destroyStats();
destroySubtitleSync();
resetUpNextDialog();
var player = currentPlayer;
const player = currentPlayer;
if (player) {
events.off(player, 'playbackstart', onPlaybackStart);
@ -650,15 +692,15 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
function onTimeUpdate(e) {
// Test for 'currentItem' is required for Firefox since its player spams 'timeupdate' events even being at breakpoint
if (isEnabled && currentItem) {
var now = new Date().getTime();
const now = new Date().getTime();
if (!(now - lastUpdateTime < 700)) {
lastUpdateTime = now;
var player = this;
const player = this;
currentRuntimeTicks = playbackManager.duration(player);
var currentTime = playbackManager.currentTime(player);
const currentTime = playbackManager.currentTime(player);
updateTimeDisplay(currentTime, currentRuntimeTicks, playbackManager.playbackStartTime(player), playbackManager.getBufferedRanges(player));
var item = currentItem;
const item = currentItem;
refreshProgramInfoIfNeeded(player, item);
showComingUpNextIfNeeded(player, item, currentTime, currentRuntimeTicks);
}
@ -667,9 +709,9 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
function showComingUpNextIfNeeded(player, currentItem, currentTimeTicks, runtimeTicks) {
if (runtimeTicks && currentTimeTicks && !comingUpNextDisplayed && !currentVisibleMenu && 'Episode' === currentItem.Type && userSettings.enableNextVideoInfoOverlay()) {
var showAtSecondsLeft = runtimeTicks >= 3e10 ? 40 : runtimeTicks >= 24e9 ? 35 : 30;
var showAtTicks = runtimeTicks - 1e3 * showAtSecondsLeft * 1e4;
var timeRemainingTicks = runtimeTicks - currentTimeTicks;
const showAtSecondsLeft = runtimeTicks >= 3e10 ? 40 : runtimeTicks >= 24e9 ? 35 : 30;
const showAtTicks = runtimeTicks - 1e3 * showAtSecondsLeft * 1e4;
const timeRemainingTicks = runtimeTicks - currentTimeTicks;
if (currentTimeTicks >= showAtTicks && runtimeTicks >= 6e9 && timeRemainingTicks >= 2e8) {
showComingUpNext(player);
@ -684,7 +726,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function showComingUpNext(player) {
require(['upNextDialog'], function (UpNextDialog) {
import('upNextDialog').then(({default: UpNextDialog}) => {
if (!(currentVisibleMenu || currentUpNextDialog)) {
currentVisibleMenu = 'upnext';
comingUpNextDisplayed = true;
@ -702,15 +744,15 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
function refreshProgramInfoIfNeeded(player, item) {
if ('TvChannel' === item.Type) {
var program = item.CurrentProgram;
const program = item.CurrentProgram;
if (program && program.EndDate) {
try {
var endDate = datetime.parseISO8601Date(program.EndDate);
const endDate = datetime.parseISO8601Date(program.EndDate);
if (new Date().getTime() >= endDate.getTime()) {
console.debug('program info needs to be refreshed');
var state = playbackManager.getPlayerState(player);
const state = playbackManager.getPlayerState(player);
onStateChanged.call(player, {
type: 'init'
}, state);
@ -738,9 +780,9 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function updatePlayerStateInternal(event, player, state) {
var playState = state.PlayState || {};
const playState = state.PlayState || {};
updatePlayPauseState(playState.IsPaused);
var supportedCommands = playbackManager.getSupportedCommands(player);
const supportedCommands = playbackManager.getSupportedCommands(player);
currentPlayerSupportedCommands = supportedCommands;
supportsBrightnessChange = -1 !== supportedCommands.indexOf('SetBrightness');
updatePlayerVolumeState(player, playState.IsMuted, playState.VolumeLevel);
@ -751,7 +793,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
btnFastForward.disabled = !playState.CanSeek;
btnRewind.disabled = !playState.CanSeek;
var nowPlayingItem = state.NowPlayingItem || {};
const nowPlayingItem = state.NowPlayingItem || {};
playbackStartTimeTicks = playState.PlaybackStartTimeTicks;
updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playState.PlaybackStartTimeTicks, playState.BufferedRanges || []);
updateNowPlayingInfo(player, state);
@ -762,7 +804,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
view.querySelector('.btnVideoOsdSettings').classList.add('hide');
}
var isProgressClear = state.MediaSource && null == state.MediaSource.RunTimeTicks;
const isProgressClear = state.MediaSource && null == state.MediaSource.RunTimeTicks;
nowPlayingPositionSlider.setIsClear(isProgressClear);
if (nowPlayingItem.RunTimeTicks) {
@ -799,12 +841,12 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
if (enableProgressByTimeOfDay) {
if (nowPlayingPositionSlider && !nowPlayingPositionSlider.dragging) {
if (programStartDateMs && programEndDateMs) {
var currentTimeMs = (playbackStartTimeTicks + (positionTicks || 0)) / 1e4;
var programRuntimeMs = programEndDateMs - programStartDateMs;
const currentTimeMs = (playbackStartTimeTicks + (positionTicks || 0)) / 1e4;
const programRuntimeMs = programEndDateMs - programStartDateMs;
if (nowPlayingPositionSlider.value = getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, currentTimeMs), bufferedRanges.length) {
var rangeStart = getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, (playbackStartTimeTicks + (bufferedRanges[0].start || 0)) / 1e4);
var rangeEnd = getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, (playbackStartTimeTicks + (bufferedRanges[0].end || 0)) / 1e4);
const rangeStart = getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, (playbackStartTimeTicks + (bufferedRanges[0].start || 0)) / 1e4);
const rangeEnd = getDisplayPercentByTimeOfDay(programStartDateMs, programRuntimeMs, (playbackStartTimeTicks + (bufferedRanges[0].end || 0)) / 1e4);
nowPlayingPositionSlider.setBufferedRanges([{
start: rangeStart,
end: rangeEnd
@ -823,7 +865,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
} else {
if (nowPlayingPositionSlider && !nowPlayingPositionSlider.dragging) {
if (runtimeTicks) {
var pct = positionTicks / runtimeTicks;
let pct = positionTicks / runtimeTicks;
pct *= 100;
nowPlayingPositionSlider.value = pct;
} else {
@ -847,9 +889,9 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function updatePlayerVolumeState(player, isMuted, volumeLevel) {
var supportedCommands = currentPlayerSupportedCommands;
var showMuteButton = true;
var showVolumeSlider = true;
const supportedCommands = currentPlayerSupportedCommands;
let showMuteButton = true;
let showVolumeSlider = true;
if (-1 === supportedCommands.indexOf('Mute')) {
showMuteButton = false;
@ -897,8 +939,8 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function updatePlaylist(player) {
var btnPreviousTrack = view.querySelector('.btnPreviousTrack');
var btnNextTrack = view.querySelector('.btnNextTrack');
const btnPreviousTrack = view.querySelector('.btnPreviousTrack');
const btnNextTrack = view.querySelector('.btnNextTrack');
btnPreviousTrack.classList.remove('hide');
btnNextTrack.classList.remove('hide');
btnNextTrack.disabled = false;
@ -911,7 +953,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
return;
}
var html = datetime.getDisplayRunningTime(ticks);
let html = datetime.getDisplayRunningTime(ticks);
if (divider) {
html = '&nbsp;/&nbsp;' + html;
@ -921,15 +963,15 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function onSettingsButtonClick(e) {
var btn = this;
const btn = this;
require(['playerSettingsMenu'], function (playerSettingsMenu) {
var player = currentPlayer;
import('playerSettingsMenu').then(({default: playerSettingsMenu}) => {
const player = currentPlayer;
if (player) {
// show subtitle offset feature only if player and media support it
var showSubOffset = playbackManager.supportSubtitleOffset(player) &&
const showSubOffset = playbackManager.supportSubtitleOffset(player) &&
playbackManager.canHandleOffsetOnCurrentSubtitle(player);
playerSettingsMenu.show({
@ -948,7 +990,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
if ('stats' === selectedOption) {
toggleStats();
} else if ('suboffset' === selectedOption) {
var player = currentPlayer;
const player = currentPlayer;
if (player) {
playbackManager.enableShowingSubtitleOffset(player);
toggleSubtitleSync();
@ -957,8 +999,8 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function toggleStats() {
require(['playerStats'], function (PlayerStats) {
var player = currentPlayer;
import('playerStats').then(({default: PlayerStats}) => {
const player = currentPlayer;
if (player) {
if (statsOverlay) {
@ -980,11 +1022,11 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function showAudioTrackSelection() {
var player = currentPlayer;
var audioTracks = playbackManager.audioTracks(player);
var currentIndex = playbackManager.getAudioStreamIndex(player);
var menuItems = audioTracks.map(function (stream) {
var opt = {
const player = currentPlayer;
const audioTracks = playbackManager.audioTracks(player);
const currentIndex = playbackManager.getAudioStreamIndex(player);
const menuItems = audioTracks.map(function (stream) {
const opt = {
name: stream.DisplayTitle,
id: stream.Index
};
@ -995,15 +1037,15 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
return opt;
});
var positionTo = this;
const positionTo = this;
require(['actionsheet'], function (actionsheet) {
import('actionsheet').then(({default: actionsheet}) => {
actionsheet.show({
items: menuItems,
title: globalize.translate('Audio'),
positionTo: positionTo
}).then(function (id) {
var index = parseInt(id);
const index = parseInt(id);
if (index !== currentIndex) {
playbackManager.setAudioStreamIndex(index, player);
@ -1013,9 +1055,9 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function showSubtitleTrackSelection() {
var player = currentPlayer;
var streams = playbackManager.subtitleTracks(player);
var currentIndex = playbackManager.getSubtitleStreamIndex(player);
const player = currentPlayer;
const streams = playbackManager.subtitleTracks(player);
let currentIndex = playbackManager.getSubtitleStreamIndex(player);
if (null == currentIndex) {
currentIndex = -1;
@ -1025,8 +1067,8 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
Index: -1,
DisplayTitle: globalize.translate('Off')
});
var menuItems = streams.map(function (stream) {
var opt = {
const menuItems = streams.map(function (stream) {
const opt = {
name: stream.DisplayTitle,
id: stream.Index
};
@ -1037,15 +1079,15 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
return opt;
});
var positionTo = this;
const positionTo = this;
require(['actionsheet'], function (actionsheet) {
import('actionsheet').then(({default: actionsheet}) => {
actionsheet.show({
title: globalize.translate('Subtitles'),
items: menuItems,
positionTo: positionTo
}).then(function (id) {
var index = parseInt(id);
const index = parseInt(id);
if (index !== currentIndex) {
playbackManager.setSubtitleStreamIndex(index, player);
@ -1057,8 +1099,8 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function toggleSubtitleSync(action) {
require(['subtitleSync'], function (SubtitleSync) {
var player = currentPlayer;
import('subtitleSync').then(({default: SubtitleSync}) => {
const player = currentPlayer;
if (subtitleSyncOverlay) {
subtitleSyncOverlay.toggle(action);
} else if (player) {
@ -1078,12 +1120,12 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
* Clicked element.
* To skip 'click' handling on Firefox/Edge.
*/
var clickedElement;
let clickedElement;
function onWindowKeyDown(e) {
function onKeyDown(e) {
clickedElement = e.srcElement;
var key = keyboardnavigation.getKeyName(e);
const key = keyboardnavigation.getKeyName(e);
if (!currentVisibleMenu && 32 === e.keyCode) {
playbackManager.playPause(currentPlayer);
@ -1187,19 +1229,37 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
case '6':
case '7':
case '8':
case '9':
var percent = parseInt(key, 10) * 10;
case '9': {
const percent = parseInt(key, 10) * 10;
playbackManager.seekPercent(percent, currentPlayer);
break;
}
}
}
function onKeyDownCapture() {
// Restart hide timer if OSD is currently visible
if (currentVisibleMenu) {
showOsd();
}
}
function onWindowMouseDown(e) {
clickedElement = e.srcElement;
lockOsd();
}
function onWindowMouseUp() {
unlockOsd();
}
function onWindowTouchStart(e) {
clickedElement = e.srcElement;
lockOsd();
}
function onWindowTouchEnd() {
unlockOsd();
}
function getImgUrl(item, chapter, index, maxWidth, apiClient) {
@ -1216,11 +1276,11 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
function getChapterBubbleHtml(apiClient, item, chapters, positionTicks) {
var chapter;
var index = -1;
let chapter;
let index = -1;
for (var i = 0, length = chapters.length; i < length; i++) {
var currentChapter = chapters[i];
for (let i = 0, length = chapters.length; i < length; i++) {
const currentChapter = chapters[i];
if (positionTicks >= currentChapter.StartPositionTicks) {
chapter = currentChapter;
@ -1232,10 +1292,10 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
return null;
}
var src = getImgUrl(item, chapter, index, 400, apiClient);
const src = getImgUrl(item, chapter, index, 400, apiClient);
if (src) {
var html = '<div class="chapterThumbContainer">';
let html = '<div class="chapterThumbContainer">';
html += '<img class="chapterThumb" src="' + src + '" />';
html += '<div class="chapterThumbTextContainer">';
html += '<div class="chapterThumbText chapterThumbText-dim">';
@ -1251,15 +1311,15 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
return null;
}
var playPauseClickTimeout;
let playPauseClickTimeout;
function onViewHideStopPlayback() {
if (playbackManager.isPlayingVideo()) {
require(['shell'], function (shell) {
import('shell').then(({default: shell}) => {
shell.disableFullscreen();
});
clearTimeout(playPauseClickTimeout);
var player = currentPlayer;
const player = currentPlayer;
view.removeEventListener('viewbeforehide', onViewHideStopPlayback);
releaseCurrentPlayer();
playbackManager.stop(player);
@ -1274,47 +1334,49 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
}
}
require(['shell'], function (shell) {
import('shell').then(({default: shell}) => {
shell.enableFullscreen();
});
var currentPlayer;
var comingUpNextDisplayed;
var currentUpNextDialog;
var isEnabled;
var currentItem;
var recordingButtonManager;
var enableProgressByTimeOfDay;
var supportsBrightnessChange;
var currentVisibleMenu;
var statsOverlay;
var osdHideTimeout;
var lastPointerMoveData;
var self = this;
var currentPlayerSupportedCommands = [];
var currentRuntimeTicks = 0;
var lastUpdateTime = 0;
var programStartDateMs = 0;
var programEndDateMs = 0;
var playbackStartTimeTicks = 0;
var subtitleSyncOverlay;
var nowPlayingVolumeSlider = view.querySelector('.osdVolumeSlider');
var nowPlayingVolumeSliderContainer = view.querySelector('.osdVolumeSliderContainer');
var nowPlayingPositionSlider = view.querySelector('.osdPositionSlider');
var nowPlayingPositionText = view.querySelector('.osdPositionText');
var nowPlayingDurationText = view.querySelector('.osdDurationText');
var startTimeText = view.querySelector('.startTimeText');
var endTimeText = view.querySelector('.endTimeText');
var endsAtText = view.querySelector('.endsAtText');
var btnRewind = view.querySelector('.btnRewind');
var btnFastForward = view.querySelector('.btnFastForward');
var transitionEndEventName = dom.whichTransitionEvent();
var headerElement = document.querySelector('.skinHeader');
var osdBottomElement = document.querySelector('.videoOsdBottom-maincontrols');
let currentPlayer;
let comingUpNextDisplayed;
let currentUpNextDialog;
let isEnabled;
let currentItem;
let recordingButtonManager;
let enableProgressByTimeOfDay;
let supportsBrightnessChange;
let currentVisibleMenu;
let statsOverlay;
let osdHideTimeout;
let lastPointerMoveData;
const self = this;
let currentPlayerSupportedCommands = [];
let currentRuntimeTicks = 0;
let lastUpdateTime = 0;
let programStartDateMs = 0;
let programEndDateMs = 0;
let playbackStartTimeTicks = 0;
let subtitleSyncOverlay;
const nowPlayingVolumeSlider = view.querySelector('.osdVolumeSlider');
const nowPlayingVolumeSliderContainer = view.querySelector('.osdVolumeSliderContainer');
const nowPlayingPositionSlider = view.querySelector('.osdPositionSlider');
const nowPlayingPositionText = view.querySelector('.osdPositionText');
const nowPlayingDurationText = view.querySelector('.osdDurationText');
const startTimeText = view.querySelector('.startTimeText');
const endTimeText = view.querySelector('.endTimeText');
const endsAtText = view.querySelector('.endsAtText');
const btnRewind = view.querySelector('.btnRewind');
const btnFastForward = view.querySelector('.btnFastForward');
const transitionEndEventName = dom.whichTransitionEvent();
const headerElement = document.querySelector('.skinHeader');
const osdBottomElement = document.querySelector('.videoOsdBottom-maincontrols');
nowPlayingPositionSlider.enableKeyboardDragging();
nowPlayingVolumeSlider.enableKeyboardDragging();
if (layoutManager.tv) {
nowPlayingPositionSlider.classList.add('focusable');
nowPlayingPositionSlider.enableKeyboardDragging();
}
view.addEventListener('viewbeforeshow', function (e) {
@ -1325,23 +1387,35 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
try {
events.on(playbackManager, 'playerchange', onPlayerChange);
bindToPlayer(playbackManager.getCurrentPlayer());
/* eslint-disable-next-line compat/compat */
dom.addEventListener(document, window.PointerEvent ? 'pointermove' : 'mousemove', onPointerMove, {
passive: true
});
showOsd();
inputManager.on(window, onInputCommand);
dom.addEventListener(window, 'keydown', onWindowKeyDown, {
document.addEventListener('keydown', onKeyDown);
dom.addEventListener(document, 'keydown', onKeyDownCapture, {
capture: true
});
/* eslint-disable-next-line compat/compat */
dom.addEventListener(window, window.PointerEvent ? 'pointerdown' : 'mousedown', onWindowMouseDown, {
passive: true
});
/* eslint-disable-next-line compat/compat */
dom.addEventListener(window, window.PointerEvent ? 'pointerup' : 'mouseup', onWindowMouseUp, {
passive: true
});
dom.addEventListener(window, 'touchstart', onWindowTouchStart, {
passive: true
});
['touchend', 'touchcancel'].forEach((event) => {
dom.addEventListener(window, event, onWindowTouchEnd, {
passive: true
});
});
} catch (e) {
require(['appRouter'], function(appRouter) {
appRouter.showDirect('/');
import('appRouter').then(({default: appRouter}) => {
appRouter.goHome();
});
}
});
@ -1350,18 +1424,30 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
statsOverlay.enabled(false);
}
dom.removeEventListener(window, 'keydown', onWindowKeyDown, {
document.removeEventListener('keydown', onKeyDown);
dom.removeEventListener(document, 'keydown', onKeyDownCapture, {
capture: true
});
/* eslint-disable-next-line compat/compat */
dom.removeEventListener(window, window.PointerEvent ? 'pointerdown' : 'mousedown', onWindowMouseDown, {
passive: true
});
/* eslint-disable-next-line compat/compat */
dom.removeEventListener(window, window.PointerEvent ? 'pointerup' : 'mouseup', onWindowMouseUp, {
passive: true
});
dom.removeEventListener(window, 'touchstart', onWindowTouchStart, {
passive: true
});
['touchend', 'touchcancel'].forEach((event) => {
dom.removeEventListener(window, event, onWindowTouchEnd, {
passive: true
});
});
stopOsdHideTimer();
headerElement.classList.remove('osdHeader');
headerElement.classList.remove('osdHeader-hidden');
/* eslint-disable-next-line compat/compat */
dom.removeEventListener(document, window.PointerEvent ? 'pointermove' : 'mousemove', onPointerMove, {
passive: true
});
@ -1396,14 +1482,15 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
destroyStats();
destroySubtitleSync();
});
var lastPointerDown = 0;
let lastPointerDown = 0;
/* eslint-disable-next-line compat/compat */
dom.addEventListener(view, window.PointerEvent ? 'pointerdown' : 'click', function (e) {
if (dom.parentWithClass(e.target, ['videoOsdBottom', 'upNextContainer'])) {
return void showOsd();
}
var pointerType = e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse');
var now = new Date().getTime();
const pointerType = e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse');
const now = new Date().getTime();
switch (pointerType) {
case 'touch':
@ -1441,31 +1528,28 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
if (browser.touch) {
dom.addEventListener(view, 'dblclick', onDoubleClick, {});
} else {
var options = { passive: true };
const options = { passive: true };
dom.addEventListener(view, 'dblclick', function () {
playbackManager.toggleFullscreen(currentPlayer);
}, options);
}
function setVolume() {
playbackManager.setVolume(this.value, currentPlayer);
}
view.querySelector('.buttonMute').addEventListener('click', function () {
playbackManager.toggleMute(currentPlayer);
});
nowPlayingVolumeSlider.addEventListener('change', setVolume);
nowPlayingVolumeSlider.addEventListener('mousemove', setVolume);
nowPlayingVolumeSlider.addEventListener('touchmove', setVolume);
nowPlayingVolumeSlider.addEventListener('input', (e) => {
playbackManager.setVolume(e.target.value, currentPlayer);
});
nowPlayingPositionSlider.addEventListener('change', function () {
var player = currentPlayer;
const player = currentPlayer;
if (player) {
var newPercent = parseFloat(this.value);
const newPercent = parseFloat(this.value);
if (enableProgressByTimeOfDay) {
var seekAirTimeTicks = newPercent / 100 * (programEndDateMs - programStartDateMs) * 1e4;
let seekAirTimeTicks = newPercent / 100 * (programEndDateMs - programStartDateMs) * 1e4;
seekAirTimeTicks += 1e4 * programStartDateMs;
seekAirTimeTicks -= playbackStartTimeTicks;
playbackManager.seek(seekAirTimeTicks, player);
@ -1479,7 +1563,7 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
showOsd();
if (enableProgressByTimeOfDay) {
if (programStartDateMs && programEndDateMs) {
var ms = programEndDateMs - programStartDateMs;
let ms = programEndDateMs - programStartDateMs;
ms /= 100;
ms *= value;
ms += programStartDateMs;
@ -1493,13 +1577,13 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
return '--:--';
}
var ticks = currentRuntimeTicks;
let ticks = currentRuntimeTicks;
ticks /= 100;
ticks *= value;
var item = currentItem;
const item = currentItem;
if (item && item.Chapters && item.Chapters.length && item.Chapters[0].ImageTag) {
var html = getChapterBubbleHtml(connectionManager.getApiClient(item.ServerId), item, item.Chapters, ticks);
let html = getChapterBubbleHtml(connectionManager.getApiClient(item.ServerId), item, item.Chapters, ticks);
if (html) {
return html;
@ -1532,8 +1616,13 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
if (browser.touch) {
(function () {
<<<<<<< HEAD
require(['touchHelper'], function (TouchHelper) {
self.touchHelper = new TouchHelper.default(view, {
=======
import('touchHelper').then(({default: TouchHelper}) => {
self.touchHelper = new TouchHelper(view, {
>>>>>>> upstream/master
swipeYThreshold: 30,
triggerOnMove: true,
preventDefaultOnMove: true,
@ -1544,5 +1633,6 @@ define(['playbackManager', 'dom', 'inputManager', 'datetime', 'itemHelper', 'med
});
})();
}
};
});
}
/* eslint-enable indent */

View file

@ -1,20 +1,23 @@
define(['displaySettings', 'userSettings', 'autoFocuser'], function (DisplaySettings, userSettings, autoFocuser) {
'use strict';
import DisplaySettings from 'displaySettings';
import * as userSettings from 'userSettings';
import autoFocuser from 'autoFocuser';
/* eslint-disable indent */
// Shortcuts
const UserSettings = userSettings.UserSettings;
return function (view, params) {
export default function (view, params) {
function onBeforeUnload(e) {
if (hasChanges) {
e.returnValue = 'You currently have unsaved changes. Are you sure you wish to leave?';
}
}
var settingsInstance;
var hasChanges;
var userId = params.userId || ApiClient.getCurrentUserId();
var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings();
let settingsInstance;
let hasChanges;
const userId = params.userId || ApiClient.getCurrentUserId();
const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings();
view.addEventListener('viewshow', function () {
window.addEventListener('beforeunload', onBeforeUnload);
@ -26,28 +29,23 @@ define(['displaySettings', 'userSettings', 'autoFocuser'], function (DisplaySett
userId: userId,
element: view.querySelector('.settingsContainer'),
userSettings: currentSettings,
enableSaveButton: false,
enableSaveConfirmation: false,
enableSaveButton: true,
enableSaveConfirmation: true,
autoFocus: autoFocuser.isEnabled()
});
}
});
view.addEventListener('change', function () {
hasChanges = true;
});
view.addEventListener('viewbeforehide', function () {
window.removeEventListener('beforeunload', onBeforeUnload);
hasChanges = false;
if (settingsInstance) {
settingsInstance.submit();
}
});
view.addEventListener('viewdestroy', function () {
if (settingsInstance) {
settingsInstance.destroy();
settingsInstance = null;
}
});
};
});
}
/* eslint-enable indent */

View file

@ -1,20 +1,27 @@
define(['homescreenSettings', 'dom', 'globalize', 'loading', 'userSettings', 'autoFocuser', 'listViewStyle'], function (HomescreenSettings, dom, globalize, loading, userSettings, autoFocuser) {
'use strict';
import HomescreenSettings from 'homescreenSettings';
import dom from 'dom';
import globalize from 'globalize';
import loading from 'loading';
import * as userSettings from 'userSettings';
import autoFocuser from 'autoFocuser';
import 'listViewStyle';
/* eslint-disable indent */
// Shortcuts
const UserSettings = userSettings.UserSettings;
return function (view, params) {
export default function (view, params) {
function onBeforeUnload(e) {
if (hasChanges) {
e.returnValue = 'You currently have unsaved changes. Are you sure you wish to leave?';
}
}
var homescreenSettingsInstance;
var hasChanges;
var userId = params.userId || ApiClient.getCurrentUserId();
var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings();
let homescreenSettingsInstance;
let hasChanges;
const userId = params.userId || ApiClient.getCurrentUserId();
const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings();
view.addEventListener('viewshow', function () {
window.addEventListener('beforeunload', onBeforeUnload);
@ -26,27 +33,23 @@ define(['homescreenSettings', 'dom', 'globalize', 'loading', 'userSettings', 'au
userId: userId,
element: view.querySelector('.homeScreenSettingsContainer'),
userSettings: currentSettings,
enableSaveButton: false,
enableSaveConfirmation: false,
enableSaveButton: true,
enableSaveConfirmation: true,
autoFocus: autoFocuser.isEnabled()
});
}
});
view.addEventListener('change', function () {
hasChanges = true;
});
view.addEventListener('viewbeforehide', function () {
hasChanges = false;
if (homescreenSettingsInstance) {
homescreenSettingsInstance.submit();
}
});
view.addEventListener('viewdestroy', function () {
if (homescreenSettingsInstance) {
homescreenSettingsInstance.destroy();
homescreenSettingsInstance = null;
}
});
};
});
}
/* eslint-enable indent */

View file

@ -1,20 +1,27 @@
define(['playbackSettings', 'dom', 'globalize', 'loading', 'userSettings', 'autoFocuser', 'listViewStyle'], function (PlaybackSettings, dom, globalize, loading, userSettings, autoFocuser) {
'use strict';
import PlaybackSettings from 'playbackSettings';
import dom from 'dom';
import globalize from 'globalize';
import loading from 'loading';
import * as userSettings from 'userSettings';
import autoFocuser from 'autoFocuser';
import 'listViewStyle';
/* eslint-disable indent */
// Shortcuts
const UserSettings = userSettings.UserSettings;
return function (view, params) {
export default function (view, params) {
function onBeforeUnload(e) {
if (hasChanges) {
e.returnValue = 'You currently have unsaved changes. Are you sure you wish to leave?';
}
}
var settingsInstance;
var hasChanges;
var userId = params.userId || ApiClient.getCurrentUserId();
var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings();
let settingsInstance;
let hasChanges;
const userId = params.userId || ApiClient.getCurrentUserId();
const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings();
view.addEventListener('viewshow', function () {
window.addEventListener('beforeunload', onBeforeUnload);
@ -26,27 +33,23 @@ define(['playbackSettings', 'dom', 'globalize', 'loading', 'userSettings', 'auto
userId: userId,
element: view.querySelector('.settingsContainer'),
userSettings: currentSettings,
enableSaveButton: false,
enableSaveConfirmation: false,
enableSaveButton: true,
enableSaveConfirmation: true,
autoFocus: autoFocuser.isEnabled()
});
}
});
view.addEventListener('change', function () {
hasChanges = true;
});
view.addEventListener('viewbeforehide', function () {
hasChanges = false;
if (settingsInstance) {
settingsInstance.submit();
}
});
view.addEventListener('viewdestroy', function () {
if (settingsInstance) {
settingsInstance.destroy();
settingsInstance = null;
}
});
};
});
}
/* eslint-enable indent */

View file

@ -1,6 +1,14 @@
<<<<<<< HEAD
import subtitleSettings from 'subtitleSettings';
import {UserSettings, currentSettings as userSettings} from 'userSettings';
import autoFocuser from 'autoFocuser';
=======
import SubtitleSettings from 'subtitleSettings';
import * as userSettings from 'userSettings';
import autoFocuser from 'autoFocuser';
/* eslint-disable indent */
>>>>>>> upstream/master
export class SubtitleController {
constructor(view, params) {
@ -10,6 +18,7 @@ export class SubtitleController {
this.subtitleSettingsInstance = null;
this.view = view;
<<<<<<< HEAD
view.addEventListener('viewshow', this.viewShow.bind(this));
view.addEventListener('change', this.change.bind(this));
view.addEventListener('viewbeforehide', this.viewBeforeHide.bind(this));
@ -56,8 +65,53 @@ export class SubtitleController {
beforeUnload(e) {
if (this.hasChanges) {
e.returnValue = 'You currently have unsaved changes. Are you sure you wish to leave?';
=======
export default function (view, params) {
function onBeforeUnload(e) {
if (hasChanges) {
e.returnValue = 'You currently have unsaved changes. Are you sure you wish to leave?';
}
>>>>>>> upstream/master
}
}
}
<<<<<<< HEAD
export default SubtitleController;
=======
let subtitleSettingsInstance;
let hasChanges;
const userId = params.userId || ApiClient.getCurrentUserId();
const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings();
view.addEventListener('viewshow', function () {
window.addEventListener('beforeunload', onBeforeUnload);
if (subtitleSettingsInstance) {
subtitleSettingsInstance.loadData();
} else {
subtitleSettingsInstance = new SubtitleSettings({
serverId: ApiClient.serverId(),
userId: userId,
element: view.querySelector('.settingsContainer'),
userSettings: currentSettings,
enableSaveButton: true,
enableSaveConfirmation: true,
autoFocus: autoFocuser.isEnabled()
});
}
});
view.addEventListener('change', function () {
hasChanges = true;
});
view.addEventListener('viewdestroy', function () {
if (subtitleSettingsInstance) {
subtitleSettingsInstance.destroy();
subtitleSettingsInstance = null;
}
});
}
/* eslint-enable indent */
>>>>>>> upstream/master

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
import browser from 'browser';
import dom from 'dom';
import layoutManager from 'layoutManager';
@ -6,6 +7,10 @@ import appRouter from 'appRouter';
import appHost from 'apphost';
import 'css!./emby-button';
import 'registerElement';
=======
define(['browser', 'dom', 'layoutManager', 'shell', 'appRouter', 'apphost', 'css!./emby-button', 'webcomponents'], function (browser, dom, layoutManager, shell, appRouter, appHost) {
'use strict';
>>>>>>> upstream/master
const EmbyButtonPrototype = Object.create(HTMLButtonElement.prototype);
const EmbyLinkButtonPrototype = Object.create(HTMLAnchorElement.prototype);

View file

@ -1,6 +1,11 @@
<<<<<<< HEAD
import layoutManager from 'layoutManager';
import 'css!./emby-button';
import 'registerElement';
=======
define(['layoutManager', 'css!./emby-button', 'webcomponents'], function (layoutManager) {
'use strict';
>>>>>>> upstream/master
const EmbyButtonPrototype = Object.create(HTMLButtonElement.prototype);

View file

@ -1,7 +1,12 @@
<<<<<<< HEAD
import browser from 'browser';
import dom from 'dom';
import 'css!./emby-checkbox';
import 'registerElement';
=======
define(['browser', 'dom', 'css!./emby-checkbox', 'webcomponents'], function (browser, dom) {
'use strict';
>>>>>>> upstream/master
/* eslint-disable indent */

View file

@ -1,7 +1,12 @@
<<<<<<< HEAD
import browser from 'browser';
import 'css!./emby-collapse';
import 'registerElement';
import 'emby-button';
=======
define(['browser', 'css!./emby-collapse', 'webcomponents', 'emby-button'], function (browser) {
'use strict';
>>>>>>> upstream/master
/* eslint-disable indent */

View file

@ -1,8 +1,13 @@
<<<<<<< HEAD
import layoutManager from 'layoutManager';
import browser from 'browser';
import dom from 'dom';
import 'css!./emby-input';
import 'registerElement';
=======
define(['layoutManager', 'browser', 'dom', 'css!./emby-input', 'webcomponents'], function (layoutManager, browser, dom) {
'use strict';
>>>>>>> upstream/master
/* eslint-disable indent */

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
import EmbyProgressRing from 'emby-progressring';
import dom from 'dom';
import serverNotifications from 'serverNotifications';
@ -5,6 +6,10 @@ import events from 'events';
import 'registerElement';
/* eslint-disable indent */
=======
define(['emby-progressring', 'dom', 'serverNotifications', 'events', 'webcomponents'], function (EmbyProgressRing, dom, serverNotifications, events) {
'use strict';
>>>>>>> upstream/master
function addNotificationEvent(instance, name, handler) {

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
import itemShortcuts from 'itemShortcuts';
import inputManager from 'inputManager';
import connectionManager from 'connectionManager';
@ -15,6 +16,12 @@ import 'registerElement';
/* eslint-disable indent */
const ItemsContainerPrototype = Object.create(HTMLDivElement.prototype);
=======
define(['itemShortcuts', 'inputManager', 'connectionManager', 'playbackManager', 'imageLoader', 'layoutManager', 'browser', 'dom', 'loading', 'focusManager', 'serverNotifications', 'events', 'webcomponents'], function (itemShortcuts, inputManager, connectionManager, playbackManager, imageLoader, layoutManager, browser, dom, loading, focusManager, serverNotifications, events) {
'use strict';
var ItemsContainerPrototype = Object.create(HTMLDivElement.prototype);
>>>>>>> upstream/master
function onClick(e) {
const itemsContainer = this;
@ -42,7 +49,7 @@ import 'registerElement';
// check for serverId, it won't be present on selectserver
if (card && card.getAttribute('data-serverid')) {
inputManager.trigger('menu', {
inputManager.handleCommand('menu', {
sourceElement: card
});

View file

@ -1,6 +1,11 @@
<<<<<<< HEAD
import require from 'require';
import 'css!./emby-progressring';
import 'webcomponents';
=======
define(['require', 'css!./emby-progressring', 'webcomponents'], function (require) {
'use strict';
>>>>>>> upstream/master
/* eslint-disable indent */

View file

@ -1,6 +1,11 @@
<<<<<<< HEAD
import layoutManager from 'layoutManager';
import 'css!./emby-radio';
import 'registerElement';
=======
define(['layoutManager', 'css!./emby-radio', 'webcomponents'], function (layoutManager) {
'use strict';
>>>>>>> upstream/master
/* eslint-disable indent */

View file

@ -6,7 +6,7 @@
justify-content: center;
min-width: 104px;
min-height: 24px;
padding-top: 1.25em;
padding-top: 0.85em;
z-index: 1;
color: #fff;
display: flex;

View file

@ -1,8 +1,13 @@
<<<<<<< HEAD
import layoutManager from 'layoutManager';
import dom from 'dom';
import 'css!./emby-scrollbuttons';
import 'registerElement';
import 'paper-icon-button-light';
=======
define(['layoutManager', 'dom', 'css!./emby-scrollbuttons', 'webcomponents', 'paper-icon-button-light'], function (layoutManager, dom) {
'use strict';
>>>>>>> upstream/master
/* eslint-disable indent */

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
import scroller from 'scroller';
import dom from 'dom';
import layoutManager from 'layoutManager';
@ -6,6 +7,10 @@ import focusManager from 'focusManager';
import browser from 'browser';
import 'registerElement';
import 'css!./emby-scroller';
=======
define(['scroller', 'dom', 'layoutManager', 'inputManager', 'focusManager', 'browser', 'webcomponents', 'css!./emby-scroller'], function (scroller, dom, layoutManager, inputManager, focusManager, browser) {
'use strict';
>>>>>>> upstream/master
/* eslint-disable indent */

View file

@ -1,8 +1,13 @@
<<<<<<< HEAD
import layoutManager from 'layoutManager';
import browser from 'browser';
import actionsheet from 'actionsheet';
import 'css!./emby-select';
import 'registerElement';
=======
define(['layoutManager', 'browser', 'actionsheet', 'css!./emby-select', 'webcomponents'], function (layoutManager, browser, actionsheet) {
'use strict';
>>>>>>> upstream/master
/* eslint-disable indent */

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
import browser from 'browser';
import dom from 'dom';
import layoutManager from 'layoutManager';
@ -5,6 +6,10 @@ import keyboardnavigation from 'keyboardnavigation';
import 'css!./emby-slider';
import 'webcomponents';
import 'emby-input';
=======
define(['browser', 'dom', 'layoutManager', 'keyboardnavigation', 'css!./emby-slider', 'webcomponents', 'emby-input'], function (browser, dom, layoutManager, keyboardnavigation) {
'use strict';
>>>>>>> upstream/master
/* eslint-disable indent */

View file

@ -31,11 +31,6 @@
.emby-tabs-slider {
position: relative;
overflow: hidden;
}
.layout-mobile .emby-tabs-slider {
overflow: auto;
}
.tabContent:not(.is-active) {

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
import dom from 'dom';
import scroller from 'scroller';
import browser from 'browser';
@ -6,6 +7,10 @@ import focusManager from 'focusManager';
import 'registerElement';
import 'css!./emby-tabs';
import 'scrollStyles';
=======
define(['dom', 'scroller', 'browser', 'layoutManager', 'focusManager', 'webcomponents', 'css!./emby-tabs', 'scrollStyles'], function (dom, scroller, browser, layoutManager, focusManager) {
'use strict';
>>>>>>> upstream/master
/* eslint-disable indent */

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
import layoutManager from 'layoutManager';
import browser from 'browser';
import 'css!./emby-textarea';
@ -5,6 +6,10 @@ import 'registerElement';
import 'emby-input';
/* eslint-disable indent */
=======
define(['layoutManager', 'browser', 'css!./emby-textarea', 'webcomponents', 'emby-input'], function (layoutManager, browser) {
'use strict';
>>>>>>> upstream/master
function autoGrow(textarea, maxLines) {
const self = this;

View file

@ -1,5 +1,10 @@
<<<<<<< HEAD
import 'css!./emby-toggle';
import 'registerElement';
=======
define(['css!./emby-toggle', 'webcomponents'], function () {
'use strict';
>>>>>>> upstream/master
/* eslint-disable indent */

View file

@ -52,7 +52,7 @@
<!-- iPad Pro 1 -->
<link href="assets/splash/ipadpro1_splash.png" media="screen and (device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" rel="apple-touch-startup-image" />
<link href="assets/splash/ipadpro1_splash_l.png" media="screen and (device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" rel="apple-touch-startup-image" />
<!-- iPad Pro 3 -->
<link href="assets/splash/ipadpro3_splash.png" media="screen and (device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" rel="apple-touch-startup-image" />
<link href="assets/splash/ipadpro3_splash_l.png" media="screen and (device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" rel="apple-touch-startup-image" />
@ -69,12 +69,19 @@
<title>Jellyfin</title>
<style>
.transparentDocument, .backgroundContainer-transparent:not(.withBackdrop) {
.transparentDocument,
.backgroundContainer-transparent:not(.withBackdrop) {
background: none !important;
background-color: transparent !important;
}
.mouseIdle, .mouseIdle button, .mouseIdle select, .mouseIdle input, .mouseIdle textarea, .mouseIdle a, .mouseIdle label {
.mouseIdle,
.mouseIdle button,
.mouseIdle select,
.mouseIdle input,
.mouseIdle textarea,
.mouseIdle a,
.mouseIdle label {
cursor: none !important;
}
@ -82,7 +89,9 @@
background-color: #101010;
}
.hide, .mouseIdle .hide-mouse-idle, .mouseIdle-tv .hide-mouse-idle-tv {
.hide,
.mouseIdle .hide-mouse-idle,
.mouseIdle-tv .hide-mouse-idle-tv {
display: none !important;
}
@ -94,6 +103,42 @@
z-index: 1;
width: 0.8em;
}
@-webkit-keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.splashLogo {
-webkit-animation: fadein 0.5s;
animation: fadein 0.5s;
width: 30%;
height: 30%;
background-image: url(assets/img/banner-light.png);
background-position: center center;
background-repeat: no-repeat;
background-size: contain;
position: fixed;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
</style>
</head>
<body>
@ -103,7 +148,9 @@
<div class="mainDrawer-scrollContainer scrollContainer focuscontainer-y"></div>
</div>
<div class="skinHeader focuscontainer-x"></div>
<div class="mainAnimatedPages skinBody"></div>
<div class="mainAnimatedPages skinBody">
<div class="splashLogo"></div>
</div>
<div class="mainDrawerHandle"></div>
<!-- inject:js -->

View file

@ -1,7 +1,9 @@
<div id="loginPage" data-role="page" class="page standalonePage" data-backbutton="false">
<div id="loginPage" data-role="page" class="page standalonePage flex flex-direction-column align-items-center justify-content-center" data-backbutton="false">
<div class="padded-left padded-right padded-bottom-page">
<form class="manualLoginForm hide" style="margin: 0 auto;">
<h1 style="margin-top:1em;text-align: left;">${HeaderPleaseSignIn}</h1>
<form class="manualLoginForm hide">
<div class="padded-left padded-right flex align-items-center justify-content-center">
<h1 class="sectionTitle">${HeaderPleaseSignIn}</h1>
</div>
<div style="height:0; overflow: hidden;">
<input type="text" name="fakeusernameremembered" tabindex="-1" />
<input type="password" name="fakepasswordremembered" tabindex="-1" />
@ -29,8 +31,6 @@
<span>${ButtonCancel}</span>
</button>
</div>
<br/>
<br/>
</form>
<div class="visualLoginForm" style="text-align: center;">

View file

@ -5,7 +5,7 @@
<div class="nowPlayingInfoContainer">
<div class="nowPlayingPageImageContainer"></div>
<div class="nowPlayingInfoControls">
<div class="flex">
<div class="nowPlayingInfoContainerMedia">
@ -15,9 +15,9 @@
<div class="nowPlayingArtist nowPlayingSerie"></div>
</div>
<div class="nowPlayingPageUserDataButtonsTitle"></div>
</div>
<div class="sliderContainer flex">
<div class="positionTime"></div>
<div class="nowPlayingPositionSliderContainer">
@ -25,15 +25,20 @@
</div>
<div class="runtime"></div>
</div>
<div class="nowPlayingButtonsContainer focuscontainer-x">
<div class="nowPlayingButtonsContainer focuscontainer-x justify-content-space-between">
<div class="nowPlayingInfoButtons">
<button is="paper-icon-button-light" class="btnCommand btnRepeat repeatToggleButton autoSize" title="${ButtonRepeat}"
data-command="SetRepeatMode">
<span class="material-icons repeat"></span>
</button>
<button is="paper-icon-button-light" class="btnRewind btnNowPlayingRewind btnPlayStateCommand autoSize" title="${Rewind}">
<span class="material-icons replay_10"></span>
</button>
<button is="paper-icon-button-light" class="btnPreviousTrack btnPlayStateCommand autoSize" title="${ButtonPreviousTrack}">
<span class="material-icons skip_previous"></span>
</button>
@ -49,15 +54,19 @@
<button is="paper-icon-button-light" class="btnPlayStateCommand btnNextTrack autoSize" title="${ButtonNextTrack}">
<span class="material-icons skip_next"></span>
</button>
<button is="paper-icon-button-light" class="btnPlayStateCommand btnFastForward btnNowPlayingFastForward autoSize" title="${FastForward}">
<span class="material-icons forward_30"></span>
</button>
<button is="paper-icon-button-light" class="btnShuffleQueue autoSize" title="${ButtonShuffle}">
<span class="material-icons shuffle"></span>
</button>
</div>
<div class="nowPlayingSecondaryButtons">
<button is="paper-icon-button-light" class="btnAudioTracks videoButton btnPlayStateCommand autoSize" title="${ButtonAudioTracks}" data-command="GoToSearch">
<span class="material-icons audiotrack"></span>
</button>
@ -65,14 +74,19 @@
<button is="paper-icon-button-light" class="btnSubtitles videoButton btnPlayStateCommand autoSize" title="${ButtonSubtitles}" data-command="GoToSearch">
<span class="material-icons closed_caption"></span>
</button>
<div class="nowPlayingPageUserDataButtons"></div>
<button is="paper-icon-button-light" class="btnToggleFullscreen videoButton btnPlayStateCommand autoSize" title="${ButtonFullscreen}" data-command="ToggleFullscreen">
<span class="material-icons fullscreen"></span>
</button>
<button is="paper-icon-button-light" class="btnCommand repeatToggleButton autoSize" title="${ButtonRepeat}" data-command="SetRepeatMode">
<button is="paper-icon-button-light" class="btnShuffleQueue autoSize" title="${ButtonShuffle}">
<span class="material-icons shuffle"></span>
</button>
<button is="paper-icon-button-light" class="btnCommand btnRepeat repeatToggleButton autoSize" title="${ButtonRepeat}"
data-command="SetRepeatMode">
<span class="material-icons repeat"></span>
</button>
@ -162,21 +176,18 @@
</div>
</div>
<div class="playlistSection">
<div class="playlistSectionButton flex align-items-center justify-content-center">
<button id="togglePlaylist" is="paper-icon-button-light" class="btnTogglePlaylist" title="${ButtonTogglePlaylist}">
<div class="playlistSectionButton flex align-items-center justify-content-center focuscontainer-x">
<button id="togglePlaylist" is="paper-icon-button-light" class="btnTogglePlaylist hide" title="${ButtonTogglePlaylist}">
<span class="material-icons queue_music"></span>
</button>
<button is="paper-icon-button-light" class="btnSavePlaylist" title="${ButtonSave}">
<button is="paper-icon-button-light" class="btnSavePlaylist hide" title="${ButtonSave}">
<span class="material-icons save"></span>
</button>
<button id="toggleContextMenu" is="paper-icon-button-light" class="btnToggleContextMenu" title="${ButtonToggleContextMenu}">
<span class="material-icons more_vert"></span>
</button>
</div>
<div id="playlist" class="playlist itemsContainer vertical-list nowPlayingPlaylist hide" is="emby-itemscontainer" data-dragreorder="true"></div>
<div id="contextMenu" class="contextMenu itemsContainer vertical-list nowPlayingContextMenu hide" is="emby-itemscontainer">
<div class="listItem listItem-border contextMenuList contextMenuArtist">
</div>
<div class="listItem listItem-border contextMenuList contextMenuAlbum">
</div>
</div>
</div>
</div>
</div>

View file

@ -548,7 +548,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
events.trigger(instance, 'playbackstop', [state]);
var state = instance.lastPlayerData.PlayState || {};
state = instance.lastPlayerData.PlayState || {};
var volume = state.VolumeLevel || 0.5;
var mute = state.IsMuted || false;
@ -572,6 +572,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
bindEventForRelay(instance, 'unpause');
bindEventForRelay(instance, 'volumechange');
bindEventForRelay(instance, 'repeatmodechange');
bindEventForRelay(instance, 'shufflequeuemodechange');
events.on(instance._castPlayer, 'playstatechange', function (e, data) {
@ -651,6 +652,7 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
'SetSubtitleStreamIndex',
'DisplayContent',
'SetRepeatMode',
'SetShuffleQueue',
'EndSession',
'PlayMediaSource',
'PlayTrailers'
@ -864,6 +866,12 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
return state.RepeatMode;
};
ChromecastPlayer.prototype.getQueueShuffleMode = function () {
var state = this.lastPlayerData || {};
state = state.PlayState || {};
return state.ShuffleMode;
};
ChromecastPlayer.prototype.playTrailers = function (item) {
this._castPlayer.sendMessage({
@ -884,6 +892,15 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', '
});
};
ChromecastPlayer.prototype.setQueueShuffleMode = function (value) {
this._castPlayer.sendMessage({
options: {
ShuffleMode: value
},
command: 'SetShuffleQueue'
});
};
ChromecastPlayer.prototype.toggleMute = function () {
this._castPlayer.sendMessage({

View file

@ -649,7 +649,7 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
function setTrackEventsSubtitleOffset(trackEvents, offsetValue) {
if (Array.isArray(trackEvents)) {
offsetValue = updateCurrentTrackOffset(offsetValue);
offsetValue = updateCurrentTrackOffset(offsetValue) * 1e7; // ticks
trackEvents.forEach(function(trackEvent) {
trackEvent.StartPositionTicks -= offsetValue;
trackEvent.EndPositionTicks -= offsetValue;

View file

@ -263,7 +263,7 @@ define(['playbackManager', 'events', 'serverNotifications', 'connectionManager']
appName: s.Client,
playableMediaTypes: s.PlayableMediaTypes,
isLocalPlayer: false,
supportedCommands: s.SupportedCommands,
supportedCommands: s.Capabilities.SupportedCommands,
user: s.UserId ? {
Id: s.UserId,
@ -506,6 +506,17 @@ define(['playbackManager', 'events', 'serverNotifications', 'connectionManager']
});
};
SessionPlayer.prototype.setQueueShuffleMode = function (mode) {
sendCommandByName(this, 'SetShuffleQueue', {
ShuffleMode: mode
});
};
SessionPlayer.prototype.getQueueShuffleMode = function () {
};
SessionPlayer.prototype.displayContent = function (options) {
sendCommandByName(this, 'DisplayContent', options);

View file

@ -11,6 +11,7 @@
src += `?v=${self.dashboardVersion}`;
}
script.src = src;
script.setAttribute('async', '');
if (onload) {
script.onload = onload;

View file

@ -60,7 +60,7 @@ define(['browser'], function (browser) {
function canPlayHlsWithMSE() {
// text tracks dont work with this in firefox
return window.MediaSource != null;
return window.MediaSource != null; /* eslint-disable-line compat/compat */
}
function supportsAc3(videoTestElement) {

View file

@ -211,7 +211,7 @@
'MozAnimation': 'animationend',
'WebkitAnimation': 'webkitAnimationEnd'
};
for (let t in animations) {
for (const t in animations) {
if (el.style[t] !== undefined) {
_animationEvent = animations[t];
return animations[t];
@ -251,7 +251,7 @@
'MozTransition': 'transitionend',
'WebkitTransition': 'webkitTransitionEnd'
};
for (let t in transitions) {
for (const t in transitions) {
if (el.style[t] !== undefined) {
_transitionEvent = transitions[t];
return transitions[t];

View file

@ -201,6 +201,9 @@ import appHost from 'apphost';
'rewind': () => {
playbackManager.rewind();
},
'seek': () => {
playbackManager.seekMs(options);
},
'togglefullscreen': () => {
playbackManager.toggleFullscreen();
},
@ -235,9 +238,6 @@ import appHost from 'apphost';
}
}
// Alias for backward compatibility
export const trigger = handleCommand;
dom.addEventListener(document, 'click', notify, {
passive: true
});
@ -245,8 +245,7 @@ import appHost from 'apphost';
/* eslint-enable indent */
export default {
trigger: handleCommand,
handle: handleCommand,
handleCommand: handleCommand,
notify: notify,
notifyMouseMove: notifyMouseMove,
idleTime: idleTime,

View file

@ -71,12 +71,12 @@ define(['connectionManager', 'listView', 'cardBuilder', 'imageLoader', 'libraryB
html += '<div class="' + sectionClass + '" data-type="' + section.type + '">';
html += '<div class="sectionTitleContainer sectionTitleContainer-cards">';
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">';
html += '<h2 class="sectionTitle sectionTitle-cards">';
html += section.name;
html += '</h2>';
html += '<a is="emby-linkbutton" href="#" class="clearLink hide" style="margin-left:1em;vertical-align:middle;"><button is="emby-button" type="button" class="raised more raised-mini noIcon">' + globalize.translate('ButtonMore') + '</button></a>';
html += '</div>';
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right">';
html += '<div is="emby-itemscontainer" class="itemsContainer padded-right">';
html += '</div>';
return html += '</div>';
}).join('');

View file

@ -78,7 +78,7 @@ export function isNavigationKey(key) {
}
export function enable() {
document.addEventListener('keydown', function (e) {
window.addEventListener('keydown', function (e) {
const key = getKeyName(e);
// Ignore navigation keys for non-TV
@ -90,53 +90,53 @@ export function enable() {
switch (key) {
case 'ArrowLeft':
inputManager.handle('left');
inputManager.handleCommand('left');
break;
case 'ArrowUp':
inputManager.handle('up');
inputManager.handleCommand('up');
break;
case 'ArrowRight':
inputManager.handle('right');
inputManager.handleCommand('right');
break;
case 'ArrowDown':
inputManager.handle('down');
inputManager.handleCommand('down');
break;
case 'Back':
inputManager.handle('back');
inputManager.handleCommand('back');
break;
case 'Escape':
if (layoutManager.tv) {
inputManager.handle('back');
inputManager.handleCommand('back');
} else {
capture = false;
}
break;
case 'MediaPlay':
inputManager.handle('play');
inputManager.handleCommand('play');
break;
case 'Pause':
inputManager.handle('pause');
inputManager.handleCommand('pause');
break;
case 'MediaPlayPause':
inputManager.handle('playpause');
inputManager.handleCommand('playpause');
break;
case 'MediaRewind':
inputManager.handle('rewind');
inputManager.handleCommand('rewind');
break;
case 'MediaFastForward':
inputManager.handle('fastforward');
inputManager.handleCommand('fastforward');
break;
case 'MediaStop':
inputManager.handle('stop');
inputManager.handleCommand('stop');
break;
case 'MediaTrackPrevious':
inputManager.handle('previoustrack');
inputManager.handleCommand('previoustrack');
break;
case 'MediaTrackNext':
inputManager.handle('nexttrack');
inputManager.handleCommand('nexttrack');
break;
default:

View file

@ -13,7 +13,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
html += '<div class="headerRight">';
html += '<span class="headerSelectedPlayer"></span>';
html += `<button is="paper-icon-button-light" class="headerSyncButton syncButton headerButton headerButtonRight hide" title="${globalize.translate('ButtonSyncPlay')}"><span class="material-icons sync_disabled"></span></button>`;
html += '<button is="paper-icon-button-light" class="headerAudioPlayerButton audioPlayerButton headerButton headerButtonRight hide"><span class="material-icons music_note"></span></button>';
html += `<button is="paper-icon-button-light" class="headerAudioPlayerButton audioPlayerButton headerButton headerButtonRight hide" title="${globalize.translate('ButtonPlayer')}"><span class="material-icons music_note"></span></button>`;
html += `<button is="paper-icon-button-light" class="headerCastButton castButton headerButton headerButtonRight hide" title="${globalize.translate('ButtonCast')}"><span class="material-icons cast"></span></button>`;
html += `<button type="button" is="paper-icon-button-light" class="headerButton headerButtonRight headerSearchButton hide" title="${globalize.translate('ButtonSearch')}"><span class="material-icons search"></span></button>`;
html += '<button is="paper-icon-button-light" class="headerButton headerButtonRight headerUserButton hide"><span class="material-icons person"></span></button>';
@ -89,7 +89,8 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
var policy = user.Policy ? user.Policy : user.localUser.Policy;
if (headerSyncButton && policy && policy.SyncPlayAccess !== 'None') {
var apiClient = getCurrentApiClient();
if (headerSyncButton && policy && policy.SyncPlayAccess !== 'None' && apiClient.isMinServerVersion('10.6.0')) {
headerSyncButton.classList.remove('hide');
}
} else {
@ -116,7 +117,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
}
function showSearch() {
inputManager.trigger('search');
inputManager.handleCommand('search');
}
function onHeaderUserButtonClick(e) {
@ -967,8 +968,10 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
updateUserInHeader();
});
events.on(playbackManager, 'playerchange', updateCastIcon);
events.on(syncPlayManager, 'enabled', onSyncPlayEnabled);
events.on(syncPlayManager, 'syncing', onSyncPlaySyncing);
loadNavDrawer();
return LibraryMenu;
});

View file

@ -136,6 +136,7 @@ define(['inputManager', 'focusManager', 'browser', 'layoutManager', 'events', 'd
stopMouseInterval();
/* eslint-disable-next-line compat/compat */
dom.removeEventListener(document, (window.PointerEvent ? 'pointermove' : 'mousemove'), onPointerMove, {
passive: true
});
@ -148,6 +149,7 @@ define(['inputManager', 'focusManager', 'browser', 'layoutManager', 'events', 'd
});
}
/* eslint-disable-next-line compat/compat */
dom.removeEventListener(document, (window.PointerEvent ? 'pointerenter' : 'mouseenter'), onPointerEnter, {
capture: true,
passive: true

View file

@ -15,7 +15,7 @@ define([
'detailtablecss'], function () {
function defineRoute(newRoute) {
var path = newRoute.path;
var path = newRoute.alias ? newRoute.alias : newRoute.path;
console.debug('defining route: ' + path);
newRoute.dictionary = 'core';
Emby.Page.addRoute(path, newRoute);
@ -240,8 +240,9 @@ define([
transition: 'fade'
});
defineRoute({
path: '/itemdetails.html',
controller: 'itemDetails',
alias: '/details',
path: '/controllers/itemDetails/index.html',
controller: 'itemDetails/index',
autoFocus: false,
transition: 'fade'
});

View file

@ -36,28 +36,28 @@ define(['connectionManager', 'playbackManager', 'syncPlayManager', 'events', 'in
console.debug('Received command: ' + cmd.Name);
switch (cmd.Name) {
case 'Select':
inputManager.trigger('select');
inputManager.handleCommand('select');
return;
case 'Back':
inputManager.trigger('back');
inputManager.handleCommand('back');
return;
case 'MoveUp':
inputManager.trigger('up');
inputManager.handleCommand('up');
return;
case 'MoveDown':
inputManager.trigger('down');
inputManager.handleCommand('down');
return;
case 'MoveLeft':
inputManager.trigger('left');
inputManager.handleCommand('left');
return;
case 'MoveRight':
inputManager.trigger('right');
inputManager.handleCommand('right');
return;
case 'PageUp':
inputManager.trigger('pageup');
inputManager.handleCommand('pageup');
return;
case 'PageDown':
inputManager.trigger('pagedown');
inputManager.handleCommand('pagedown');
return;
case 'PlayTrailers':
playTrailers(apiClient, cmd.Arguments.ItemId);
@ -65,26 +65,29 @@ define(['connectionManager', 'playbackManager', 'syncPlayManager', 'events', 'in
case 'SetRepeatMode':
playbackManager.setRepeatMode(cmd.Arguments.RepeatMode);
break;
case 'SetShuffleQueue':
playbackManager.setQueueShuffleMode(cmd.Arguments.ShuffleMode);
break;
case 'VolumeUp':
inputManager.trigger('volumeup');
inputManager.handleCommand('volumeup');
return;
case 'VolumeDown':
inputManager.trigger('volumedown');
inputManager.handleCommand('volumedown');
return;
case 'ChannelUp':
inputManager.trigger('channelup');
inputManager.handleCommand('channelup');
return;
case 'ChannelDown':
inputManager.trigger('channeldown');
inputManager.handleCommand('channeldown');
return;
case 'Mute':
inputManager.trigger('mute');
inputManager.handleCommand('mute');
return;
case 'Unmute':
inputManager.trigger('unmute');
inputManager.handleCommand('unmute');
return;
case 'ToggleMute':
inputManager.trigger('togglemute');
inputManager.handleCommand('togglemute');
return;
case 'SetVolume':
notifyApp();
@ -99,19 +102,19 @@ define(['connectionManager', 'playbackManager', 'syncPlayManager', 'events', 'in
playbackManager.setSubtitleStreamIndex(parseInt(cmd.Arguments.Index));
break;
case 'ToggleFullscreen':
inputManager.trigger('togglefullscreen');
inputManager.handleCommand('togglefullscreen');
return;
case 'GoHome':
inputManager.trigger('home');
inputManager.handleCommand('home');
return;
case 'GoToSettings':
inputManager.trigger('settings');
inputManager.handleCommand('settings');
return;
case 'DisplayContent':
displayContent(cmd, apiClient);
break;
case 'GoToSearch':
inputManager.trigger('search');
inputManager.handleCommand('search');
return;
case 'DisplayMessage':
displayMessage(cmd);
@ -162,19 +165,19 @@ define(['connectionManager', 'playbackManager', 'syncPlayManager', 'events', 'in
}
} else if (msg.MessageType === 'Playstate') {
if (msg.Data.Command === 'Stop') {
inputManager.trigger('stop');
inputManager.handleCommand('stop');
} else if (msg.Data.Command === 'Pause') {
inputManager.trigger('pause');
inputManager.handleCommand('pause');
} else if (msg.Data.Command === 'Unpause') {
inputManager.trigger('play');
inputManager.handleCommand('play');
} else if (msg.Data.Command === 'PlayPause') {
inputManager.trigger('playpause');
inputManager.handleCommand('playpause');
} else if (msg.Data.Command === 'Seek') {
playbackManager.seek(msg.Data.SeekPositionTicks);
} else if (msg.Data.Command === 'NextTrack') {
inputManager.trigger('next');
inputManager.handleCommand('next');
} else if (msg.Data.Command === 'PreviousTrack') {
inputManager.trigger('previous');
inputManager.handleCommand('previous');
} else {
notifyApp();
}

View file

@ -196,7 +196,7 @@ var Dashboard = {
capabilities: function (appHost) {
var capabilities = {
PlayableMediaTypes: ['Audio', 'Video'],
SupportedCommands: ['MoveUp', 'MoveDown', 'MoveLeft', 'MoveRight', 'PageUp', 'PageDown', 'PreviousLetter', 'NextLetter', 'ToggleOsd', 'ToggleContextMenu', 'Select', 'Back', 'SendKey', 'SendString', 'GoHome', 'GoToSettings', 'VolumeUp', 'VolumeDown', 'Mute', 'Unmute', 'ToggleMute', 'SetVolume', 'SetAudioStreamIndex', 'SetSubtitleStreamIndex', 'DisplayContent', 'GoToSearch', 'DisplayMessage', 'SetRepeatMode', 'ChannelUp', 'ChannelDown', 'PlayMediaSource', 'PlayTrailers'],
SupportedCommands: ['MoveUp', 'MoveDown', 'MoveLeft', 'MoveRight', 'PageUp', 'PageDown', 'PreviousLetter', 'NextLetter', 'ToggleOsd', 'ToggleContextMenu', 'Select', 'Back', 'SendKey', 'SendString', 'GoHome', 'GoToSettings', 'VolumeUp', 'VolumeDown', 'Mute', 'Unmute', 'ToggleMute', 'SetVolume', 'SetAudioStreamIndex', 'SetSubtitleStreamIndex', 'DisplayContent', 'GoToSearch', 'DisplayMessage', 'SetRepeatMode', 'SetShuffleQueue', 'ChannelUp', 'ChannelDown', 'PlayMediaSource', 'PlayTrailers'],
SupportsPersistentIdentifier: 'cordova' === self.appMode || 'android' === self.appMode,
SupportsMediaControl: true
};
@ -387,8 +387,6 @@ var AppInfo = {};
define('lazyLoader', [componentsPath + '/lazyLoader/lazyLoaderIntersectionObserver'], returnFirstDependency);
define('shell', [scriptsPath + '/shell'], returnFirstDependency);
define('registerElement', ['document-register-element'], returnFirstDependency);
define('alert', [componentsPath + '/alert'], returnFirstDependency);
defineResizeObserver();
@ -672,7 +670,6 @@ var AppInfo = {};
},
bundles: {
bundle: [
'document-register-element',
'fetch',
'flvjs',
'jstree',
@ -1039,7 +1036,7 @@ var AppInfo = {};
}
if ('SeriesTimer' == itemType) {
return 'itemdetails.html?seriesTimerId=' + id + '&serverId=' + serverId;
return 'details?seriesTimerId=' + id + '&serverId=' + serverId;
}
if ('livetv' == item.CollectionType) {
@ -1109,13 +1106,13 @@ var AppInfo = {};
var itemTypes = ['Playlist', 'TvChannel', 'Program', 'BoxSet', 'MusicAlbum', 'MusicGenre', 'Person', 'Recording', 'MusicArtist'];
if (itemTypes.indexOf(itemType) >= 0) {
return 'itemdetails.html?id=' + id + '&serverId=' + serverId;
return 'details?id=' + id + '&serverId=' + serverId;
}
var contextSuffix = context ? '&context=' + context : '';
if ('Series' == itemType || 'Season' == itemType || 'Episode' == itemType) {
return 'itemdetails.html?id=' + id + contextSuffix + '&serverId=' + serverId;
return 'details?id=' + id + contextSuffix + '&serverId=' + serverId;
}
if (item.IsFolder) {
@ -1126,7 +1123,7 @@ var AppInfo = {};
return '#';
}
return 'itemdetails.html?id=' + id + '&serverId=' + serverId;
return 'details?id=' + id + '&serverId=' + serverId;
};
appRouter.showItem = showItem;

View file

@ -1,10 +1,10 @@
<div id="selectServerPage" data-role="page" class="page noSecondaryNavPage standalonePage pageContainer fullWidth vertical flex flex-direction-column">
<div class="verticalSection flex-shrink-zero flex flex-direction-column" style="margin: 2em 0 1em;">
<div class="padded-left padded-right flex align-items-center justify-content-center" style="margin-bottom:2em;">
<div id="selectServerPage" data-role="page" class="page noSecondaryNavPage standalonePage pageContainer fullWidthContent vertical flex flex-direction-column align-items-center justify-content-center">
<div class="verticalSection flex-shrink-zero w-100 flex flex-direction-column">
<div class="padded-left padded-right flex align-items-center justify-content-center">
<h1 class="sectionTitle sectionTitle-cards">${HeaderSelectServer}</h1>
</div>
<div class="padded-top padded-bottom-focusscale flex-grow flex" data-horizontal="true" data-centerfocus="card">
<div is="emby-itemscontainer" class="scrollSlider focuscontainer-x padded-left padded-right servers flex-grow" style="display: block; text-align: center;" data-hovermenu="false" data-multiselect="false"></div>
<div is="emby-itemscontainer" class="scrollSlider focuscontainer-x servers flex-grow" style="display: block; text-align: center;" data-hovermenu="false" data-multiselect="false"></div>
</div>
</div>
<div class="padded-top padded-left padded-right flex flex-shrink-zero justify-content-center verticalSection flex-wrap-wrap" style="margin-left:auto;margin-right:auto;">

Some files were not shown because too many files have changed in this diff Show more