1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

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' targetPath: '$(Build.SourcesDirectory)/deployment/dist'
artifactName: 'jellyfin-web-$(BuildConfiguration)' 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 - task: CopyFilesOverSSH@0
displayName: 'Upload artifacts to repository server' displayName: 'Upload artifacts to repository server'
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) 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: update_configs:
- package_manager: "javascript" - package_manager: "javascript"
directory: "/" directory: "/"
update_schedule: "weekly" update_schedule: "live"

4
.github/CODEOWNERS vendored
View file

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

View file

@ -1,23 +1,20 @@
--- ---
name: Bug report name: Bug Report
about: Create a bug report about: You have noticed a general issue or regression, and would like to report it
title: ''
labels: bug labels: bug
assignees: ''
--- ---
**Describe the bug** **Describe The Bug**
<!-- A clear and concise description of what the bug is. --> <!-- A clear and concise description of what the bug is. -->
**To Reproduce** **Steps To Reproduce**
<!-- Steps to reproduce the behavior: --> <!-- Steps to reproduce the behavior: -->
1. Go to '...' 1. Go to '...'
2. Click on '....' 2. Click on '....'
3. Scroll down to '....' 3. Scroll down to '....'
4. See error 4. See error
**Expected behavior** **Expected Behavior**
<!-- A clear and concise description of what you expected to happen. --> <!-- A clear and concise description of what you expected to happen. -->
**Logs** **Logs**
@ -27,9 +24,9 @@ assignees: ''
<!-- If applicable, add screenshots to help explain your problem. --> <!-- If applicable, add screenshots to help explain your problem. -->
**System (please complete the following information):** **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] - 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. --> <!-- 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() { function injectBundle() {
return src(options.injectBundle.query, { base: './src/' }) return src(options.injectBundle.query, { base: './src/' })
.pipe(inject( .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(dest('dist/'))
.pipe(browserSync.stream()); .pipe(browserSync.stream());

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -222,46 +222,13 @@ define(['loading', 'globalize', 'events', 'viewManager', 'skinManager', 'backdro
} }
function normalizeImageOptions(options) { function normalizeImageOptions(options) {
var scaleFactor = browser.tv ? 0.8 : 1;
var setQuality; var setQuality;
if (options.maxWidth) { if (options.maxWidth || options.width || options.maxHeight || options.height) {
options.maxWidth = Math.round(options.maxWidth * scaleFactor);
setQuality = true; setQuality = true;
} }
if (options.width) { if (setQuality && !options.quality) {
options.width = Math.round(options.width * scaleFactor); options.quality = 90;
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;
} }
} }

View file

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

View file

@ -1310,7 +1310,7 @@ import 'programStyles';
} }
const mediaSourceCount = item.MediaSourceCount || 1; const mediaSourceCount = item.MediaSourceCount || 1;
if (mediaSourceCount > 1) { if (mediaSourceCount > 1 && options.disableIndicators !== true) {
innerCardFooter += '<div class="mediaSourceIndicator">' + mediaSourceCount + '</div>'; innerCardFooter += '<div class="mediaSourceIndicator">' + mediaSourceCount + '</div>';
} }
@ -1391,34 +1391,44 @@ import 'programStyles';
cardBoxClose = '</div>'; cardBoxClose = '</div>';
cardScalableClose = '</div>'; cardScalableClose = '</div>';
let indicatorsHtml = ''; if (options.disableIndicators !== true) {
let indicatorsHtml = '';
if (options.missingIndicator !== false) { if (options.missingIndicator !== false) {
indicatorsHtml += indicators.getMissingIndicator(item); indicatorsHtml += indicators.getMissingIndicator(item);
} }
indicatorsHtml += indicators.getSyncIndicator(item); indicatorsHtml += indicators.getSyncIndicator(item);
indicatorsHtml += indicators.getTimerIndicator(item); indicatorsHtml += indicators.getTimerIndicator(item);
indicatorsHtml += indicators.getTypeIndicator(item); indicatorsHtml += indicators.getTypeIndicator(item);
if (options.showGroupCount) { if (options.showGroupCount) {
indicatorsHtml += indicators.getChildCountIndicatorHtml(item, { indicatorsHtml += indicators.getChildCountIndicatorHtml(item, {
minCount: 1 minCount: 1
}); });
} else { } else {
indicatorsHtml += indicators.getPlayedIndicatorHtml(item); indicatorsHtml += indicators.getPlayedIndicatorHtml(item);
} }
<<<<<<< HEAD
if (item.Type === 'CollectionFolder' || item.CollectionType) { if (item.Type === 'CollectionFolder' || item.CollectionType) {
const refreshClass = item.RefreshProgress ? '' : ' class="hide"'; const refreshClass = item.RefreshProgress ? '' : ' class="hide"';
indicatorsHtml += '<div is="emby-itemrefreshindicator"' + refreshClass + ' data-progress="' + (item.RefreshProgress || 0) + '" data-status="' + item.RefreshStatus + '"></div>'; indicatorsHtml += '<div is="emby-itemrefreshindicator"' + refreshClass + ' data-progress="' + (item.RefreshProgress || 0) + '" data-status="' + item.RefreshStatus + '"></div>';
importRefreshIndicator(); 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) { if (indicatorsHtml) {
cardImageContainerOpen += '<div class="cardIndicators">' + indicatorsHtml + '</div>'; cardImageContainerOpen += '<div class="cardIndicators">' + indicatorsHtml + '</div>';
}
} }
if (!imgUrl) { if (!imgUrl) {
@ -1466,8 +1476,8 @@ import 'programStyles';
let additionalCardContent = ''; let additionalCardContent = '';
if (layoutManager.desktop) { if (layoutManager.desktop && !options.disableHoverMenu) {
additionalCardContent += getHoverMenuHtml(item, action); 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 + '>'; 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. * Generates HTML markup for the card overlay.
* @param {object} item - Item used to generate the card overlay. * @param {object} item - Item used to generate the card overlay.
* @param {string} action - Action assigned to the overlay. * @param {string} action - Action assigned to the overlay.
* @param {Array} options - Card builder options.
* @returns {string} HTML markup of the card overlay. * @returns {string} HTML markup of the card overlay.
*/ */
function getHoverMenuHtml(item, action) { function getHoverMenuHtml(item, action, options) {
let html = ''; let html = '';
html += '<div class="cardOverlayContainer itemAction" data-action="' + action + '">'; 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 += '<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>';
html += '</div>'; html += '</div>';
@ -1532,6 +1542,8 @@ import 'programStyles';
case 'MusicArtist': case 'MusicArtist':
case 'Person': case 'Person':
return '<span class="cardImageIcon material-icons person"></span>'; return '<span class="cardImageIcon material-icons person"></span>';
case 'Audio':
return '<span class="cardImageIcon material-icons audiotrack"></span>';
case 'Movie': case 'Movie':
return '<span class="cardImageIcon material-icons movie"></span>'; return '<span class="cardImageIcon material-icons movie"></span>';
case 'Series': case 'Series':
@ -1540,6 +1552,12 @@ import 'programStyles';
return '<span class="cardImageIcon material-icons book"></span>'; return '<span class="cardImageIcon material-icons book"></span>';
case 'Folder': case 'Folder':
return '<span class="cardImageIcon material-icons folder"></span>'; 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) { if (options && options.defaultCardImageIcon) {

View file

@ -25,7 +25,7 @@ import connectionManager from 'connectionManager';
return void appRouter.showItem(items[0]); return void appRouter.showItem(items[0]);
} }
var url = 'itemdetails.html?id=' + itemId + '&serverId=' + serverId; var url = 'details?id=' + itemId + '&serverId=' + serverId;
Dashboard.navigate(url); Dashboard.navigate(url);
}); });
e.stopPropagation(); 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'; 'use strict';
function showViewSettings(instance) { function showViewSettings(instance) {

View file

@ -12,7 +12,7 @@ import 'css!./style';
fillImageElement(elem, source); fillImageElement(elem, source);
} }
async function itemBlurhashing(target, blurhashstr) { function itemBlurhashing(target, blurhashstr) {
if (blurhash.isBlurhashValid(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, // 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. // 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); imgData.data.set(pixels);
ctx.putImageData(imgData, 0, 0); ctx.putImageData(imgData, 0, 0);
let child = target.appendChild(canvas); requestAnimationFrame(() => {
child.classList.add('blurhash-canvas'); canvas.classList.add('blurhash-canvas');
child.style.opacity = 1; if (userSettings.enableFastFadein()) {
if (userSettings.enableFastFadein()) { canvas.classList.add('lazy-blurhash-fadein-fast');
child.classList.add('lazy-blurhash-fadein-fast'); } else {
} else { canvas.classList.add('lazy-blurhash-fadein');
child.classList.add('lazy-blurhash-fadein'); }
}
target.classList.add('blurhashed'); target.parentNode.insertBefore(canvas, target);
target.removeAttribute('data-blurhash'); 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;
} }
} }
@ -66,23 +60,16 @@ import 'css!./style';
if (target) { if (target) {
source = target.getAttribute('data-src'); source = target.getAttribute('data-src');
var blurhashstr = target.getAttribute('data-blurhash');
} else { } else {
source = entry; 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 (entry.intersectionRatio > 0) {
if (source) fillImageElement(target, source); if (source) fillImageElement(target, source);
} else if (!source) { } else if (!source) {
emptyImageElement(target); requestAnimationFrame(() => {
emptyImageElement(target);
});
} }
} }
@ -94,29 +81,24 @@ import 'css!./style';
let preloaderImg = new Image(); let preloaderImg = new Image();
preloaderImg.src = url; preloaderImg.src = url;
// This is necessary here, so changing blurhash settings without reloading the page works elem.classList.add('lazy-hidden');
if (!userSettings.enableBlurhash() || elem.classList.contains('non-blurhashable')) {
elem.classList.add('lazy-hidden');
}
preloaderImg.addEventListener('load', () => { preloaderImg.addEventListener('load', () => {
if (elem.tagName !== 'IMG') { requestAnimationFrame(() => {
elem.style.backgroundImage = "url('" + url + "')"; if (elem.tagName !== 'IMG') {
} else { elem.style.backgroundImage = "url('" + url + "')";
elem.setAttribute('src', url); } else {
} elem.setAttribute('src', url);
elem.removeAttribute('data-src'); }
elem.removeAttribute('data-src');
if (elem.classList.contains('non-blurhashable') || !userSettings.enableBlurhash()) {
elem.classList.remove('lazy-hidden'); elem.classList.remove('lazy-hidden');
if (userSettings.enableFastFadein()) { if (userSettings.enableFastFadein()) {
elem.classList.add('lazy-image-fadein-fast'); elem.classList.add('lazy-image-fadein-fast');
} else { } else {
elem.classList.add('lazy-image-fadein'); elem.classList.add('lazy-image-fadein');
} }
} else { });
switchCanvas(elem);
}
}); });
} }
@ -132,15 +114,21 @@ import 'css!./style';
} }
elem.setAttribute('data-src', url); 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.remove('lazy-image-fadein-fast', 'lazy-image-fadein'); elem.classList.add('lazy-hidden');
elem.classList.add('lazy-hidden');
} else {
switchCanvas(elem);
}
} }
export function lazyChildren(elem) { 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); 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 */ /* eslint-enable indent */
export default { export default {
serLazyImage: setLazyImage,
fillImages: fillImages, fillImages: fillImages,
fillImage: fillImage, fillImage: fillImage,
lazyImage: lazyImage, lazyImage: lazyImage,

View file

@ -12,12 +12,22 @@
opacity: 0; opacity: 0;
} }
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.lazy-blurhash-fadein-fast { .lazy-blurhash-fadein-fast {
transition: opacity 0.2s; animation: fadein 0.1s;
} }
.lazy-blurhash-fadein { .lazy-blurhash-fadein {
transition: opacity 0.7s; animation: fadein 0.4s;
} }
.blurhash-canvas { .blurhash-canvas {
@ -28,6 +38,5 @@
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: 100;
pointer-events: none; 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 (playbackManager.canQueue(item)) {
if (options.queue !== false) { if (options.queue !== false) {
commands.push({ commands.push({
@ -54,13 +71,6 @@ import actionsheet from 'actionsheet';
icon: 'playlist_add' icon: 'playlist_add'
}); });
} }
//if (options.queueAllFromHere) {
// commands.push({
// name: globalize.translate("QueueAllFromHere"),
// id: "queueallfromhere"
// });
//}
} }
if (item.IsFolder || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') { if (item.IsFolder || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') {
@ -298,10 +308,11 @@ import actionsheet from 'actionsheet';
icon: 'album' icon: 'album'
}); });
} }
// Show Album Artist by default, as a song can have multiple artists, which specific one would this option refer to?
if (options.openArtist !== false && item.ArtistItems && item.ArtistItems.length) { // 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({ commands.push({
name: globalize.translate('ViewArtist'), name: globalize.translate('ViewAlbumArtist'),
id: 'artist', id: 'artist',
icon: 'person' icon: 'person'
}); });
@ -441,6 +452,12 @@ import actionsheet from 'actionsheet';
play(item, false, true, true); play(item, false, true, true);
getResolveFunction(resolve, id)(); getResolveFunction(resolve, id)();
break; break;
case 'stopPlayback':
playbackManager.stop();
break;
case 'clearQueue':
playbackManager.clearQueue();
break;
case 'record': case 'record':
import('recordingCreator').then(({default: recordingCreator}) => { import('recordingCreator').then(({default: recordingCreator}) => {
recordingCreator.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); recordingCreator.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id));
@ -469,7 +486,7 @@ import actionsheet from 'actionsheet';
getResolveFunction(resolve, id)(); getResolveFunction(resolve, id)();
break; break;
case 'artist': case 'artist':
appRouter.showItem(item.ArtistItems[0].Id, item.ServerId); appRouter.showItem(item.AlbumArtists[0].Id, item.ServerId);
getResolveFunction(resolve, id)(); getResolveFunction(resolve, id)();
break; break;
case 'playallfromhere': case 'playallfromhere':

View file

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

View file

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

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
/* eslint-disable indent */ /* eslint-disable indent */
/** /**
@ -15,6 +16,10 @@ import datetime from 'datetime';
import 'css!./listview'; import 'css!./listview';
import 'emby-ratingbutton'; import 'emby-ratingbutton';
import 'emby-playstatebutton'; 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) { function getIndex(item, options) {
@ -106,11 +111,8 @@ import 'emby-playstatebutton';
itemId = item.ParentPrimaryImageItemId; itemId = item.ParentPrimaryImageItemId;
} }
let blurHashes = item.ImageBlurHashes || {};
let blurhashstr = (blurHashes[options.type] || {})[options.tag];
if (itemId) { if (itemId) {
return { url: apiClient.getScaledImageUrl(itemId, options), blurhash: blurhashstr }; return apiClient.getScaledImageUrl(itemId, options);
} }
return null; return null;
} }
@ -126,24 +128,30 @@ import 'emby-playstatebutton';
if (item.ChannelId && item.ChannelPrimaryImageTag) { if (item.ChannelId && item.ChannelPrimaryImageTag) {
options.tag = item.ChannelPrimaryImageTag; options.tag = item.ChannelPrimaryImageTag;
} }
let blurHashes = item.ImageBlurHashes || {};
let blurhashstr = (blurHashes[options.type])[options.tag];
if (item.ChannelId) { if (item.ChannelId) {
return { url: apiClient.getScaledImageUrl(item.ChannelId, options), blurhash: blurhashstr }; return apiClient.getScaledImageUrl(item.ChannelId, options);
} }
} }
function getTextLinesHtml(textlines, isLargeStyle) { function getTextLinesHtml(textlines, isLargeStyle) {
<<<<<<< HEAD
let html = ''; let html = '';
=======
var html = '';
>>>>>>> upstream/master
const largeTitleTagName = layoutManager.tv ? 'h2' : 'div'; const largeTitleTagName = layoutManager.tv ? 'h2' : 'div';
<<<<<<< HEAD
for (let i = 0, length = textlines.length; i < length; i++) { for (let i = 0, length = textlines.length; i < length; i++) {
const text = textlines[i]; const text = textlines[i];
=======
for (const [i, text] of textlines.entries()) {
>>>>>>> upstream/master
if (!text) { if (!text) {
continue; continue;
} }
@ -284,10 +292,8 @@ import 'emby-playstatebutton';
} }
if (options.image !== false) { if (options.image !== false) {
let imgData = options.imageSource === 'channel' ? getChannelImageUrl(item, downloadWidth) : getImageUrl(item, downloadWidth); var imgUrl = options.imageSource === 'channel' ? getChannelImageUrl(item, downloadWidth) : getImageUrl(item, downloadWidth);
let imgUrl = imgData.url; var imageClass = isLargeStyle ? 'listItemImage listItemImage-large' : 'listItemImage';
let blurhash = imgData.blurhash;
let imageClass = isLargeStyle ? 'listItemImage listItemImage-large' : 'listItemImage';
if (isLargeStyle && layoutManager.tv) { if (isLargeStyle && layoutManager.tv) {
imageClass += ' listItemImage-large-tv'; imageClass += ' listItemImage-large-tv';
@ -299,6 +305,7 @@ import 'emby-playstatebutton';
imageClass += ' itemAction'; imageClass += ' itemAction';
} }
<<<<<<< HEAD
const imageAction = playOnImageClick ? 'resume' : action; const imageAction = playOnImageClick ? 'resume' : action;
let blurhashAttrib = ''; let blurhashAttrib = '';
@ -310,6 +317,14 @@ import 'emby-playstatebutton';
html += `<div data-action="${imageAction}" class="${imageClass} lazy" data-src="${imgUrl}" ${blurhashAttrib} item-icon>`; html += `<div data-action="${imageAction}" class="${imageClass} lazy" data-src="${imgUrl}" ${blurhashAttrib} item-icon>`;
} else { } else {
html += `<div class="${imageClass}">`; 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 = ''; let indicatorsHtml = '';
@ -449,8 +464,6 @@ import 'emby-playstatebutton';
html += `<div class="${cssClass}">`; html += `<div class="${cssClass}">`;
const moreIcon = 'more_vert';
html += getTextLinesHtml(textlines, isLargeStyle); html += getTextLinesHtml(textlines, isLargeStyle);
if (options.mediaInfo !== false) { 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>'; 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) { if (options.moreButton !== false) {
html += `<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="menu"><span class="material-icons ${moreIcon}"></span></button>`; 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) { if (options.infoButton) {
html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="link"><span class="material-icons info_outline"></span></button>'; 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 userData = item.UserData || {};
const likes = userData.Likes == null ? '' : userData.Likes; const likes = userData.Likes == null ? '' : userData.Likes;
<<<<<<< HEAD
if (itemHelper.canMarkPlayed(item)) { 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>`; 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)) { 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>`; 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>'; 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'; 'use strict';
if (browser.tizen || browser.operaTv || browser.chromecast || browser.orsay || browser.web0s || browser.ps4) {
return loadingLegacy;
}
var loadingElem; var loadingElem;
var layer1; var layer1;
var layer2; 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; width: auto !important;
height: auto !important; height: auto !important;
font-size: 1.4em; font-size: 1.4em;
margin-right: 0.125em;
color: #f2b01e;
} }
.mediaInfoCriticRating { .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) { function onEditorClick(e) {
const btnRemoveFromEditorList = dom.parentWithClass(e.target, 'btnRemoveFromEditorList'); const btnRemoveFromEditorList = dom.parentWithClass(e.target, 'btnRemoveFromEditorList');
@ -291,7 +328,6 @@ import 'flexStyles';
} }
function init(context, apiClient) { function init(context, apiClient) {
context.querySelector('.externalIds').addEventListener('click', function (e) { context.querySelector('.externalIds').addEventListener('click', function (e) {
const btnOpenExternalId = dom.parentWithClass(e.target, 'btnOpenExternalId'); const btnOpenExternalId = dom.parentWithClass(e.target, 'btnOpenExternalId');
if (btnOpenExternalId) { if (btnOpenExternalId) {
@ -315,13 +351,17 @@ import 'flexStyles';
closeDialog(false); 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('.btnSave').click();
}); });
context.querySelector('#chkLockData').addEventListener('click', function (e) { context.querySelector('#chkLockData').addEventListener('click', function (e) {
if (!e.target.checked) { if (!e.target.checked) {
showElement('.providerSettingsContainer'); showElement('.providerSettingsContainer');
} else { } else {
@ -1107,6 +1147,7 @@ import 'flexStyles';
elem.innerHTML = globalize.translateHtml(template, 'core'); elem.innerHTML = globalize.translateHtml(template, 'core');
elem.querySelector('.formDialogFooter').classList.remove('formDialogFooter'); elem.querySelector('.formDialogFooter').classList.remove('formDialogFooter');
elem.querySelector('.btnClose').classList.add('hide');
elem.querySelector('.btnHeaderSave').classList.remove('hide'); elem.querySelector('.btnHeaderSave').classList.remove('hide');
elem.querySelector('.btnCancel').classList.add('hide'); elem.querySelector('.btnCancel').classList.add('hide');

View file

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

View file

@ -56,8 +56,8 @@
text-align: left; text-align: left;
flex-grow: 1; flex-grow: 1;
font-size: 92%; font-size: 92%;
margin-right: 2.4em; margin-right: 1em;
margin-left: 1em; margin-left: 0.5em;
} }
.nowPlayingBarCenter { .nowPlayingBarCenter {
@ -114,8 +114,6 @@
.nowPlayingBarUserDataButtons { .nowPlayingBarUserDataButtons {
display: inline-block; display: inline-block;
margin-left: 1em;
margin-right: 1em;
} }
.nowPlayingBarPositionSlider::-webkit-slider-thumb { .nowPlayingBarPositionSlider::-webkit-slider-thumb {
@ -133,33 +131,50 @@
.toggleRepeatButton { .toggleRepeatButton {
display: none !important; display: none !important;
} }
}
@media all and (max-width: 62em) { .nowPlayingBar .btnShuffleQueue {
.nowPlayingBarCenter .nowPlayingBarCurrentTime {
display: none !important; 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) { @media all and (max-width: 56em) {
.nowPlayingBarCenter { .nowPlayingBarCenter {
display: none !important; display: none !important;
} }
} }
@media all and (min-width: 56em) { @media all and (max-width: 60em) {
.nowPlayingBarRight .playPauseButton {
display: none;
}
}
@media all and (max-width: 36em) {
.nowPlayingBarRight .nowPlayingBarVolumeSliderContainer { .nowPlayingBarRight .nowPlayingBarVolumeSliderContainer {
display: none !important; display: none !important;
} }
.nowPlayingBarInfoContainer { .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="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="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 class="nowPlayingBarCurrentTime"></div>';
html += '</div>'; html += '</div>';
@ -76,12 +78,17 @@ import 'emby-ratingbutton';
html += '</div>'; 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="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 class="nowPlayingBarUserDataButtons">';
html += '</div>'; 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="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>';
html += '</div>'; html += '</div>';
@ -132,8 +139,13 @@ import 'emby-ratingbutton';
nowPlayingImageElement = elem.querySelector('.nowPlayingImage'); nowPlayingImageElement = elem.querySelector('.nowPlayingImage');
nowPlayingTextElement = elem.querySelector('.nowPlayingBarText'); nowPlayingTextElement = elem.querySelector('.nowPlayingBarText');
nowPlayingUserData = elem.querySelector('.nowPlayingBarUserDataButtons'); nowPlayingUserData = elem.querySelector('.nowPlayingBarUserDataButtons');
positionSlider = elem.querySelector('.nowPlayingBarPositionSlider');
muteButton = elem.querySelector('.muteButton'); muteButton = elem.querySelector('.muteButton');
playPauseButtons = elem.querySelectorAll('.playPauseButton');
toggleRepeatButton = elem.querySelector('.toggleRepeatButton');
volumeSlider = elem.querySelector('.nowPlayingBarVolumeSlider');
volumeSliderContainer = elem.querySelector('.nowPlayingBarVolumeSliderContainer');
muteButton.addEventListener('click', function () { muteButton.addEventListener('click', function () {
if (currentPlayer) { if (currentPlayer) {
@ -149,7 +161,6 @@ import 'emby-ratingbutton';
} }
}); });
playPauseButtons = elem.querySelectorAll('.playPauseButton');
playPauseButtons.forEach((button) => { playPauseButtons.forEach((button) => {
button.addEventListener('click', onPlayPauseClick); 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) { if (currentPlayer) {
playbackManager.previousTrack(currentPlayer); playbackManager.previousTrack(currentPlayer);
} }
}); });
elem.querySelector('.btnShuffleQueue').addEventListener('click', function () {
if (currentPlayer) {
playbackManager.toggleQueueShuffleMode();
}
});
toggleRepeatButton = elem.querySelector('.toggleRepeatButton'); toggleRepeatButton = elem.querySelector('.toggleRepeatButton');
toggleRepeatButton.addEventListener('click', function () { toggleRepeatButton.addEventListener('click', function () {
switch (playbackManager.getRepeatMode()) {
if (currentPlayer) { case 'RepeatAll':
playbackManager.setRepeatMode('RepeatOne');
switch (playbackManager.getRepeatMode(currentPlayer)) { break;
case 'RepeatAll': case 'RepeatOne':
playbackManager.setRepeatMode('RepeatOne', currentPlayer); playbackManager.setRepeatMode('RepeatNone');
break; break;
case 'RepeatOne': case 'RepeatNone':
playbackManager.setRepeatMode('RepeatNone', currentPlayer); playbackManager.setRepeatMode('RepeatAll');
break;
default:
playbackManager.setRepeatMode('RepeatAll', currentPlayer);
break;
}
} }
}); });
toggleRepeatButtonIcon = toggleRepeatButton.querySelector('.material-icons'); toggleRepeatButtonIcon = toggleRepeatButton.querySelector('.material-icons');
volumeSlider = elem.querySelector('.nowPlayingBarVolumeSlider'); volumeSliderContainer.classList.toggle('hide', appHost.supports('physicalvolumecontrol'));
volumeSliderContainer = elem.querySelector('.nowPlayingBarVolumeSliderContainer');
if (appHost.supports('physicalvolumecontrol')) { volumeSlider.addEventListener('input', (e) => {
volumeSliderContainer.classList.add('hide');
} else {
volumeSliderContainer.classList.remove('hide');
}
function setVolume() {
if (currentPlayer) { 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 () { positionSlider.addEventListener('change', function () {
if (currentPlayer) { if (currentPlayer) {
@ -277,6 +293,11 @@ import 'emby-ratingbutton';
parentContainer.insertAdjacentHTML('afterbegin', getNowPlayingBarHtml()); parentContainer.insertAdjacentHTML('afterbegin', getNowPlayingBarHtml());
nowPlayingBarElement = parentContainer.querySelector('.nowPlayingBar'); nowPlayingBarElement = parentContainer.querySelector('.nowPlayingBar');
if (layoutManager.mobile) {
hideButton(nowPlayingBarElement.querySelector('.btnShuffleQueue'));
hideButton(nowPlayingBarElement.querySelector('.nowPlayingBarCenter'));
}
if (browser.safari && browser.slow) { if (browser.safari && browser.slow) {
// Not handled well here. The wrong elements receive events, bar doesn't update quickly enough, etc. // Not handled well here. The wrong elements receive events, bar doesn't update quickly enough, etc.
nowPlayingBarElement.classList.add('noMediaProgress'); nowPlayingBarElement.classList.add('noMediaProgress');
@ -329,7 +350,8 @@ import 'emby-ratingbutton';
toggleRepeatButton.classList.remove('hide'); toggleRepeatButton.classList.remove('hide');
} }
updateRepeatModeDisplay(playState.RepeatMode); updateRepeatModeDisplay(playbackManager.getRepeatMode());
onQueueShuffleModeChange();
updatePlayerVolumeState(playState.IsMuted, playState.VolumeLevel); updatePlayerVolumeState(playState.IsMuted, playState.VolumeLevel);
@ -349,32 +371,39 @@ import 'emby-ratingbutton';
function updateRepeatModeDisplay(repeatMode) { function updateRepeatModeDisplay(repeatMode) {
toggleRepeatButtonIcon.classList.remove('repeat', 'repeat_one'); toggleRepeatButtonIcon.classList.remove('repeat', 'repeat_one');
const cssClass = 'buttonActive';
if (repeatMode === 'RepeatAll') { switch (repeatMode) {
toggleRepeatButtonIcon.classList.add('repeat'); case 'RepeatAll':
toggleRepeatButton.classList.add('repeatButton-active'); toggleRepeatButtonIcon.classList.add('repeat');
} else if (repeatMode === 'RepeatOne') { toggleRepeatButton.classList.add(cssClass);
toggleRepeatButtonIcon.classList.add('repeat_one'); break;
toggleRepeatButton.classList.add('repeatButton-active'); case 'RepeatOne':
} else { toggleRepeatButtonIcon.classList.add('repeat_one');
toggleRepeatButtonIcon.classList.add('repeat'); toggleRepeatButton.classList.add(cssClass);
toggleRepeatButton.classList.remove('repeatButton-active'); break;
case 'RepeatNone':
default:
toggleRepeatButtonIcon.classList.add('repeat');
toggleRepeatButton.classList.remove(cssClass);
break;
} }
} }
function updateTimeDisplay(positionTicks, runtimeTicks, bufferedRanges) { function updateTimeDisplay(positionTicks, runtimeTicks, bufferedRanges) {
// See bindEvents for why this is necessary // See bindEvents for why this is necessary
if (positionSlider && !positionSlider.dragging) { if (positionSlider && !positionSlider.dragging) {
if (runtimeTicks) { if (runtimeTicks) {
<<<<<<< HEAD
let pct = positionTicks / runtimeTicks; let pct = positionTicks / runtimeTicks;
=======
var pct = positionTicks / runtimeTicks;
>>>>>>> upstream/master
pct *= 100; pct *= 100;
positionSlider.value = pct; positionSlider.value = pct;
} else { } else {
positionSlider.value = 0; positionSlider.value = 0;
} }
} }
@ -384,9 +413,13 @@ import 'emby-ratingbutton';
} }
if (currentTimeElement) { if (currentTimeElement) {
<<<<<<< HEAD
let timeText = positionTicks == null ? '--:--' : datetime.getDisplayRunningTime(positionTicks); let timeText = positionTicks == null ? '--:--' : datetime.getDisplayRunningTime(positionTicks);
=======
var timeText = positionTicks == null ? '--:--' : datetime.getDisplayRunningTime(positionTicks);
>>>>>>> upstream/master
if (runtimeTicks) { if (runtimeTicks) {
timeText += ' / ' + datetime.getDisplayRunningTime(runtimeTicks); timeText += ' / ' + datetime.getDisplayRunningTime(runtimeTicks);
} }
@ -428,11 +461,7 @@ import 'emby-ratingbutton';
// See bindEvents for why this is necessary // See bindEvents for why this is necessary
if (volumeSlider) { if (volumeSlider) {
if (showVolumeSlider) { volumeSliderContainer.classList.toggle('hide', !showVolumeSlider);
volumeSliderContainer.classList.remove('hide');
} else {
volumeSliderContainer.classList.add('hide');
}
if (!volumeSlider.dragging) { if (!volumeSlider.dragging) {
volumeSlider.value = volumeLevel || 0; 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) { function seriesImageUrl(item, options) {
if (!item) { if (!item) {
@ -520,6 +540,7 @@ import 'emby-ratingbutton';
const nowPlayingItem = state.NowPlayingItem; const nowPlayingItem = state.NowPlayingItem;
<<<<<<< HEAD
const textLines = nowPlayingItem ? nowPlayingHelper.getNowPlayingNames(nowPlayingItem) : []; const textLines = nowPlayingItem ? nowPlayingHelper.getNowPlayingNames(nowPlayingItem) : [];
if (textLines.length > 1) { if (textLines.length > 1) {
textLines[1].secondary = true; textLines[1].secondary = true;
@ -536,6 +557,31 @@ import 'emby-ratingbutton';
return `<div ${cssClass}>${nowPlayingText}</div>`; return `<div ${cssClass}>${nowPlayingText}</div>`;
}).join(''); }).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; const imgHeight = 70;
@ -553,8 +599,12 @@ import 'emby-ratingbutton';
if (url) { if (url) {
imageLoader.lazyImage(nowPlayingImageElement, url); imageLoader.lazyImage(nowPlayingImageElement, url);
nowPlayingImageElement.style.display = null;
nowPlayingTextElement.style.marginLeft = null;
} else { } else {
nowPlayingImageElement.style.backgroundImage = ''; nowPlayingImageElement.style.backgroundImage = '';
nowPlayingImageElement.style.display = 'none';
nowPlayingTextElement.style.marginLeft = '1em';
} }
} }
@ -563,6 +613,7 @@ import 'emby-ratingbutton';
const apiClient = connectionManager.getApiClient(nowPlayingItem.ServerId); const apiClient = connectionManager.getApiClient(nowPlayingItem.ServerId);
apiClient.getItem(apiClient.getCurrentUserId(), nowPlayingItem.Id).then(function (item) { apiClient.getItem(apiClient.getCurrentUserId(), nowPlayingItem.Id).then(function (item) {
<<<<<<< HEAD
const userData = item.UserData || {}; const userData = item.UserData || {};
const likes = userData.Likes == null ? '' : userData.Likes; const likes = userData.Likes == null ? '' : userData.Likes;
const contextButton = document.querySelector('.btnToggleContextMenu'); const contextButton = document.querySelector('.btnToggleContextMenu');
@ -578,8 +629,32 @@ import 'emby-ratingbutton';
item: item, item: item,
user: user user: user
}, options )); }, 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 { } else {
@ -589,25 +664,49 @@ import 'emby-ratingbutton';
function onPlaybackStart(e, state) { function onPlaybackStart(e, state) {
console.debug('nowplaying event: ' + e.type); console.debug('nowplaying event: ' + e.type);
<<<<<<< HEAD
const player = this; const player = this;
=======
var player = this;
>>>>>>> upstream/master
onStateChanged.call(player, e, state); onStateChanged.call(player, e, state);
} }
function onRepeatModeChange(e) { function onRepeatModeChange() {
if (!isEnabled) { if (!isEnabled) {
return; return;
} }
<<<<<<< HEAD
const player = this; 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() { function showNowPlayingBar() {
if (!isVisibilityAllowed) { if (!isVisibilityAllowed) {
hideNowPlayingBar(); hideNowPlayingBar();
return; return;
@ -711,6 +810,7 @@ import 'emby-ratingbutton';
events.off(player, 'playbackstart', onPlaybackStart); events.off(player, 'playbackstart', onPlaybackStart);
events.off(player, 'statechange', onPlaybackStart); events.off(player, 'statechange', onPlaybackStart);
events.off(player, 'repeatmodechange', onRepeatModeChange); events.off(player, 'repeatmodechange', onRepeatModeChange);
events.off(player, 'shufflequeuemodechange', onQueueShuffleModeChange);
events.off(player, 'playbackstop', onPlaybackStopped); events.off(player, 'playbackstop', onPlaybackStopped);
events.off(player, 'volumechange', onVolumeChanged); events.off(player, 'volumechange', onVolumeChanged);
events.off(player, 'pause', onPlayPauseStateChanged); events.off(player, 'pause', onPlayPauseStateChanged);
@ -759,6 +859,7 @@ import 'emby-ratingbutton';
events.on(player, 'playbackstart', onPlaybackStart); events.on(player, 'playbackstart', onPlaybackStart);
events.on(player, 'statechange', onPlaybackStart); events.on(player, 'statechange', onPlaybackStart);
events.on(player, 'repeatmodechange', onRepeatModeChange); events.on(player, 'repeatmodechange', onRepeatModeChange);
events.on(player, 'shufflequeuemodechange', onQueueShuffleModeChange);
events.on(player, 'playbackstop', onPlaybackStopped); events.on(player, 'playbackstop', onPlaybackStopped);
events.on(player, 'volumechange', onVolumeChanged); events.on(player, 'volumechange', onVolumeChanged);
events.on(player, 'pause', onPlayPauseStateChanged); 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) { 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'; 'use strict';
/** Delay time in ms for reportPlayback logging */
const reportPlaybackLogDelay = 1e3;
function enableLocalPlaylistManagement(player) { function enableLocalPlaylistManagement(player) {
if (player.getPlaylist) { if (player.getPlaylist) {
@ -43,12 +40,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
events.trigger(playbackManagerInstance, 'playerchange', [newPlayer, newTarget, previousPlayer]); 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) { function reportPlayback(playbackManagerInstance, state, player, reportPlaylist, serverId, method, progressEventName) {
if (!serverId) { if (!serverId) {
@ -69,14 +60,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
addPlaylistToPlaybackReport(playbackManagerInstance, info, player, serverId); 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 apiClient = connectionManager.getApiClient(serverId);
var reportPlaybackPromise = apiClient[method](info); var reportPlaybackPromise = apiClient[method](info);
// Notify that report has been sent // Notify that report has been sent
@ -2097,6 +2080,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
state.PlayState.IsMuted = player.isMuted(); state.PlayState.IsMuted = player.isMuted();
state.PlayState.IsPaused = player.paused(); state.PlayState.IsPaused = player.paused();
state.PlayState.RepeatMode = self.getRepeatMode(player); state.PlayState.RepeatMode = self.getRepeatMode(player);
state.PlayState.ShuffleMode = self.getQueueShuffleMode(player);
state.PlayState.MaxStreamingBitrate = self.getMaxStreamingBitrate(player); state.PlayState.MaxStreamingBitrate = self.getMaxStreamingBitrate(player);
state.PlayState.PositionTicks = getCurrentTicks(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); queue(options, '', player);
}; };
self.queueNext = function (options, player) { self.queueNext = function (options, player = this._currentPlayer) {
queue(options, 'next', player); queue(options, 'next', player);
}; };
@ -2969,6 +2953,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
} else { } else {
self._playQueueManager.queue(items); self._playQueueManager.queue(items);
} }
events.trigger(player, 'playlistitemadd');
} }
function onPlayerProgressInterval() { function onPlayerProgressInterval() {
@ -3304,6 +3289,11 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
sendProgressUpdate(player, 'repeatmodechange'); sendProgressUpdate(player, 'repeatmodechange');
} }
function onShuffleQueueModeChange() {
var player = this;
sendProgressUpdate(player, 'shufflequeuemodechange');
}
function onPlaylistItemMove(e) { function onPlaylistItemMove(e) {
var player = this; var player = this;
sendProgressUpdate(player, 'playlistitemmove', true); sendProgressUpdate(player, 'playlistitemmove', true);
@ -3358,6 +3348,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
events.on(player, 'unpause', onPlaybackUnpause); events.on(player, 'unpause', onPlaybackUnpause);
events.on(player, 'volumechange', onPlaybackVolumeChange); events.on(player, 'volumechange', onPlaybackVolumeChange);
events.on(player, 'repeatmodechange', onRepeatModeChange); events.on(player, 'repeatmodechange', onRepeatModeChange);
events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange);
events.on(player, 'playlistitemmove', onPlaylistItemMove); events.on(player, 'playlistitemmove', onPlaylistItemMove);
events.on(player, 'playlistitemremove', onPlaylistItemRemove); events.on(player, 'playlistitemremove', onPlaylistItemRemove);
events.on(player, 'playlistitemadd', onPlaylistItemAdd); events.on(player, 'playlistitemadd', onPlaylistItemAdd);
@ -3370,6 +3361,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
events.on(player, 'unpause', onPlaybackUnpause); events.on(player, 'unpause', onPlaybackUnpause);
events.on(player, 'volumechange', onPlaybackVolumeChange); events.on(player, 'volumechange', onPlaybackVolumeChange);
events.on(player, 'repeatmodechange', onRepeatModeChange); events.on(player, 'repeatmodechange', onRepeatModeChange);
events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange);
events.on(player, 'playlistitemmove', onPlaylistItemMove); events.on(player, 'playlistitemmove', onPlaylistItemMove);
events.on(player, 'playlistitemremove', onPlaylistItemRemove); events.on(player, 'playlistitemremove', onPlaylistItemRemove);
events.on(player, 'playlistitemadd', onPlaylistItemAdd); events.on(player, 'playlistitemadd', onPlaylistItemAdd);
@ -3655,6 +3647,14 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
this.seek(parseInt(ticks), player); 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) { PlaybackManager.prototype.playTrailers = function (item) {
var player = this._currentPlayer; var player = this._currentPlayer;
@ -3702,7 +3702,6 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
}; };
PlaybackManager.prototype.stop = function (player) { PlaybackManager.prototype.stop = function (player) {
player = player || this._currentPlayer; player = player || this._currentPlayer;
if (player) { 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; player = player || this._currentPlayer;
if (player && player.shuffle) { if (player && player.shuffle) {
@ -3878,6 +3877,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
'GoToSearch', 'GoToSearch',
'DisplayMessage', 'DisplayMessage',
'SetRepeatMode', 'SetRepeatMode',
'SetShuffleQueue',
'PlayMediaSource', 'PlayMediaSource',
'PlayTrailers' 'PlayTrailers'
]; ];
@ -3911,9 +3911,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
return info ? info.supportedCommands : []; return info ? info.supportedCommands : [];
}; };
PlaybackManager.prototype.setRepeatMode = function (value, player) { PlaybackManager.prototype.setRepeatMode = function (value, player = this._currentPlayer) {
player = player || this._currentPlayer;
if (player && !enableLocalPlaylistManagement(player)) { if (player && !enableLocalPlaylistManagement(player)) {
return player.setRepeatMode(value); return player.setRepeatMode(value);
} }
@ -3922,9 +3920,7 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
events.trigger(player, 'repeatmodechange'); events.trigger(player, 'repeatmodechange');
}; };
PlaybackManager.prototype.getRepeatMode = function (player) { PlaybackManager.prototype.getRepeatMode = function (player = this._currentPlayer) {
player = player || this._currentPlayer;
if (player && !enableLocalPlaylistManagement(player)) { if (player && !enableLocalPlaylistManagement(player)) {
return player.getRepeatMode(); return player.getRepeatMode();
} }
@ -3932,6 +3928,52 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
return this._playQueueManager.getRepeatMode(); 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) { PlaybackManager.prototype.trySetActiveDeviceName = function (name) {
name = normalizeName(name); name = normalizeName(name);
@ -4000,6 +4042,9 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
case 'SetRepeatMode': case 'SetRepeatMode':
this.setRepeatMode(cmd.Arguments.RepeatMode, player); this.setRepeatMode(cmd.Arguments.RepeatMode, player);
break; break;
case 'SetShuffleQueue':
this.setQueueShuffleMode(cmd.Arguments.ShuffleMode, player);
break;
case 'VolumeUp': case 'VolumeUp':
this.volumeUp(player); this.volumeUp(player);
break; break;

View file

@ -24,8 +24,10 @@ define([], function () {
function PlayQueueManager() { function PlayQueueManager() {
this._sortedPlaylist = [];
this._playlist = []; this._playlist = [];
this._repeatMode = 'RepeatNone'; this._repeatMode = 'RepeatNone';
this._shuffleMode = 'Sorted';
} }
PlayQueueManager.prototype.getPlaylist = function () { 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) { function arrayInsertAt(destArray, pos, arrayToInsert) {
var args = []; var args = [];
args.push(pos); // where to insert args.push(pos); // where to insert
@ -116,9 +152,7 @@ define([], function () {
PlayQueueManager.prototype.removeFromPlaylist = function (playlistItemIds) { PlayQueueManager.prototype.removeFromPlaylist = function (playlistItemIds) {
var playlist = this.getPlaylist(); if (this._playlist.length <= playlistItemIds.length) {
if (playlist.length <= playlistItemIds.length) {
return { return {
result: 'empty' result: 'empty'
}; };
@ -127,8 +161,12 @@ define([], function () {
var currentPlaylistItemId = this.getCurrentPlaylistItemId(); var currentPlaylistItemId = this.getCurrentPlaylistItemId();
var isCurrentIndex = playlistItemIds.indexOf(currentPlaylistItemId) !== -1; var isCurrentIndex = playlistItemIds.indexOf(currentPlaylistItemId) !== -1;
this._playlist = playlist.filter(function (item) { this._sortedPlaylist = this._sortedPlaylist.filter(function (item) {
return playlistItemIds.indexOf(item.PlaylistItemId) === -1; return !playlistItemIds.includes(item.PlaylistItemId);
});
this._playlist = this._playlist.filter(function (item) {
return !playlistItemIds.includes(item.PlaylistItemId);
}); });
return { return {
@ -176,21 +214,56 @@ define([], function () {
PlayQueueManager.prototype.reset = function () { PlayQueueManager.prototype.reset = function () {
this._sortedPlaylist = [];
this._playlist = []; this._playlist = [];
this._currentPlaylistItemId = null; this._currentPlaylistItemId = null;
this._repeatMode = 'RepeatNone'; this._repeatMode = 'RepeatNone';
this._shuffleMode = 'Sorted';
}; };
PlayQueueManager.prototype.setRepeatMode = function (value) { PlayQueueManager.prototype.setRepeatMode = function (value) {
const repeatModes = ['RepeatOne', 'RepeatAll', 'RepeatNone'];
this._repeatMode = value; if (repeatModes.includes(value)) {
this._repeatMode = value;
} else {
throw new TypeError('invalid value provided for setRepeatMode');
}
}; };
PlayQueueManager.prototype.getRepeatMode = function () { PlayQueueManager.prototype.getRepeatMode = function () {
return this._repeatMode; 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 () { PlayQueueManager.prototype.getNextItemInfo = function () {
var newIndex; var newIndex;

View file

@ -415,7 +415,8 @@ import 'css!./playerstats';
name: 'Original Media Info' 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({ categories.push({
stats: getSyncPlayStats(), stats: getSyncPlayStats(),
name: 'SyncPlay Info' name: 'SyncPlay Info'

View file

@ -157,43 +157,110 @@
} }
.nowPlayingSecondaryButtons { .nowPlayingSecondaryButtons {
display: -webkit-box;
display: -webkit-flex;
display: flex; display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center; align-items: center;
-webkit-flex-wrap: wrap;
flex-wrap: wrap; flex-wrap: wrap;
-webkit-box-pack: end;
-webkit-justify-content: flex-end;
justify-content: flex-end; justify-content: flex-end;
z-index: 0; 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) { @media all and (min-width: 63em) {
.nowPlayingPage { .nowPlayingPage {
padding: 8em 0 0 0 !important; 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) { @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 { .remoteControlContent {
padding-left: 7.3% !important; padding-left: 7.3% !important;
padding-right: 7.3% !important; padding-right: 7.3% !important;
@ -211,6 +278,10 @@
flex-direction: column; flex-direction: column;
} }
.layout-desktop .nowPlayingPageUserDataButtons {
display: none;
}
.nowPlayingInfoContainer { .nowPlayingInfoContainer {
-webkit-box-orient: vertical !important; -webkit-box-orient: vertical !important;
-webkit-box-direction: normal !important; -webkit-box-direction: normal !important;
@ -280,6 +351,7 @@
.nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle { .nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle {
width: 20%; width: 20%;
font-size: large; font-size: large;
display: unset;
} }
.nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle button { .nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle button {
@ -290,7 +362,7 @@
border-radius: 0; border-radius: 0;
} }
.nowPlayingInfoButtons .btnRewind { .nowPlayingInfoButtons .btnRepeat {
position: absolute; position: absolute;
left: 0; left: 0;
margin-left: 0; margin-left: 0;
@ -298,7 +370,7 @@
font-size: smaller; font-size: smaller;
} }
.nowPlayingInfoButtons .btnFastForward { .nowPlayingInfoButtons .btnShuffleQueue {
position: absolute; position: absolute;
right: 0; right: 0;
margin-right: 0; margin-right: 0;
@ -342,250 +414,6 @@
width: auto; 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 { .playlistSectionButton .volumecontrol {
width: 100%; width: 100%;
} }
@ -627,6 +455,10 @@
background-image: url(../../assets/img/equalizer.gif) !important; background-image: url(../../assets/img/equalizer.gif) !important;
} }
.playlistIndexIndicatorImage > * {
display: none;
}
.hideVideoButtons .videoButton { .hideVideoButtons .videoButton {
display: none; display: none;
} }
@ -636,7 +468,6 @@
} }
@media all and (max-width: 63em) { @media all and (max-width: 63em) {
.nowPlayingSecondaryButtons .nowPlayingPageUserDataButtons,
.nowPlayingSecondaryButtons .repeatToggleButton, .nowPlayingSecondaryButtons .repeatToggleButton,
.nowPlayingInfoButtons .playlist .listItemMediaInfo, .nowPlayingInfoButtons .playlist .listItemMediaInfo,
.nowPlayingInfoButtons .btnStop { .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'; 'use strict';
var showMuteButton = true;
var showVolumeSlider = true;
function showAudioMenu(context, player, button, item) { function showAudioMenu(context, player, button, item) {
var currentIndex = playbackManager.getAudioStreamIndex(player); 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') { if (item.Type == 'Audio' || item.MediaStreams[0].Type == 'Audio') {
var songName = item.Name; var songName = item.Name;
if (item.Album != null && item.Artists != null) { if (item.Album != null && item.Artists != null) {
var artistsSeries = '';
var albumName = item.Album; var albumName = item.Album;
var artistName;
if (item.ArtistItems != null) { if (item.ArtistItems != null) {
artistName = item.ArtistItems[0].Name; for (const artist of item.ArtistItems) {
context.querySelector('.nowPlayingAlbum').innerHTML = '<a class="button-link emby-button" is="emby-linkbutton" href="itemdetails.html?id=' + item.AlbumId + `&amp;serverId=${nowPlayingServerId}">${albumName}</a>`; let artistName = artist.Name;
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>`; let artistId = artist.Id;
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>'; artistsSeries += `<a class="button-link emby-button" is="emby-linkbutton" href="details?id=${artistId}&serverId=${nowPlayingServerId}">${artistName}</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>'; if (artist !== item.ArtistItems.slice(-1)[0]) {
} else { artistsSeries += ', ';
artistName = item.Artists; }
context.querySelector('.nowPlayingAlbum').innerHTML = albumName; }
context.querySelector('.nowPlayingArtist').innerHTML = artistName; } 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; context.querySelector('.nowPlayingSongName').innerHTML = songName;
} else if (item.Type == 'Episode') { } else if (item.Type == 'Episode') {
if (item.SeasonName != null) { if (item.SeasonName != null) {
var seasonName = item.SeasonName; 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) { if (item.SeriesName != null) {
var seriesName = item.SeriesName; var seriesName = item.SeriesName;
if (item.SeriesId != null) { 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 { } else {
context.querySelector('.nowPlayingSerie').innerHTML = seriesName; context.querySelector('.nowPlayingSerie').innerHTML = seriesName;
} }
@ -163,11 +176,38 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
maxHeight: 300 * 2 maxHeight: 300 * 2
}) : null; }) : 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); setImageUrl(context, state, url);
if (item) { if (item) {
backdrop.setBackdrops([item]); backdrop.setBackdrops([item]);
var apiClient = connectionManager.getApiClient(item.ServerId);
apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) {
var userData = fullItem.UserData || {}; var userData = fullItem.UserData || {};
var likes = null == userData.Likes ? '' : userData.Likes; var likes = null == userData.Likes ? '' : userData.Likes;
@ -219,20 +259,16 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
var currentImgUrl; var currentImgUrl;
return function () { return function () {
function toggleRepeat(player) { function toggleRepeat() {
if (player) { switch (playbackManager.getRepeatMode()) {
switch (playbackManager.getRepeatMode(player)) { case 'RepeatAll':
case 'RepeatNone': playbackManager.setRepeatMode('RepeatOne');
playbackManager.setRepeatMode('RepeatAll', player); break;
break; case 'RepeatOne':
playbackManager.setRepeatMode('RepeatNone');
case 'RepeatAll': break;
playbackManager.setRepeatMode('RepeatOne', player); case 'RepeatNone':
break; playbackManager.setRepeatMode('RepeatAll');
case 'RepeatOne':
playbackManager.setRepeatMode('RepeatNone', player);
}
} }
} }
@ -275,8 +311,13 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
buttonVisible(context.querySelector('.btnStop'), null != item); buttonVisible(context.querySelector('.btnStop'), null != item);
buttonVisible(context.querySelector('.btnNextTrack'), null != item); buttonVisible(context.querySelector('.btnNextTrack'), null != item);
buttonVisible(context.querySelector('.btnPreviousTrack'), null != item); buttonVisible(context.querySelector('.btnPreviousTrack'), null != item);
buttonVisible(context.querySelector('.btnRewind'), null != item); if (layoutManager.mobile) {
buttonVisible(context.querySelector('.btnFastForward'), null != item); 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'); var positionSlider = context.querySelector('.nowPlayingPositionSlider');
if (positionSlider && item && item.RunTimeTicks) { if (positionSlider && item && item.RunTimeTicks) {
@ -300,7 +341,8 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
context.classList.add('hideVideoButtons'); context.classList.add('hideVideoButtons');
} }
updateRepeatModeDisplay(playState.RepeatMode); updateRepeatModeDisplay(playbackManager.getRepeatMode());
onShuffleQueueModeChange(false);
updateNowPlayingInfo(context, state); updateNowPlayingInfo(context, state);
} }
@ -316,25 +358,32 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
function updateRepeatModeDisplay(repeatMode) { function updateRepeatModeDisplay(repeatMode) {
var context = dlg; 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) { switch (repeatMode) {
toggleRepeatButton.innerHTML = "<span class='material-icons repeat'></span>"; case 'RepeatAll':
toggleRepeatButton.classList.add('repeatButton-active'); break;
} else if ('RepeatOne' == repeatMode) { case 'RepeatOne':
toggleRepeatButton.innerHTML = "<span class='material-icons repeat_one'></span>"; innHtml = '<span class="material-icons repeat_one"></span>';
toggleRepeatButton.classList.add('repeatButton-active'); break;
} else { case 'RepeatNone':
toggleRepeatButton.innerHTML = "<span class='material-icons repeat'></span>"; default:
toggleRepeatButton.classList.remove('repeatButton-active'); repeatOn = false;
break;
}
for (const toggleRepeatButton of toggleRepeatButtons) {
toggleRepeatButton.classList.toggle(cssClass, repeatOn);
toggleRepeatButton.innerHTML = innHtml;
} }
} }
function updatePlayerVolumeState(context, isMuted, volumeLevel) { function updatePlayerVolumeState(context, isMuted, volumeLevel) {
var view = context; var view = context;
var supportedCommands = currentPlayerSupportedCommands; var supportedCommands = currentPlayerSupportedCommands;
var showMuteButton = true;
var showVolumeSlider = true;
if (-1 === supportedCommands.indexOf('Mute')) { if (-1 === supportedCommands.indexOf('Mute')) {
showMuteButton = false; showMuteButton = false;
@ -362,24 +411,21 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
buttonMuteIcon.classList.add('volume_up'); buttonMuteIcon.classList.add('volume_up');
} }
if (showMuteButton) { if (!showMuteButton && !showVolumeSlider) {
buttonMute.classList.remove('hide'); context.querySelector('.volumecontrol').classList.add('hide');
} else { } else {
buttonMute.classList.add('hide'); buttonMute.classList.toggle('hide', !showMuteButton);
}
var nowPlayingVolumeSlider = context.querySelector('.nowPlayingVolumeSlider'); var nowPlayingVolumeSlider = context.querySelector('.nowPlayingVolumeSlider');
var nowPlayingVolumeSliderContainer = context.querySelector('.nowPlayingVolumeSliderContainer'); var nowPlayingVolumeSliderContainer = context.querySelector('.nowPlayingVolumeSliderContainer');
if (nowPlayingVolumeSlider) { if (nowPlayingVolumeSlider) {
if (showVolumeSlider) {
nowPlayingVolumeSliderContainer.classList.remove('hide');
} else {
nowPlayingVolumeSliderContainer.classList.add('hide');
}
if (!nowPlayingVolumeSlider.dragging) { nowPlayingVolumeSliderContainer.classList.toggle('hide', !showVolumeSlider);
nowPlayingVolumeSlider.value = volumeLevel || 0;
if (!nowPlayingVolumeSlider.dragging) {
nowPlayingVolumeSlider.value = volumeLevel || 0;
}
} }
} }
} }
@ -420,11 +466,21 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
function loadPlaylist(context, player) { function loadPlaylist(context, player) {
getPlaylistItems(player).then(function (items) { getPlaylistItems(player).then(function (items) {
var html = ''; 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({ html += listView.getListViewHtml({
items: items, items: items,
smallIcon: true, smallIcon: true,
action: 'setplaylistindex', action: 'setplaylistindex',
enableUserDataButtons: false, enableUserDataButtons: favoritesEnabled,
rightButtons: [{ rightButtons: [{
icon: 'remove_circle_outline', icon: 'remove_circle_outline',
title: globalize.translate('ButtonRemove'), title: globalize.translate('ButtonRemove'),
@ -433,18 +489,21 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
dragHandle: true dragHandle: true
}); });
if (items.length) { var itemsContainer = context.querySelector('.playlist');
context.querySelector('.btnTogglePlaylist').classList.remove('hide'); let focusedItemPlaylistId = itemsContainer.querySelector('button:focus');
} else { itemsContainer.innerHTML = html;
context.querySelector('.btnTogglePlaylist').classList.add('hide'); 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); var playlistItemId = playbackManager.getCurrentPlaylistItemId(player);
if (playlistItemId) { if (playlistItemId) {
var img = itemsContainer.querySelector('.listItem[data-playlistItemId="' + playlistItemId + '"] .listItemImage'); var img = itemsContainer.querySelector(`.listItem[data-playlistItemId="${playlistItemId}"] .listItemImage`);
if (img) { if (img) {
img.classList.remove('lazy'); img.classList.remove('lazy');
@ -453,9 +512,6 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
} }
imageLoader.lazyChildren(itemsContainer); 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); onStateChanged.call(player, e, state);
} }
function onRepeatModeChange(e) { function onRepeatModeChange() {
var player = this; updateRepeatModeDisplay(playbackManager.getRepeatMode());
updateRepeatModeDisplay(playbackManager.getRepeatMode(player)); }
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) { function onPlaylistUpdate(e) {
@ -476,14 +554,18 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
function onPlaylistItemRemoved(e, info) { function onPlaylistItemRemoved(e, info) {
var context = dlg; var context = dlg;
var playlistItemIds = info.playlistItemIds; if (info !== undefined) {
var playlistItemIds = info.playlistItemIds;
for (var i = 0, length = playlistItemIds.length; i < length; i++) { for (var i = 0, length = playlistItemIds.length; i < length; i++) {
var listItem = context.querySelector('.listItem[data-playlistItemId="' + playlistItemIds[i] + '"]'); var listItem = context.querySelector('.listItem[data-playlistItemId="' + playlistItemIds[i] + '"]');
if (listItem) { if (listItem) {
listItem.parentNode.removeChild(listItem); listItem.parentNode.removeChild(listItem);
}
} }
} else {
onPlaylistUpdate();
} }
} }
@ -493,7 +575,6 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
if (!state.NextMediaType) { if (!state.NextMediaType) {
updatePlayerState(player, dlg, {}); updatePlayerState(player, dlg, {});
loadPlaylist(dlg);
Emby.Page.back(); Emby.Page.back();
} }
} }
@ -505,7 +586,7 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
function onStateChanged(event, state) { function onStateChanged(event, state) {
var player = this; var player = this;
updatePlayerState(player, dlg, state); updatePlayerState(player, dlg, state);
loadPlaylist(dlg, player); onPlaylistUpdate();
} }
function onTimeUpdate(e) { function onTimeUpdate(e) {
@ -531,8 +612,10 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
events.off(player, 'playbackstart', onPlaybackStart); events.off(player, 'playbackstart', onPlaybackStart);
events.off(player, 'statechange', onStateChanged); events.off(player, 'statechange', onStateChanged);
events.off(player, 'repeatmodechange', onRepeatModeChange); 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, 'playlistitemmove', onPlaylistUpdate);
events.off(player, 'playlistitemadd', onPlaylistUpdate);
events.off(player, 'playbackstop', onPlaybackStopped); events.off(player, 'playbackstop', onPlaybackStopped);
events.off(player, 'volumechange', onVolumeChanged); events.off(player, 'volumechange', onVolumeChanged);
events.off(player, 'pause', onPlayPauseStateChanged); events.off(player, 'pause', onPlayPauseStateChanged);
@ -551,8 +634,10 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
events.on(player, 'playbackstart', onPlaybackStart); events.on(player, 'playbackstart', onPlaybackStart);
events.on(player, 'statechange', onStateChanged); events.on(player, 'statechange', onStateChanged);
events.on(player, 'repeatmodechange', onRepeatModeChange); events.on(player, 'repeatmodechange', onRepeatModeChange);
events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange);
events.on(player, 'playlistitemremove', onPlaylistItemRemoved); events.on(player, 'playlistitemremove', onPlaylistItemRemoved);
events.on(player, 'playlistitemmove', onPlaylistUpdate); events.on(player, 'playlistitemmove', onPlaylistUpdate);
events.on(player, 'playlistitemadd', onPlaylistUpdate);
events.on(player, 'playbackstop', onPlaybackStopped); events.on(player, 'playbackstop', onPlaybackStopped);
events.on(player, 'volumechange', onVolumeChanged); events.on(player, 'volumechange', onVolumeChanged);
events.on(player, 'pause', onPlayPauseStateChanged); events.on(player, 'pause', onPlayPauseStateChanged);
@ -568,7 +653,7 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
function onBtnCommandClick() { function onBtnCommandClick() {
if (currentPlayer) { if (currentPlayer) {
if (this.classList.contains('repeatToggleButton')) { if (this.classList.contains('repeatToggleButton')) {
toggleRepeat(currentPlayer); toggleRepeat();
} else { } else {
playbackManager.sendCommand({ playbackManager.sendCommand({
Name: this.getAttribute('data-command') Name: this.getAttribute('data-command')
@ -603,6 +688,7 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
function bindEvents(context) { function bindEvents(context) {
var btnCommand = context.querySelectorAll('.btnCommand'); var btnCommand = context.querySelectorAll('.btnCommand');
var positionSlider = context.querySelector('.nowPlayingPositionSlider');
for (var i = 0, length = btnCommand.length; i < length; i++) { for (var i = 0, length = btnCommand.length; i < length; i++) {
btnCommand[i].addEventListener('click', onBtnCommandClick); btnCommand[i].addEventListener('click', onBtnCommandClick);
@ -650,12 +736,37 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
playbackManager.fastForward(currentPlayer); 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) { if (currentPlayer) {
playbackManager.previousTrack(currentPlayer); playbackManager.previousTrack(currentPlayer);
} }
}); });
context.querySelector('.nowPlayingPositionSlider').addEventListener('change', function () { positionSlider.addEventListener('change', function () {
var value = this.value; var value = this.value;
if (currentPlayer) { 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; var state = lastPlayerState;
if (!state || !state.NowPlayingItem || !currentRuntimeTicks) { if (!state || !state.NowPlayingItem || !currentRuntimeTicks) {
@ -677,13 +788,10 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
return datetime.getDisplayRunningTime(ticks); return datetime.getDisplayRunningTime(ticks);
}; };
function setVolume() { context.querySelector('.nowPlayingVolumeSlider').addEventListener('input', (e) => {
playbackManager.setVolume(this.value, currentPlayer); 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 () { context.querySelector('.buttonMute').addEventListener('click', function () {
playbackManager.toggleMute(currentPlayer); playbackManager.toggleMute(currentPlayer);
}); });
@ -701,21 +809,19 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
if (context.querySelector('.playlist').classList.contains('hide')) { if (context.querySelector('.playlist').classList.contains('hide')) {
context.querySelector('.playlist').classList.remove('hide'); context.querySelector('.playlist').classList.remove('hide');
context.querySelector('.btnSavePlaylist').classList.remove('hide'); context.querySelector('.btnSavePlaylist').classList.remove('hide');
context.querySelector('.contextMenu').classList.add('hide');
context.querySelector('.volumecontrol').classList.add('hide'); context.querySelector('.volumecontrol').classList.add('hide');
if (layoutManager.mobile) {
context.querySelector('.playlistSectionButton').classList.remove('playlistSectionButtonTransparent');
}
} else { } else {
context.querySelector('.playlist').classList.add('hide'); context.querySelector('.playlist').classList.add('hide');
context.querySelector('.btnSavePlaylist').classList.add('hide'); context.querySelector('.btnSavePlaylist').classList.add('hide');
context.querySelector('.volumecontrol').classList.remove('hide'); if (showMuteButton || showVolumeSlider) {
} context.querySelector('.volumecontrol').classList.remove('hide');
}); }
context.querySelector('.btnToggleContextMenu').addEventListener('click', function () { if (layoutManager.mobile) {
if (context.querySelector('.contextMenu').classList.contains('hide')) { context.querySelector('.playlistSectionButton').classList.add('playlistSectionButtonTransparent');
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');
} }
}); });
} }
@ -764,16 +870,24 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL
} }
function init(ownerView, context) { 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">'; 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 += `<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 class="sliderContainer nowPlayingVolumeSliderContainer"><input is="emby-slider" type="range" step="1" min="0" max="100" value="0" class="nowPlayingVolumeSlider"/></div>';
volumecontrolHtml += '</div>'; volumecontrolHtml += '</div>';
let optionsSection = context.querySelector('.playlistSectionButton');
if (!layoutManager.mobile) { if (!layoutManager.mobile) {
context.querySelector('.nowPlayingSecondaryButtons').innerHTML += volumecontrolHtml; context.querySelector('.nowPlayingSecondaryButtons').insertAdjacentHTML('beforeend', volumecontrolHtml);
context.querySelector('.playlistSectionButton').innerHTML += contextmenuHtml; 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 { } 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); bindEvents(context);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
import appHost from 'apphost'; import appHost from 'apphost';
import appSettings from 'appSettings'; import appSettings from 'appSettings';
import dom from 'dom'; import dom from 'dom';
@ -8,6 +9,10 @@ import browser from 'browser';
import globalize from 'globalize'; import globalize from 'globalize';
import 'cardStyle'; import 'cardStyle';
import 'emby-checkbox'; 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 */ /* eslint-disable indent */
@ -16,6 +21,7 @@ import 'emby-checkbox';
function authenticateUserByName(page, apiClient, username, password) { function authenticateUserByName(page, apiClient, username, password) {
loading.show(); loading.show();
apiClient.authenticateUserByName(username, password).then(function (result) { apiClient.authenticateUserByName(username, password).then(function (result) {
<<<<<<< HEAD
const user = result.User; const user = result.User;
const serverId = getParameterByName('serverid'); const serverId = getParameterByName('serverid');
let newUrl; let newUrl;
@ -26,9 +32,13 @@ import 'emby-checkbox';
newUrl = 'home.html'; newUrl = 'home.html';
} }
=======
var user = result.User;
>>>>>>> upstream/master
loading.hide(); loading.hide();
Dashboard.onServerChanged(user.Id, result.AccessToken, apiClient); Dashboard.onServerChanged(user.Id, result.AccessToken, apiClient);
Dashboard.navigate(newUrl); Dashboard.navigate('home.html');
}, function (response) { }, function (response) {
page.querySelector('#txtManualName').value = ''; page.querySelector('#txtManualName').value = '';
page.querySelector('#txtManualPassword').value = ''; page.querySelector('#txtManualPassword').value = '';
@ -204,6 +214,7 @@ import 'emby-checkbox';
}); });
view.addEventListener('viewshow', function (e) { view.addEventListener('viewshow', function (e) {
loading.show(); loading.show();
libraryMenu.setTransparentMenu(true);
if (!appHost.supports('multiserver')) { if (!appHost.supports('multiserver')) {
view.querySelector('.btnSelectServer').classList.add('hide'); view.querySelector('.btnSelectServer').classList.add('hide');
@ -225,6 +236,14 @@ import 'emby-checkbox';
view.querySelector('.disclaimer').textContent = options.LoginDisclaimer || ''; view.querySelector('.disclaimer').textContent = options.LoginDisclaimer || '';
}); });
}); });
<<<<<<< HEAD
} }
/* eslint-enable indent */ /* 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 loading from 'loading';
import appRouter from 'appRouter'; import appRouter from 'appRouter';
import layoutManager from 'layoutManager'; import layoutManager from 'layoutManager';
@ -19,6 +20,12 @@ import 'emby-button';
/* eslint-disable indent */ /* eslint-disable indent */
const enableFocusTransform = !browser.slow && !browser.edge; 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) { function renderSelectServerItems(view, servers) {
const items = servers.map(function (server) { const items = servers.map(function (server) {
@ -200,6 +207,7 @@ import 'emby-button';
view.addEventListener('viewshow', function (e) { view.addEventListener('viewshow', function (e) {
const isRestored = e.detail.isRestored; const isRestored = e.detail.isRestored;
appRouter.setTitle(null); appRouter.setTitle(null);
libraryMenu.setTransparentMenu(true);
if (!isRestored) { if (!isRestored) {
loadServers(); loadServers();

View file

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

View file

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

View file

@ -105,7 +105,9 @@ import globalize from 'globalize';
$('#chkEnableSharing', page).prop('checked', user.Policy.EnablePublicSharing); $('#chkEnableSharing', page).prop('checked', user.Policy.EnablePublicSharing);
$('#txtRemoteClientBitrateLimit', page).val(user.Policy.RemoteClientBitrateLimit / 1e6 || ''); $('#txtRemoteClientBitrateLimit', page).val(user.Policy.RemoteClientBitrateLimit / 1e6 || '');
$('#txtLoginAttemptsBeforeLockout', page).val(user.Policy.LoginAttemptsBeforeLockout || '0'); $('#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(); loading.hide();
} }
@ -147,7 +149,9 @@ import globalize from 'globalize';
}).map(function (c) { }).map(function (c) {
return c.getAttribute('data-id'); 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.updateUser(user).then(function () {
ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () { ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
onSaveComplete(page, user); 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"> <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>
<div class="detailLogo"></div> <div class="detailLogo"></div>
@ -15,94 +12,75 @@
</div> </div>
<div class="mainDetailButtons"> <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"> <div class="detailButton-content">
<span class="material-icons detailButton-icon play_arrow"></span> <span class="material-icons detailButton-icon play_arrow"></span>
<div class="detailButton-text">${ButtonResume}</div>
</div> </div>
</button> </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"> <div class="detailButton-content">
<span class="material-icons detailButton-icon play_arrow"></span> <span class="material-icons detailButton-icon play_arrow"></span>
<div class="detailButton-text">${ButtonPlay}</div>
</div> </div>
</button> </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"> <div class="detailButton-content">
<span class="material-icons detailButton-icon get_app"></span> <span class="material-icons detailButton-icon get_app"></span>
<div class="detailButton-text">${ButtonDownload}</div>
</div> </div>
</button> </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"> <div class="detailButton-content">
<span class="material-icons detailButton-icon theaters"></span> <span class="material-icons detailButton-icon theaters"></span>
<div class="detailButton-text">${ButtonTrailer}</div>
</div> </div>
</button> </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"> <div class="detailButton-content">
<span class="material-icons detailButton-icon explore"></span> <span class="material-icons detailButton-icon explore"></span>
<div class="detailButton-text">${HeaderInstantMix}</div>
</div> </div>
</button> </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"> <div class="detailButton-content">
<span class="material-icons detailButton-icon shuffle"></span> <span class="material-icons detailButton-icon shuffle"></span>
<div class="detailButton-text">${ButtonShuffle}</div>
</div> </div>
</button> </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"> <div class="detailButton-content">
<span class="material-icons detailButton-icon delete"></span> <span class="material-icons detailButton-icon delete"></span>
<div class="detailButton-text">${CancelSeries}</div>
</div> </div>
</button> </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"> <div class="detailButton-content">
<span class="material-icons detailButton-icon stop"></span> <span class="material-icons detailButton-icon stop"></span>
<div class="detailButton-text">${StopRecording}</div>
</div> </div>
</button> </button>
<button is="emby-button" type="button" class="button-flat btnDeleteItem 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 delete"></span>
<div class="detailButton-text">${Delete}</div>
</div>
</button>
<button is="emby-playstatebutton" type="button" class="button-flat btnPlaystate hide detailButton">
<div class="detailButton-content"> <div class="detailButton-content">
<span class="material-icons detailButton-icon check"></span> <span class="material-icons detailButton-icon check"></span>
<div class="detailButton-text button-text"></div>
</div> </div>
</button> </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"> <div class="detailButton-content">
<span class="material-icons detailButton-icon favorite"></span> <span class="material-icons detailButton-icon favorite"></span>
<div class="detailButton-text button-text">${Rate}</div>
</div> </div>
</button> </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"> <div class="detailButton-content">
<span class="material-icons detailButton-icon call_split"></span> <span class="material-icons detailButton-icon call_split"></span>
<div class="detailButton-text">${ButtonSplit}</div>
</div> </div>
</button> </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"> <div class="detailButton-content">
<span class="material-icons detailButton-icon more_vert"></span> <span class="material-icons detailButton-icon more_vert"></span>
<div class="detailButton-text">${ButtonMore}</div>
</div> </div>
</button> </button>
</div> </div>
@ -110,7 +88,7 @@
<div class="detailPageSecondaryContainer"> <div class="detailPageSecondaryContainer">
<div class="detailImageContainer padded-left"></div> <div class="detailImageContainer padded-left"></div>
<div class="detailPageContent"> <div class="detailPageContent">
<div class="detailPagePrimaryContent padded-left padded-right"> <div class="detailPagePrimaryContent padded-right">
<div class="detailSection"> <div class="detailSection">
<div class="itemMiscInfo nativeName hide"></div> <div class="itemMiscInfo nativeName hide"></div>
@ -124,6 +102,11 @@
<div class="directorsLabel label"></div> <div class="directorsLabel label"></div>
<div class="directors content"></div> <div class="directors content"></div>
</div> </div>
<div class="detailsGroupItem writersGroup hide">
<div class="writersLabel label"></div>
<div class="writers content"></div>
</div>
</div> </div>
<form class="trackSelections hide focuscontainer-x"> <form class="trackSelections hide focuscontainer-x">
@ -164,15 +147,15 @@
</div> </div>
<div class="seriesTimerScheduleSection verticalSection detailVerticalSection hide" style="margin-top:-3em;"> <div class="seriesTimerScheduleSection verticalSection detailVerticalSection hide" style="margin-top:-3em;">
<h2 class="sectionTitle padded-left">${HeaderSchedule}</h2> <h2 class="sectionTitle">${HeaderSchedule}</h2>
<div class="seriesTimerSchedule padded-left padded-right"></div> <div class="seriesTimerSchedule padded-right"></div>
</div> </div>
<div class="collectionItems"></div> <div class="collectionItems hide"></div>
<div class="nextUpSection verticalSection detailVerticalSection hide"> <div class="nextUpSection verticalSection detailVerticalSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderNextUp}</h2> <h2 class="sectionTitle sectionTitle-cards">${HeaderNextUp}</h2>
<div is="emby-itemscontainer" class="nextUpItems vertical-wrap padded-left padded-right"></div> <div is="emby-itemscontainer" class="nextUpItems vertical-wrap padded-right"></div>
</div> </div>
<div class="programGuideSection hide verticalSection detailVerticalSection"> <div class="programGuideSection hide verticalSection detailVerticalSection">
@ -180,64 +163,64 @@
</div> </div>
<div id="childrenCollapsible" class="hide verticalSection detailVerticalSection"> <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> <span id="childrenTitle"></span>
</h2> </h2>
<div id="childrenContent"> <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> </div>
<div id="additionalPartsCollapsible" class="verticalSection detailVerticalSection hide"> <div id="additionalPartsCollapsible" class="verticalSection detailVerticalSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderAdditionalParts}</h2> <h2 class="sectionTitle sectionTitle-cards padded-right">${HeaderAdditionalParts}</h2>
<div id="additionalPartsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right"></div> <div id="additionalPartsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-right"></div>
</div> </div>
<div class="verticalSection itemVerticalSection moreFromSeasonSection hide"> <div class="verticalSection detailVerticalSection moreFromSeasonSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right"></h2> <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-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">
<div is="emby-itemscontainer" class="scrollSlider focuscontainer-x itemsContainer"></div> <div is="emby-itemscontainer" class="scrollSlider focuscontainer-x itemsContainer"></div>
</div> </div>
</div> </div>
<div class="verticalSection itemVerticalSection moreFromArtistSection hide"> <div class="verticalSection detailVerticalSection moreFromArtistSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right"></h2> <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-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">
<div is="emby-itemscontainer" class="scrollSlider focuscontainer-x itemsContainer"></div> <div is="emby-itemscontainer" class="scrollSlider focuscontainer-x itemsContainer"></div>
</div> </div>
</div> </div>
<div id="castCollapsible" class="verticalSection detailVerticalSection hide"> <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 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 id="castContent" is="emby-itemscontainer" class="scrollSlider focuscontainer-x itemsContainer"></div>
</div> </div>
</div> </div>
<div id="seriesScheduleSection" class="verticalSection detailVerticalSection hide"> <div id="seriesScheduleSection" class="verticalSection detailVerticalSection hide">
<h2 class="sectionTitle padded-left padded-right">${HeaderUpcomingOnTV}</h2> <h2 class="sectionTitle padded-right">${HeaderUpcomingOnTV}</h2>
<div id="seriesScheduleList" is="emby-itemscontainer" class="itemsContainer vertical-list padded-left padded-right"></div> <div id="seriesScheduleList" is="emby-itemscontainer" class="itemsContainer vertical-list padded-right"></div>
</div> </div>
<div id="specialsCollapsible" class="verticalSection detailVerticalSection hide"> <div id="specialsCollapsible" class="verticalSection detailVerticalSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderSpecialFeatures}</h2> <h2 class="sectionTitle sectionTitle-cards padded-right">${HeaderSpecialFeatures}</h2>
<div id="specialsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right"></div> <div id="specialsContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-right"></div>
</div> </div>
<div id="musicVideosCollapsible" class="verticalSection detailVerticalSection hide"> <div id="musicVideosCollapsible" class="verticalSection detailVerticalSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderMusicVideos}</h2> <h2 class="sectionTitle sectionTitle-cards padded-right">${HeaderMusicVideos}</h2>
<div id="musicVideosContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right"></div> <div id="musicVideosContent" is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-right"></div>
</div> </div>
<div id="scenesCollapsible" class="verticalSection itemVerticalSection verticalSection-extrabottompadding hide"> <div id="scenesCollapsible" class="verticalSection detailVerticalSection verticalSection-extrabottompadding hide">
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderScenes}</h2> <h2 class="sectionTitle sectionTitle-cards padded-right">${HeaderScenes}</h2>
<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true"> <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 id="scenesContent" is="emby-itemscontainer" class="scrollSlider focuscontainer-x itemsContainer"></div>
</div> </div>
</div> </div>
<div id="similarCollapsible" class="verticalSection itemVerticalSection verticalSection-extrabottompadding hide"> <div id="similarCollapsible" class="verticalSection detailVerticalSection verticalSection-extrabottompadding hide">
<h2 class="sectionTitle sectionTitle-cards padded-left padded-right">${HeaderMoreLikeThis}</h2> <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-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">
<div is="emby-itemscontainer" class="scrollSlider focuscontainer-x itemsContainer similarContent"></div> <div is="emby-itemscontainer" class="scrollSlider focuscontainer-x itemsContainer similarContent"></div>
</div> </div>

View file

@ -28,15 +28,11 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
} }
function hideAll(page, className, show) { function hideAll(page, className, show) {
var i; for (const elem of page.querySelectorAll('.' + className)) {
var length;
var elems = page.querySelectorAll('.' + className);
for (i = 0, length = elems.length; i < length; i++) {
if (show) { if (show) {
elems[i].classList.remove('hide'); elem.classList.remove('hide');
} else { } else {
elems[i].classList.add('hide'); elem.classList.add('hide');
} }
} }
} }
@ -51,17 +47,19 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
positionTo: button, positionTo: button,
cancelTimer: false, cancelTimer: false,
record: false, record: false,
deleteItem: true === item.IsFolder, deleteItem: item.CanDelete === true,
shuffle: false, shuffle: false,
instantMix: false, instantMix: false,
user: user, user: user,
share: true share: true
}; };
return options; return options;
} }
function getProgramScheduleHtml(items) { function getProgramScheduleHtml(items) {
var html = ''; var html = '';
html += '<div is="emby-itemscontainer" class="itemsContainer vertical-list" data-contextmenu="false">'; html += '<div is="emby-itemscontainer" class="itemsContainer vertical-list" data-contextmenu="false">';
html += listView.getListViewHtml({ html += listView.getListViewHtml({
items: items, items: items,
@ -75,7 +73,10 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
moreButton: false, moreButton: false,
recordButton: false recordButton: false
}); });
return html += '</div>';
html += '</div>';
return html;
} }
function renderSeriesTimerSchedule(page, apiClient, seriesTimerId) { function renderSeriesTimerSchedule(page, apiClient, seriesTimerId) {
@ -143,9 +144,12 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
var mediaSources = item.MediaSources; var mediaSources = item.MediaSources;
instance._currentPlaybackMediaSources = mediaSources; instance._currentPlaybackMediaSources = mediaSources;
page.querySelector('.trackSelections').classList.remove('hide'); page.querySelector('.trackSelections').classList.remove('hide');
select.setLabel(globalize.translate('LabelVersion')); select.setLabel(globalize.translate('LabelVersion'));
var currentValue = select.value; var currentValue = select.value;
var selectedId = mediaSources[0].Id; var selectedId = mediaSources[0].Id;
select.innerHTML = mediaSources.map(function (v) { select.innerHTML = mediaSources.map(function (v) {
var selected = v.Id === selectedId ? ' selected' : ''; var selected = v.Id === selectedId ? ' selected' : '';
@ -163,7 +167,6 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
renderAudioSelections(page, mediaSources); renderAudioSelections(page, mediaSources);
renderSubtitleSelections(page, mediaSources); renderSubtitleSelections(page, mediaSources);
} }
} }
function renderVideoSelections(page, mediaSources) { function renderVideoSelections(page, mediaSources) {
@ -171,9 +174,11 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
var mediaSource = mediaSources.filter(function (m) { var mediaSource = mediaSources.filter(function (m) {
return m.Id === mediaSourceId; return m.Id === mediaSourceId;
})[0]; })[0];
var tracks = mediaSource.MediaStreams.filter(function (m) { var tracks = mediaSource.MediaStreams.filter(function (m) {
return 'Video' === m.Type; return m.Type === 'Video';
}); });
var select = page.querySelector('.selectVideo'); var select = page.querySelector('.selectVideo');
select.setLabel(globalize.translate('LabelVideo')); select.setLabel(globalize.translate('LabelVideo'));
var selectedId = tracks.length ? tracks[0].Index : -1; var selectedId = tracks.length ? tracks[0].Index : -1;
@ -242,12 +247,24 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
select.setLabel(globalize.translate('LabelSubtitles')); select.setLabel(globalize.translate('LabelSubtitles'));
var selectedId = null == mediaSource.DefaultSubtitleStreamIndex ? -1 : mediaSource.DefaultSubtitleStreamIndex; 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' : ''; var selected = -1 === selectedId ? ' selected' : '';
select.innerHTML = '<option value="-1">' + globalize.translate('Off') + '</option>' + tracks.map(function (v) { select.innerHTML = '<option value="-1">' + globalize.translate('Off') + '</option>' + tracks.map(function (v) {
selected = v.Index === selectedId ? ' selected' : ''; selected = v.Index === selectedId ? ' selected' : '';
return '<option value="' + v.Index + '" ' + selected + '>' + v.DisplayTitle + '</option>'; return '<option value="' + v.Index + '" ' + selected + '>' + v.DisplayTitle + '</option>';
}).join(''); }).join('');
if (tracks.length > 0) {
select.removeAttribute('disabled');
} else {
select.setAttribute('disabled', 'disabled');
}
page.querySelector('.selectSubtitlesContainer').classList.remove('hide'); page.querySelector('.selectSubtitlesContainer').classList.remove('hide');
} else { } else {
select.innerHTML = ''; select.innerHTML = '';
@ -278,7 +295,15 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
var enableShuffle = item.IsFolder || -1 !== ['MusicAlbum', 'MusicGenre', 'MusicArtist'].indexOf(item.Type); var enableShuffle = item.IsFolder || -1 !== ['MusicAlbum', 'MusicGenre', 'MusicArtist'].indexOf(item.Type);
hideAll(page, 'btnShuffle', enableShuffle); hideAll(page, 'btnShuffle', enableShuffle);
canPlay = true; 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 { } else {
hideAll(page, 'btnPlay'); hideAll(page, 'btnPlay');
hideAll(page, 'btnResume'); hideAll(page, 'btnResume');
@ -324,8 +349,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
function getArtistLinksHtml(artists, serverId, context) { function getArtistLinksHtml(artists, serverId, context) {
var html = []; var html = [];
for (var i = 0, length = artists.length; i < length; i++) { for (const artist of artists) {
var artist = artists[i];
var href = appRouter.getRouteUrl(artist, { var href = appRouter.getRouteUrl(artist, {
context: context, context: context,
itemType: 'MusicArtist', 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.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 parentRoute;
var parentNameHtml = []; var parentNameHtml = [];
var parentNameLast = false; var parentNameLast = false;
@ -410,16 +442,12 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
if (parentNameLast) { if (parentNameLast) {
// Music // Music
if (layoutManager.mobile) { 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 { } else {
html = '<h3 class="parentName" style="margin: .25em 0;">' + parentNameHtml.join(' - ') + '</h3>'; html = '<h3 class="parentName musicParentName">' + parentNameHtml.join(' - ') + '</h3>';
} }
} else { } else {
if (layoutManager.mobile) { html = '<h1 class="parentName">' + tvShowHtml + '</h1>';
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>';
}
} }
} }
@ -428,17 +456,19 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
}); });
if (html && !parentNameLast) { if (html && !parentNameLast) {
if (!layoutManager.mobile && tvSeasonHtml) { if (tvSeasonHtml) {
html += '<h3 class="itemName infoText" style="margin: 0.2em 0 0">' + tvSeasonHtml + ' - ' + name + '</h3>'; html += '<h3 class="itemName infoText subtitle">' + tvSeasonHtml + ' - ' + name + '</h3>';
} else { } 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 { } 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) { 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; container.innerHTML = html;
@ -470,43 +500,18 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
var imgUrl; var imgUrl;
var hasbackdrop = false; var hasbackdrop = false;
var itemBackdropElement = page.querySelector('#itemBackdrop'); 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()) { if (!layoutManager.mobile && !userSettings.detailsBanner()) {
return false; return false;
} }
if ('Program' === item.Type && item.ImageTags && item.ImageTags.Thumb) { if (item.BackdropImageTags && item.BackdropImageTags.length) {
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) {
imgUrl = apiClient.getScaledImageUrl(item.Id, { imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Backdrop', type: 'Backdrop',
maxWidth: dom.getScreenWidth(), maxWidth: dom.getScreenWidth(),
index: 0, index: 0,
tag: item.BackdropImageTags[0] tag: item.BackdropImageTags[0]
}); });
page.classList.remove('noBackdrop');
imageLoader.lazyImage(itemBackdropElement, imgUrl); imageLoader.lazyImage(itemBackdropElement, imgUrl);
hasbackdrop = true; hasbackdrop = true;
} else if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { } else if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) {
@ -516,50 +521,35 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
index: 0, index: 0,
tag: item.ParentBackdropImageTags[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); imageLoader.lazyImage(itemBackdropElement, imgUrl);
hasbackdrop = true; hasbackdrop = true;
} else { } else {
itemBackdropElement.style.backgroundImage = ''; 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; return hasbackdrop;
} }
function reloadFromItem(instance, page, params, item, user) { function reloadFromItem(instance, page, params, item, user) {
var context = params.context; const apiClient = connectionManager.getApiClient(item.ServerId);
page.querySelector('.detailPagePrimaryContainer').classList.add('detailSticky');
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(''); Emby.Page.setTitle('');
setInitialCollapsibleState(page, item, apiClient, context, user);
renderDetails(page, item, apiClient, context); // Start rendering the artwork first
renderTrackSelections(page, instance, item); renderImage(page, item);
renderLogo(page, item, apiClient);
renderBackdrop(item); renderBackdrop(item);
renderDetailPageBackdrop(page, item, apiClient); 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); var canPlay = reloadPlayButtons(page, item);
if ((item.LocalTrailerCount || item.RemoteTrailers && item.RemoteTrailers.length) && -1 !== playbackManager.getSupportedCommands().indexOf('PlayTrailers')) { 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); setTrailerButtonVisibility(page, item);
if (item.CanDelete && !item.IsFolder) {
hideAll(page, 'btnDeleteItem', true);
} else {
hideAll(page, 'btnDeleteItem');
}
if ('Program' !== item.Type || canPlay) { if ('Program' !== item.Type || canPlay) {
hideAll(page, 'mainDetailButtons', true); hideAll(page, 'mainDetailButtons', true);
} else { } else {
@ -667,18 +651,15 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
} }
function renderLogo(page, item, apiClient) { function renderLogo(page, item, apiClient) {
var url = logoImageUrl(item, apiClient, {
maxWidth: 400
});
var detailLogo = page.querySelector('.detailLogo'); var detailLogo = page.querySelector('.detailLogo');
var url = logoImageUrl(item, apiClient, {});
if (!layoutManager.mobile && !userSettings.enableBackdrops()) { if (!layoutManager.mobile && !userSettings.enableBackdrops()) {
detailLogo.classList.add('hide'); detailLogo.classList.add('hide');
} else if (url) { } else if (url) {
detailLogo.classList.remove('hide'); detailLogo.classList.remove('hide');
detailLogo.classList.add('lazy'); imageLoader.setLazyImage(detailLogo, url);
detailLogo.setAttribute('data-src', url);
imageLoader.lazyImage(detailLogo);
} else { } else {
detailLogo.classList.add('hide'); detailLogo.classList.add('hide');
} }
@ -704,172 +685,59 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
} }
} }
function renderLinks(linksElem, item) { function renderLinks(page, item) {
var html = []; var externalLinksElem = page.querySelector('.itemExternalLinks');
var links = []; var links = [];
if (!layoutManager.tv && item.HomePageUrl) { 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) { if (item.ExternalUrls) {
for (var i = 0, length = item.ExternalUrls.length; i < length; i++) { for (const url of item.ExternalUrls) {
var url = item.ExternalUrls[i]; links.push(`<a is="emby-linkbutton" class="button-link" href="${url.Url}" target="_blank">${url.Name}</a>`);
links.push('<a style="color:inherit;" is="emby-linkbutton" class="button-link" href="' + url.Url + '" target="_blank">' + url.Name + '</a>');
} }
} }
var html = [];
if (links.length) { if (links.length) {
html.push(links.join(', ')); html.push(links.join(', '));
} }
linksElem.innerHTML = html.join(', '); externalLinksElem.innerHTML = html.join(', ');
if (html.length) { if (html.length) {
linksElem.classList.remove('hide'); externalLinksElem.classList.remove('hide');
} else { } else {
linksElem.classList.add('hide'); externalLinksElem.classList.add('hide');
} }
} }
function renderDetailImage(page, elem, item, apiClient, editable, imageLoader, indicators) { function renderDetailImage(elem, item, imageLoader) {
if ('SeriesTimer' === item.Type || 'Program' === item.Type) { const itemArray = [];
editable = false; 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'); elem.innerHTML = cardHtml;
imageLoader.lazyChildren(elem);
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);
}
} }
function renderImage(page, item, apiClient, user) { function renderImage(page, item) {
renderDetailImage( renderDetailImage(
page,
page.querySelector('.detailImageContainer'), page.querySelector('.detailImageContainer'),
item, item,
apiClient, imageLoader
user.Policy.IsAdministrator && 'Photo' != item.MediaType,
imageLoader,
indicators
); );
} }
@ -985,54 +853,41 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
} }
} }
function renderOverview(elems, item) { function renderOverview(page, item) {
for (var i = 0, length = elems.length; i < length; i++) { for (const overviewElemnt of page.querySelectorAll('.overview')) {
var elem = elems[i];
var overview = item.Overview || ''; var overview = item.Overview || '';
if (overview) { if (overview) {
elem.innerHTML = overview; overviewElemnt.innerHTML = overview;
elem.classList.remove('hide'); overviewElemnt.classList.remove('hide');
elem.classList.add('detail-clamp-text'); overviewElemnt.classList.add('detail-clamp-text');
// Grab the sibling element to control the expand state // 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 // Detect if we have overflow of text. Based on this StackOverflow answer
// https://stackoverflow.com/a/35157976 // 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'); expandButton.classList.remove('hide');
} else { } else {
expandButton.classList.add('hide'); expandButton.classList.add('hide');
} }
expandButton.addEventListener('click', toggleLineClamp.bind(null, elem)); expandButton.addEventListener('click', toggleLineClamp.bind(null, overviewElemnt));
var anchors = elem.querySelectorAll('a'); for (const anchor of overviewElemnt.querySelectorAll('a')) {
anchor.setAttribute('target', '_blank');
for (var j = 0, length2 = anchors.length; j < length2; j++) {
anchors[j].setAttribute('target', '_blank');
} }
} else { } else {
elem.innerHTML = ''; overviewElemnt.innerHTML = '';
elem.classList.add('hide'); overviewElemnt.classList.add('hide');
} }
} }
} }
function renderGenres(page, item, context) { function renderGenres(page, item, context = inferContext(item)) {
context = context || inferContext(item);
var type;
var genres = item.GenreItems || []; var genres = item.GenreItems || [];
var type = context === 'music' ? 'MusicGenre' : 'Genre';
switch (context) {
case 'music':
type = 'MusicGenre';
break;
default:
type = 'Genre';
}
var html = genres.map(function (p) { var html = genres.map(function (p) {
return '<a style="color:inherit;" class="button-link" is="emby-linkbutton" href="' + appRouter.getRouteUrl({ 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) { function renderWriter(page, item, context) {
var directors = (item.People || []).filter(function (p) { var writers = (item.People || []).filter(function (person) {
return 'Director' === p.Type; 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({ return '<a style="color:inherit;" class="button-link" is="emby-linkbutton" href="' + appRouter.getRouteUrl({
Name: p.Name, Name: person.Name,
Type: 'Person', Type: 'Person',
ServerId: item.ServerId, ServerId: item.ServerId,
Id: p.Id Id: person.Id
}, { }, {
context: context 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(', '); }).join(', ');
var directorsLabel = page.querySelector('.directorsLabel'); var directorsLabel = page.querySelector('.directorsLabel');
@ -1086,13 +971,39 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
} }
} }
function renderDetails(page, item, apiClient, context, isStatic) { function renderMiscInfo(page, item) {
renderSimilarItems(page, item, context); const primaryItemMiscInfo = page.querySelectorAll('.itemMiscInfo-primary');
renderMoreFromSeason(page, item, apiClient);
renderMoreFromArtist(page, item, apiClient); for (const miscInfo of primaryItemMiscInfo) {
renderDirector(page, item, context); mediaInfo.fillPrimaryMediaInfo(miscInfo, item, {
renderGenres(page, item, context); interactive: true,
renderChannelGuide(page, apiClient, item); 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'); var taglineElement = page.querySelector('.tagline');
if (item.Taglines && item.Taglines.length) { if (item.Taglines && item.Taglines.length) {
@ -1101,44 +1012,21 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
} else { } else {
taglineElement.classList.add('hide'); taglineElement.classList.add('hide');
} }
}
var overview = page.querySelector('.overview'); function renderDetails(page, item, apiClient, context, isStatic) {
var externalLinksElem = page.querySelector('.itemExternalLinks'); renderSimilarItems(page, item, context);
renderMoreFromSeason(page, item, apiClient);
renderOverview([overview], item); renderMoreFromArtist(page, item, apiClient);
renderDirector(page, item, context);
var i; renderWriter(page, item, context);
var itemMiscInfo; renderGenres(page, item, context);
itemMiscInfo = page.querySelectorAll('.itemMiscInfo-primary'); renderChannelGuide(page, apiClient, item);
for (i = 0; i < itemMiscInfo.length; i++) { renderTagline(page, item);
mediaInfo.fillPrimaryMediaInfo(itemMiscInfo[i], item, { renderOverview(page, item);
interactive: true, renderMiscInfo(page, item);
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');
}
}
reloadUserDataButtons(page, item); reloadUserDataButtons(page, item);
renderLinks(externalLinksElem, item); renderLinks(page, item);
renderTags(page, item); renderTags(page, item);
renderSeriesAirTime(page, item, isStatic); renderSeriesAirTime(page, item, isStatic);
} }
@ -1426,8 +1314,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
action: 'playallfromhere', action: 'playallfromhere',
image: false, image: false,
artist: 'auto', artist: 'auto',
containerAlbumArtists: item.AlbumArtists, containerAlbumArtists: item.AlbumArtists
addToListButton: true
}); });
isList = true; isList = true;
} else if ('Series' == item.Type) { } else if ('Series' == item.Type) {
@ -1469,11 +1356,12 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
items: result.Items, items: result.Items,
showIndexNumber: false, showIndexNumber: false,
enableOverview: true, enableOverview: true,
enablePlayedButton: layoutManager.mobile ? false : true,
infoButton: layoutManager.mobile ? false : true,
imageSize: 'large', imageSize: 'large',
enableSideMediaInfo: false, enableSideMediaInfo: false,
highlight: false, highlight: false,
action: layoutManager.tv ? 'resume' : 'none', action: !layoutManager.desktop ? 'link' : 'none',
infoButton: true,
imagePlayButton: true, imagePlayButton: true,
includeParentInfoInTitle: false includeParentInfoInTitle: false
}); });
@ -1500,6 +1388,9 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
childrenItemsContainer.classList.remove('vertical-list'); childrenItemsContainer.classList.remove('vertical-list');
} }
} }
if (layoutManager.mobile) {
childrenItemsContainer.classList.remove('padded-right');
}
childrenItemsContainer.innerHTML = html; childrenItemsContainer.innerHTML = html;
imageLoader.lazyChildren(childrenItemsContainer); imageLoader.lazyChildren(childrenItemsContainer);
if ('BoxSet' == item.Type) { if ('BoxSet' == item.Type) {
@ -1701,12 +1592,10 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
} }
function renderCollectionItems(page, parentItem, types, items) { function renderCollectionItems(page, parentItem, types, items) {
page.querySelector('.collectionItems').classList.remove('hide');
page.querySelector('.collectionItems').innerHTML = ''; page.querySelector('.collectionItems').innerHTML = '';
var i;
var length;
for (i = 0, length = types.length; i < length; i++) { for (const type of types) {
var type = types[i];
var typeItems = filterItemsByCollectionItemType(items, type); var typeItems = filterItemsByCollectionItemType(items, type);
if (typeItems.length) { if (typeItems.length) {
@ -1739,8 +1628,8 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
renderChildren(page, parentItem); renderChildren(page, parentItem);
}; };
for (i = 0, length = containers.length; i < length; i++) { for (const container of containers) {
containers[i].notifyRefreshNeeded = notifyRefreshNeeded; container.notifyRefreshNeeded = notifyRefreshNeeded;
} }
// if nothing in the collection can be played hide play and shuffle buttons // 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) { function renderCast(page, item) {
var people = (item.People || []).filter(function (p) { var people = (item.People || []).filter(function (p) {
return 'Director' !== p.Type; return p.Type === 'Actor';
}); });
if (!people.length) { if (!people.length) {
@ -1905,12 +1794,10 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
} }
function bindAll(view, selector, eventName, fn) { function bindAll(view, selector, eventName, fn) {
var i;
var length;
var elems = view.querySelectorAll(selector); var elems = view.querySelectorAll(selector);
for (i = 0, length = elems.length; i < length; i++) { for (const elem of elems) {
elems[i].addEventListener(eventName, fn); elem.addEventListener(eventName, fn);
} }
} }
@ -1923,13 +1810,14 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
return function (view, params) { return function (view, params) {
function reload(instance, page, params) { function reload(instance, page, params) {
loading.show(); loading.show();
var apiClient = params.serverId ? connectionManager.getApiClient(params.serverId) : ApiClient; var apiClient = params.serverId ? connectionManager.getApiClient(params.serverId) : ApiClient;
var promises = [getPromise(apiClient, params), apiClient.getCurrentUser()];
Promise.all(promises).then(function (responses) { Promise.all([getPromise(apiClient, params), apiClient.getCurrentUser()]).then(([item, user]) => {
var item = responses[0];
var user = responses[1];
currentItem = item; currentItem = item;
reloadFromItem(instance, page, params, item, user); 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() { function onPlayClick() {
@ -1995,15 +1883,6 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
playbackManager.shuffle(currentItem); playbackManager.shuffle(currentItem);
} }
function onDeleteClick() {
require(['deleteHelper'], function (deleteHelper) {
deleteHelper.deleteItem({
item: currentItem,
navigate: true
});
});
}
function onCancelSeriesTimerClick() { function onCancelSeriesTimerClick() {
require(['recordingHelper'], function (recordingHelper) { require(['recordingHelper'], function (recordingHelper) {
recordingHelper.cancelSeriesTimerWithConfirmation(currentItem.Id, currentItem.ServerId).then(function () { 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, '.btnPlayTrailer', 'click', onPlayTrailerClick);
bindAll(view, '.btnCancelSeriesTimer', 'click', onCancelSeriesTimerClick); bindAll(view, '.btnCancelSeriesTimer', 'click', onCancelSeriesTimerClick);
bindAll(view, '.btnCancelTimer', 'click', onCancelTimerClick); bindAll(view, '.btnCancelTimer', 'click', onCancelTimerClick);
bindAll(view, '.btnDeleteItem', 'click', onDeleteClick);
bindAll(view, '.btnDownload', 'click', onDownloadClick); bindAll(view, '.btnDownload', 'click', onDownloadClick);
view.querySelector('.trackSelections').addEventListener('submit', onTrackSelectionsSubmit); view.querySelector('.trackSelections').addEventListener('submit', onTrackSelectionsSubmit);
view.querySelector('.btnSplitVersions').addEventListener('click', function () { view.querySelector('.btnSplitVersions').addEventListener('click', function () {
@ -2125,9 +2003,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti
view.addEventListener('viewshow', function (e) { view.addEventListener('viewshow', function (e) {
var page = this; var page = this;
if (layoutManager.mobile) { libraryMenu.setTransparentMenu(true);
libraryMenu.setTransparentMenu(true);
}
if (e.detail.isRestored) { if (e.detail.isRestored) {
if (currentItem) { if (currentItem) {

View file

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

View file

@ -1,6 +1,14 @@
<<<<<<< HEAD
import subtitleSettings from 'subtitleSettings'; import subtitleSettings from 'subtitleSettings';
import {UserSettings, currentSettings as userSettings} from 'userSettings'; import {UserSettings, currentSettings as userSettings} from 'userSettings';
import autoFocuser from 'autoFocuser'; 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 { export class SubtitleController {
constructor(view, params) { constructor(view, params) {
@ -10,6 +18,7 @@ export class SubtitleController {
this.subtitleSettingsInstance = null; this.subtitleSettingsInstance = null;
this.view = view; this.view = view;
<<<<<<< HEAD
view.addEventListener('viewshow', this.viewShow.bind(this)); view.addEventListener('viewshow', this.viewShow.bind(this));
view.addEventListener('change', this.change.bind(this)); view.addEventListener('change', this.change.bind(this));
view.addEventListener('viewbeforehide', this.viewBeforeHide.bind(this)); view.addEventListener('viewbeforehide', this.viewBeforeHide.bind(this));
@ -56,8 +65,53 @@ export class SubtitleController {
beforeUnload(e) { beforeUnload(e) {
if (this.hasChanges) { if (this.hasChanges) {
e.returnValue = 'You currently have unsaved changes. Are you sure you wish to leave?'; 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; 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 browser from 'browser';
import dom from 'dom'; import dom from 'dom';
import layoutManager from 'layoutManager'; import layoutManager from 'layoutManager';
@ -6,6 +7,10 @@ import appRouter from 'appRouter';
import appHost from 'apphost'; import appHost from 'apphost';
import 'css!./emby-button'; import 'css!./emby-button';
import 'registerElement'; 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 EmbyButtonPrototype = Object.create(HTMLButtonElement.prototype);
const EmbyLinkButtonPrototype = Object.create(HTMLAnchorElement.prototype); const EmbyLinkButtonPrototype = Object.create(HTMLAnchorElement.prototype);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
import itemShortcuts from 'itemShortcuts'; import itemShortcuts from 'itemShortcuts';
import inputManager from 'inputManager'; import inputManager from 'inputManager';
import connectionManager from 'connectionManager'; import connectionManager from 'connectionManager';
@ -15,6 +16,12 @@ import 'registerElement';
/* eslint-disable indent */ /* eslint-disable indent */
const ItemsContainerPrototype = Object.create(HTMLDivElement.prototype); 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) { function onClick(e) {
const itemsContainer = this; const itemsContainer = this;
@ -42,7 +49,7 @@ import 'registerElement';
// check for serverId, it won't be present on selectserver // check for serverId, it won't be present on selectserver
if (card && card.getAttribute('data-serverid')) { if (card && card.getAttribute('data-serverid')) {
inputManager.trigger('menu', { inputManager.handleCommand('menu', {
sourceElement: card sourceElement: card
}); });

View file

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

View file

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

View file

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

View file

@ -1,8 +1,13 @@
<<<<<<< HEAD
import layoutManager from 'layoutManager'; import layoutManager from 'layoutManager';
import dom from 'dom'; import dom from 'dom';
import 'css!./emby-scrollbuttons'; import 'css!./emby-scrollbuttons';
import 'registerElement'; import 'registerElement';
import 'paper-icon-button-light'; 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 */ /* eslint-disable indent */

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
import scroller from 'scroller'; import scroller from 'scroller';
import dom from 'dom'; import dom from 'dom';
import layoutManager from 'layoutManager'; import layoutManager from 'layoutManager';
@ -6,6 +7,10 @@ import focusManager from 'focusManager';
import browser from 'browser'; import browser from 'browser';
import 'registerElement'; import 'registerElement';
import 'css!./emby-scroller'; 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 */ /* eslint-disable indent */

View file

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

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
import browser from 'browser'; import browser from 'browser';
import dom from 'dom'; import dom from 'dom';
import layoutManager from 'layoutManager'; import layoutManager from 'layoutManager';
@ -5,6 +6,10 @@ import keyboardnavigation from 'keyboardnavigation';
import 'css!./emby-slider'; import 'css!./emby-slider';
import 'webcomponents'; import 'webcomponents';
import 'emby-input'; 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 */ /* eslint-disable indent */

View file

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

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
import dom from 'dom'; import dom from 'dom';
import scroller from 'scroller'; import scroller from 'scroller';
import browser from 'browser'; import browser from 'browser';
@ -6,6 +7,10 @@ import focusManager from 'focusManager';
import 'registerElement'; import 'registerElement';
import 'css!./emby-tabs'; import 'css!./emby-tabs';
import 'scrollStyles'; 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 */ /* eslint-disable indent */

View file

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

View file

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

View file

@ -52,7 +52,7 @@
<!-- iPad Pro 1 --> <!-- 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.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" /> <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 --> <!-- 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.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" /> <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> <title>Jellyfin</title>
<style> <style>
.transparentDocument, .backgroundContainer-transparent:not(.withBackdrop) { .transparentDocument,
.backgroundContainer-transparent:not(.withBackdrop) {
background: none !important; background: none !important;
background-color: transparent !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; cursor: none !important;
} }
@ -82,7 +89,9 @@
background-color: #101010; 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; display: none !important;
} }
@ -94,6 +103,42 @@
z-index: 1; z-index: 1;
width: 0.8em; 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> </style>
</head> </head>
<body> <body>
@ -103,7 +148,9 @@
<div class="mainDrawer-scrollContainer scrollContainer focuscontainer-y"></div> <div class="mainDrawer-scrollContainer scrollContainer focuscontainer-y"></div>
</div> </div>
<div class="skinHeader focuscontainer-x"></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> <div class="mainDrawerHandle"></div>
<!-- inject:js --> <!-- 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"> <div class="padded-left padded-right padded-bottom-page">
<form class="manualLoginForm hide" style="margin: 0 auto;"> <form class="manualLoginForm hide">
<h1 style="margin-top:1em;text-align: left;">${HeaderPleaseSignIn}</h1> <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;"> <div style="height:0; overflow: hidden;">
<input type="text" name="fakeusernameremembered" tabindex="-1" /> <input type="text" name="fakeusernameremembered" tabindex="-1" />
<input type="password" name="fakepasswordremembered" tabindex="-1" /> <input type="password" name="fakepasswordremembered" tabindex="-1" />
@ -29,8 +31,6 @@
<span>${ButtonCancel}</span> <span>${ButtonCancel}</span>
</button> </button>
</div> </div>
<br/>
<br/>
</form> </form>
<div class="visualLoginForm" style="text-align: center;"> <div class="visualLoginForm" style="text-align: center;">

View file

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

View file

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

View file

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

View file

@ -263,7 +263,7 @@ define(['playbackManager', 'events', 'serverNotifications', 'connectionManager']
appName: s.Client, appName: s.Client,
playableMediaTypes: s.PlayableMediaTypes, playableMediaTypes: s.PlayableMediaTypes,
isLocalPlayer: false, isLocalPlayer: false,
supportedCommands: s.SupportedCommands, supportedCommands: s.Capabilities.SupportedCommands,
user: s.UserId ? { user: s.UserId ? {
Id: 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) { SessionPlayer.prototype.displayContent = function (options) {
sendCommandByName(this, 'DisplayContent', options); sendCommandByName(this, 'DisplayContent', options);

View file

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

View file

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

View file

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

View file

@ -201,6 +201,9 @@ import appHost from 'apphost';
'rewind': () => { 'rewind': () => {
playbackManager.rewind(); playbackManager.rewind();
}, },
'seek': () => {
playbackManager.seekMs(options);
},
'togglefullscreen': () => { 'togglefullscreen': () => {
playbackManager.toggleFullscreen(); playbackManager.toggleFullscreen();
}, },
@ -235,9 +238,6 @@ import appHost from 'apphost';
} }
} }
// Alias for backward compatibility
export const trigger = handleCommand;
dom.addEventListener(document, 'click', notify, { dom.addEventListener(document, 'click', notify, {
passive: true passive: true
}); });
@ -245,8 +245,7 @@ import appHost from 'apphost';
/* eslint-enable indent */ /* eslint-enable indent */
export default { export default {
trigger: handleCommand, handleCommand: handleCommand,
handle: handleCommand,
notify: notify, notify: notify,
notifyMouseMove: notifyMouseMove, notifyMouseMove: notifyMouseMove,
idleTime: idleTime, 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="' + sectionClass + '" data-type="' + section.type + '">';
html += '<div class="sectionTitleContainer sectionTitleContainer-cards">'; html += '<div class="sectionTitleContainer sectionTitleContainer-cards">';
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">'; html += '<h2 class="sectionTitle sectionTitle-cards">';
html += section.name; html += section.name;
html += '</h2>'; 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 += '<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>';
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right">'; html += '<div is="emby-itemscontainer" class="itemsContainer padded-right">';
html += '</div>'; html += '</div>';
return html += '</div>'; return html += '</div>';
}).join(''); }).join('');

View file

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

View file

@ -13,7 +13,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
html += '<div class="headerRight">'; html += '<div class="headerRight">';
html += '<span class="headerSelectedPlayer"></span>'; 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="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 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 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>'; 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; 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'); headerSyncButton.classList.remove('hide');
} }
} else { } else {
@ -116,7 +117,7 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
} }
function showSearch() { function showSearch() {
inputManager.trigger('search'); inputManager.handleCommand('search');
} }
function onHeaderUserButtonClick(e) { function onHeaderUserButtonClick(e) {
@ -967,8 +968,10 @@ define(['dom', 'layoutManager', 'inputManager', 'connectionManager', 'events', '
updateUserInHeader(); updateUserInHeader();
}); });
events.on(playbackManager, 'playerchange', updateCastIcon); events.on(playbackManager, 'playerchange', updateCastIcon);
events.on(syncPlayManager, 'enabled', onSyncPlayEnabled); events.on(syncPlayManager, 'enabled', onSyncPlayEnabled);
events.on(syncPlayManager, 'syncing', onSyncPlaySyncing); events.on(syncPlayManager, 'syncing', onSyncPlaySyncing);
loadNavDrawer(); loadNavDrawer();
return LibraryMenu; return LibraryMenu;
}); });

View file

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

View file

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

View file

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

View file

@ -196,7 +196,7 @@ var Dashboard = {
capabilities: function (appHost) { capabilities: function (appHost) {
var capabilities = { var capabilities = {
PlayableMediaTypes: ['Audio', 'Video'], 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, SupportsPersistentIdentifier: 'cordova' === self.appMode || 'android' === self.appMode,
SupportsMediaControl: true SupportsMediaControl: true
}; };
@ -387,8 +387,6 @@ var AppInfo = {};
define('lazyLoader', [componentsPath + '/lazyLoader/lazyLoaderIntersectionObserver'], returnFirstDependency); define('lazyLoader', [componentsPath + '/lazyLoader/lazyLoaderIntersectionObserver'], returnFirstDependency);
define('shell', [scriptsPath + '/shell'], returnFirstDependency); define('shell', [scriptsPath + '/shell'], returnFirstDependency);
define('registerElement', ['document-register-element'], returnFirstDependency);
define('alert', [componentsPath + '/alert'], returnFirstDependency); define('alert', [componentsPath + '/alert'], returnFirstDependency);
defineResizeObserver(); defineResizeObserver();
@ -672,7 +670,6 @@ var AppInfo = {};
}, },
bundles: { bundles: {
bundle: [ bundle: [
'document-register-element',
'fetch', 'fetch',
'flvjs', 'flvjs',
'jstree', 'jstree',
@ -1039,7 +1036,7 @@ var AppInfo = {};
} }
if ('SeriesTimer' == itemType) { if ('SeriesTimer' == itemType) {
return 'itemdetails.html?seriesTimerId=' + id + '&serverId=' + serverId; return 'details?seriesTimerId=' + id + '&serverId=' + serverId;
} }
if ('livetv' == item.CollectionType) { if ('livetv' == item.CollectionType) {
@ -1109,13 +1106,13 @@ var AppInfo = {};
var itemTypes = ['Playlist', 'TvChannel', 'Program', 'BoxSet', 'MusicAlbum', 'MusicGenre', 'Person', 'Recording', 'MusicArtist']; var itemTypes = ['Playlist', 'TvChannel', 'Program', 'BoxSet', 'MusicAlbum', 'MusicGenre', 'Person', 'Recording', 'MusicArtist'];
if (itemTypes.indexOf(itemType) >= 0) { if (itemTypes.indexOf(itemType) >= 0) {
return 'itemdetails.html?id=' + id + '&serverId=' + serverId; return 'details?id=' + id + '&serverId=' + serverId;
} }
var contextSuffix = context ? '&context=' + context : ''; var contextSuffix = context ? '&context=' + context : '';
if ('Series' == itemType || 'Season' == itemType || 'Episode' == itemType) { 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) { if (item.IsFolder) {
@ -1126,7 +1123,7 @@ var AppInfo = {};
return '#'; return '#';
} }
return 'itemdetails.html?id=' + id + '&serverId=' + serverId; return 'details?id=' + id + '&serverId=' + serverId;
}; };
appRouter.showItem = showItem; 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 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 flex flex-direction-column" style="margin: 2em 0 1em;"> <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" style="margin-bottom:2em;"> <div class="padded-left padded-right flex align-items-center justify-content-center">
<h1 class="sectionTitle sectionTitle-cards">${HeaderSelectServer}</h1> <h1 class="sectionTitle sectionTitle-cards">${HeaderSelectServer}</h1>
</div> </div>
<div class="padded-top padded-bottom-focusscale flex-grow flex" data-horizontal="true" data-centerfocus="card"> <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> </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;"> <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