Compare commits

...

277 Commits

Author SHA1 Message Date
Joshua M. Boniface
8e37078b60 Bump version to 10.8.13
Some checks failed
Lint / Run eslint (push) Has been cancelled
Lint / Run stylelint (css) (push) Has been cancelled
Lint / Run stylelint (scss) (push) Has been cancelled
2023-11-28 22:22:14 -05:00
Joshua M. Boniface
a5184bb843 Merge pull request #5019 from joshuaboniface/set-ffmpeg-disabled
Set FFmpeg path disabled
2023-11-28 22:19:29 -05:00
Joshua M. Boniface
b9ee65e49d Set FFmpeg path disabled 2023-11-27 12:04:19 -05:00
Bill Thornton
d5ebc64fcb Merge pull request #4992 from rafma0/release-10.8.z 2023-11-14 18:36:22 -05:00
Bill Thornton
f4c49427fd Merge pull request #4993 from rafma0/backport-fix-tizen-flac 2023-11-14 18:35:52 -05:00
rafma0
6c97b7a6d8 remove video audio flac on tizen 2023-11-12 23:43:10 -03:00
rafma0
0859d4d881 fix jittering in checkboxes on tv ui 2023-11-12 23:17:29 -03:00
Joshua M. Boniface
4b6bbcfe26 Bump version to 10.8.12 2023-11-04 14:42:47 -04:00
Bill Thornton
1a1735340f Merge pull request #4912 from dmitrylyzo/fix-imports
Fix imports
2023-10-24 13:13:10 -04:00
Dmitry Lyzo
5dad4b4486 Fix imports in Playback/Queue controller
According to the template.
2023-10-24 14:10:33 +03:00
Dmitry Lyzo
201aec56c6 Fix imports in RemoteControl 2023-10-24 14:10:33 +03:00
Bill Thornton
d274d7f741 Merge pull request #4892 from thornbill/backport-4860 2023-10-19 23:04:38 -04:00
cbe
b270d4051b Fix language/subtitle switcher when using gamepad
Raising the event code `13` (Enter) should be a lot more stable since `0`
is just the default and not assigned to any actual key [1]. This keycode
also has been standardized early enough to change it at this crucial part
of gamepad handling in my opinion.

[1] https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode

Fixes #3755
2023-10-19 19:18:29 -04:00
Bill Thornton
aa09fa710e Merge pull request #4893 from thornbill/backport-4754 2023-10-19 19:01:52 -04:00
Marcus Nilsson
c33c2b1e26 Change Subtitle Sync slider to go from -300 to 300
This patch changes the subtitle sync from using a procentage to a
'slider value' that ranges from -300 to 300. The reasons for this is that
WebOS doesn't jump in 0.1 increments but instead jumps 1.0 increments in
the slider, which results in subtitle sync jumping 0.6s per increment.
Using a value from -300 to 300 makes LG WebOS jump 0.1s instead.
2023-10-19 12:53:23 -04:00
Bill Thornton
eb7fb6b39d Merge pull request #4837 from dmitrylyzo/fix-noitem-play 2023-10-04 08:04:12 -04:00
Dmitry Lyzo
5fd5292f6d Fix playing empty item set 2023-10-04 13:49:23 +03:00
Bill Thornton
1250c76567 Merge pull request #4813 from dmitrylyzo/backport/slider-force-change 2023-09-29 07:55:45 -04:00
Bill Thornton
1ba6bde32a Merge pull request #4797 from Mauroq/release-10.8.z 2023-09-29 07:54:50 -04:00
Dmitry Lyzo
3c80bf7b19 Handle pressing Enter to finish keyboard dragging of slider
(cherry picked from commit 9024ebea39)
2023-09-29 13:19:48 +03:00
Joshua M. Boniface
1e1af1c67f Merge pull request #4787 from dmitrylyzo/fix-legacy 2023-09-23 21:49:36 -04:00
Joshua M. Boniface
8d1c34f80e Bump version to 10.8.11 2023-09-23 21:41:43 -04:00
Dmitry Lyzo
d3e3bc7282 Fix homesections in legacy browser 2023-09-23 23:26:48 +03:00
Dennis de Lange
0ad87f3b87 fix: Remove h264-10bit support from Tizen 2023-09-22 17:41:26 +02:00
Bill Thornton
9e871d43ec Merge pull request #4632 from dmitrylyzo/multi-purpose-keys 2023-09-21 12:23:04 -04:00
Dmitry Lyzo
11ae2ff43f Add KeyboardEvent constructor polyfill 2023-09-14 01:28:19 +03:00
Dmitry Lyzo
76c55116ce Change behavior of arrow keys and Enter when OSD is hidden 2023-09-14 01:28:19 +03:00
Dmitry Lyzo
884ce171ea Focus on corresponding button 2023-09-14 01:26:55 +03:00
Dmitry Lyzo
51bd2bef1a Don't show OSD for Fullscreen and Mute 2023-09-14 01:26:55 +03:00
Bill Thornton
0bfe91b0fa Merge pull request #4757 from dmitrylyzo/fix-samsung_tv-dolbyvision
Remove Dolby Vision support on Samsung TV (Tizen)
2023-09-08 16:56:26 -04:00
Bill Thornton
a5feddb48b Merge pull request #4758 from dmitrylyzo/fix-slider-step 2023-09-08 07:52:20 -04:00
Dmitry Lyzo
7d27596d6b Fix slider step
Use the value of the `step` attribute if no keyboard steps are specified.
2023-08-24 00:23:11 +03:00
Dmitry Lyzo
e0cb79088b fix: Remove Dolby Vision support on Samsung TV 2023-08-23 00:18:25 +03:00
Bill Thornton
b2f3720282 Merge pull request #4709 from thornbill/backport-4692
Fix scheduled task time limit key
2023-07-07 20:16:52 -04:00
hikaps
b67f4eccfb Fix scheduled task time limit key 2023-07-07 19:42:53 -04:00
Bill Thornton
331fa87216 Merge pull request #4654 from joshuaboniface/additionalPluginVerification
Add confirmation for 3rd party repos
2023-07-01 01:32:05 -04:00
Joshua M. Boniface
59813ee0ea Merge branch 'release-10.8.z' into additionalPluginVerification 2023-06-28 23:01:40 -04:00
Joshua M. Boniface
eaae0f3c55 Add translation of "unknown" repo details 2023-06-28 22:49:58 -04:00
Bill Thornton
6304e27940 Merge pull request #4688 from dmitrylyzo/fix-macos-alac 2023-06-14 13:06:50 -04:00
Dmitry Lyzo
7ada8796a7 Disable ALAC on MacOS in non-Safari browsers 2023-06-14 11:30:22 +03:00
Bill Thornton
eab36f9934 Merge pull request #4685 from hurani/backport-directory-viewer-parent-fix 2023-06-12 16:39:52 -04:00
Bill Thornton
b044bc25de Merge pull request #4171 from nielsvanvelzen/directory-browser-go-up
Fix going to parent folder in directory browser
2023-06-11 22:34:23 -07:00
Bill Thornton
5cc91f2ee0 Merge pull request #4657 from thornbill/subs-xss
Fix xss in custom subtitles element
2023-06-01 02:13:28 -04:00
Bill Thornton
2ffb833daf Fix xss in custom subtitles element 2023-06-01 01:33:59 -04:00
Joshua M. Boniface
93d63330fd Fix conditional formatting and add fallback 2023-05-30 10:39:27 -04:00
Joshua M. Boniface
f1b0b504dd Check for pkg.versions.length
Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>
2023-05-30 10:35:46 -04:00
Joshua M. Boniface
919be18c84 Fix syntax error in HTML 2023-05-30 10:13:21 -04:00
Joshua M. Boniface
cf530b30d5 Fix linting errors
* Remove superfluous variable
* Remove extra random spaces from editor
* Use single-quotes around text
2023-05-30 09:35:23 -04:00
Joshua M. Boniface
dd004ec06b Add labels to i8n 2023-05-30 09:25:32 -04:00
Joshua M. Boniface
a11d74ae68 Use camelCase variable name 2023-05-30 09:21:41 -04:00
Joshua M. Boniface
da9eece6c0 Remove extra console.log 2023-05-29 11:19:13 -04:00
Joshua M. Boniface
509cbabedb Add confirmation for 3rd party repos
Adds a confirmation similar to the one performed during plugin
installation, when adding a 3rd party repository.

The safe domain is hardcoded to be "repo.jellyfin.org" as this is very
stable and we have no plans to change it. Individual mirrors don't need
to be specified since this is user-input content and they should be
using the main URL not the URL of a specific mirror.

The confirmation message makes explicit mention of the possibility of
malicious code from 3rd party repositories as well as updates that may
bring it in, and suggests only adding 3rd parties from trusted people.

The plugin install confirmation is also modified to use the same
conditional and an altered message similar to the above, again to
emphasize the potential security risks of 3rd party plugins.

Finally, some additional information is added to the Developer Info
section of the plugin page; specifically, the name of the repository the
plugin is sourced from as well as its URL. How this is obtained is a
hack, since these should probably be part of the main information about
the plugin and not each specific version, but this is worked around by
only showing the information from the first (i.e. newest) version.
2023-05-29 10:59:21 -04:00
Bill Thornton
62246fe0a9 Merge pull request #4628 from dmitrylyzo/fix-volume-slider 2023-05-21 12:32:19 -04:00
Bill Thornton
35a7dfbed6 Merge pull request #4627 from dmitrylyzo/fix-slider-bubble 2023-05-21 01:43:51 -04:00
Dmitry Lyzo
b93221a9b2 Fix initial state of slider 2023-05-20 23:36:20 +03:00
Dmitry Lyzo
1a858d9dda Fix overlap of slider bubble 2023-05-20 23:04:14 +03:00
Bill Thornton
07ce5c44a1 Merge pull request #4591 from thornbill/fix-es-quick-connect 2023-05-12 10:07:58 -04:00
Bill Thornton
2eda12ba8f Fix QuickConnect code not displaying in Spanish 2023-05-12 08:31:04 -04:00
Bill Thornton
9266e51aaf Merge pull request #4589 from thornbill/fix-api-key-xss 2023-05-11 17:10:22 -04:00
Bill Thornton
069ea049eb Fix xss in api key page 2023-05-11 16:48:06 -04:00
Bill Thornton
56af039fb9 Merge pull request #4561 from dmitrylyzo/fix-bottom-hide
Fix bottom video controls don't auto-hide
2023-05-02 16:54:10 -04:00
Dmitry Lyzo
1fb5c4d95d Fix query selector target 2023-05-02 22:36:51 +03:00
Bill Thornton
828fa340d5 Merge pull request #4553 from dmitrylyzo/fix-tv-episode-autoplay 2023-05-02 00:53:01 -04:00
Dmitry Lyzo
a77a8c7aec fix: Unhide PlayNextEpisodeAutomatically on TV 2023-05-01 02:17:50 +03:00
Joshua M. Boniface
55714d5341 Bump version to 10.8.10 2023-04-23 11:01:37 -04:00
Joshua M. Boniface
b88a5951e1 Merge pull request from GHSA-89hp-h43h-r5pq
Escape device id in raw HTML
2023-04-23 11:00:06 -04:00
Ian Walton
bd480aa1db Escape device id in raw HTML. 2023-04-22 10:12:58 -04:00
Bill Thornton
cf0cf93e47 Merge pull request #4492 from nyanmisaka/tonemap-mode-options
Add the tonemap mode options
2023-04-16 23:18:43 -04:00
nyanmisaka
f942072e53 Update translations for tonemapping
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-04-14 18:55:08 +08:00
nyanmisaka
f28db6699d Add tonemap mode options
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-04-14 18:50:24 +08:00
Bill Thornton
a447786a24 Merge pull request #4487 from thornbill/backport-4485
Fix dead documentation link
2023-04-12 11:01:56 -04:00
Bill Thornton
c9e5b35e42 Merge pull request #4385 from nyanmisaka/drop-progressive
Drop progressive transcoding in web client
2023-04-12 10:15:52 -04:00
Brett Petch
a2ffa9dfaf Update src/controllers/dashboard/encodingsettings.html
Co-authored-by: Bill Thornton <thornbill@users.noreply.github.com>
2023-04-12 10:08:25 -04:00
Brett Petch
3c8c6ad469 fix: dead link 2023-04-12 10:08:11 -04:00
nyanmisaka
62dbf0d106 Drop progressive transcoding in web client
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-03-14 20:38:08 +08:00
Bill Thornton
22d1f40587 Merge pull request #4395 from thornbill/fix-installed-plugin-version
Fix installed plugin version html
2023-03-07 16:27:42 -05:00
Bill Thornton
909f03460c Fix installed plugin version html 2023-03-06 16:27:35 -05:00
Bill Thornton
7e99e3ec51 Merge pull request #4330 from dmitrylyzo/fix-navigation-input
Fix navigation for some types of INPUT
2023-02-27 10:33:01 -05:00
Bill Thornton
af27e084d5 Merge pull request #4362 from dmitrylyzo/fix-reset-subtitleoffset
Fix subtitle offset reset when seeking progressive stream
2023-02-23 16:46:26 -05:00
Bill Thornton
8f75a42669 Merge pull request #4356 from dmitrylyzo/babel-libass 2023-02-18 22:10:44 -05:00
Dmitry Lyzo
1040685c1e Fix subtitle offset reset when seeking progressive stream 2023-02-19 01:41:23 +03:00
Dmitry Lyzo
38aebf4e42 Babelify @jellyfin/libass-wasm 2023-02-14 23:13:03 +03:00
Dmitry Lyzo
62d0354fe0 Simplify adding modules to babel-loader 2023-02-14 23:13:03 +03:00
Dmitry Lyzo
e580f0c869 Fix navigation for some types of INPUT
Regression from 36fce00270
2023-02-06 15:27:42 +03:00
Bill Thornton
551f12fdfb Merge pull request #4312 from dmitrylyzo/backport-4150
Backport PR #4150 to 10.8.z branch
2023-01-31 12:20:44 -05:00
Bill Thornton
00d59c546d Merge pull request #4310 from jsayol/patch-1
Backport PR #4147 to 10.8.z branch
2023-01-31 12:19:23 -05:00
Dmitry Lyzo
0864432105 Fix file name escaping
(cherry picked from commit 45542a67a4)
2023-01-28 23:30:07 +03:00
Dmitry Lyzo
0c3c47b8b4 Fix Subtitle Uploder navigation in TV mode
(cherry picked from commit 78bbac8ca7)
2023-01-28 23:30:07 +03:00
Josep Sayol
5f7d2659dc Backport PR #4147 to 10.8.z branch 2023-01-28 08:22:38 +01:00
Joshua M. Boniface
72c66e91ed Bump version to 10.8.9 2023-01-22 14:09:15 -05:00
Bill Thornton
acb6519ef9 Merge pull request #4288 from dmitrylyzo/fix-es-419
Fix loading Spanish (Latin America) (es-419)
2023-01-19 15:40:35 -05:00
Joshua M. Boniface
0ff86d9ea0 Merge pull request #4274 from dmitrylyzo/fix-hisense-back
Fix back action on Hisense TV
2023-01-19 15:32:51 -05:00
Dmitry Lyzo
b94d14399f fix loading Spanish (Latin America) (es-419) 2023-01-15 20:15:39 +03:00
Dmitry Lyzo
f4c8dd6b1f fix back action on Hisense TV 2023-01-13 14:05:25 +03:00
Bill Thornton
9139153d16 Merge pull request #4263 from dmitrylyzo/fix-change-track 2023-01-12 17:39:02 -05:00
Dmitry Lyzo
0ff3cf321c fix change audio track during playback 2023-01-11 20:17:24 +03:00
Dmitry Lyzo
fe65e0c3b3 refactor: decrease the number of iterations 2023-01-10 23:13:37 +03:00
Dmitry Lyzo
a7bd7e30c6 fix filtering of supported audio tracks 2023-01-10 23:13:37 +03:00
Bill Thornton
ea79d2651a Merge pull request #4267 from dmitrylyzo/fix-audiocontext-limit
Fix AudioContext limit exceeded
2023-01-10 10:00:55 -05:00
Joshua M. Boniface
21a3bae204 Merge pull request #4269 from thornbill/fix-plugin-xss
Fix XSS vulnerability in plugin repo pages
2023-01-09 12:15:18 -05:00
Bill Thornton
4bc0eebee0 Fix XSS vulnerability in plugin repo pages 2023-01-09 11:11:33 -05:00
Dmitry Lyzo
21cf0f5f8e fix AudioContext limit exceeded 2023-01-07 22:36:54 +03:00
Bill Thornton
96234eafb7 Merge pull request #4240 from dmitrylyzo/fix-audiotracks
Fix detection of SecondaryAudio support
2023-01-05 11:19:08 -05:00
Bill Thornton
ae907cd8a6 Merge pull request #4243 from dmitrylyzo/fix-escape-html 2023-01-04 19:41:39 -05:00
Dmitry Lyzo
0ce839c4a8 Fix HTML escaping in MediaSession and on remote page 2023-01-05 01:52:47 +03:00
Dmitry Lyzo
a3e64088b1 Fix detection of SecondaryAudio support 2023-01-05 01:05:27 +03:00
Bill Thornton
255df81375 Merge pull request #4238 from thornbill/fix-router-xss
Fix XSS vulnerability in router
2023-01-04 16:34:45 -05:00
Bill Thornton
5bfffd6209 Merge pull request #4182 from nyanmisaka/fix-progressive-mp4
Fix the progressive mp4 transcoding profile
2023-01-04 16:33:44 -05:00
Bill Thornton
8877e2f758 Fix XSS vulnerability in router 2023-01-04 13:55:25 -05:00
Joshua M. Boniface
b909369127 Bump version to 10.8.8 2022-11-29 13:43:09 -05:00
nyanmisaka
869cbef571 Fix the progressive mp4 transcoding profile
mp4 should be used instead of webm for the maximum compatibility.

Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2022-11-20 20:10:53 +08:00
Bill Thornton
ff4e6a6778 Merge pull request #4172 from thornbill/fix-items-banner-alignment
Fix item details banner image alignment
2022-11-16 22:56:16 -05:00
Bill Thornton
fda6d3c969 Fix item details banner image alignment on mobile 2022-11-15 13:22:52 -05:00
Bill Thornton
8a549eb45b Fix item details banner image alignment 2022-11-15 11:38:34 -05:00
Bill Thornton
4f3ac34739 Merge pull request #4166 from dmitrylyzo/fix-input-navigation 2022-11-10 18:09:08 -05:00
Dmitry Lyzo
36fce00270 Fix keyboard navigation for INPUT and TEXTAREA 2022-11-10 16:31:31 +03:00
Joshua M. Boniface
de8ee44b22 Bump version to 10.8.7 2022-10-31 23:06:37 -04:00
Joshua M. Boniface
1b33cb6f9b Bump version to 10.8.6 2022-10-28 22:44:19 -04:00
Bill Thornton
eb47e5c374 Merge pull request #4117 from thornbill/backport-4094
Backport PR #4094 to 10.8 release
2022-10-28 16:04:23 -04:00
CrispyBaguette
aca8b0ed18 Append new contributor 2022-10-28 15:52:20 -04:00
CrispyBaguette
e516014dc0 Accept arbitrary precision for community rating 2022-10-28 15:52:10 -04:00
Bill Thornton
4c99480c42 Merge pull request #4069 from peterspenler/fix/lazyload-image-intersection 2022-10-26 17:34:47 -04:00
peterspenler
c200a7d2c6 Use isIntersecting instead of intersectionRatio 2022-10-26 23:56:45 +03:00
Bill Thornton
aa009091d5 Merge pull request #4089 from dmitrylyzo/fix-escape-html 2022-10-22 15:38:31 -04:00
Dmitry Lyzo
f81f0ef9d7 fix double escape HTML 2022-10-22 22:31:18 +03:00
Bill Thornton
66baa9e069 Merge pull request #4047 from thornbill/backport-4028 2022-10-14 11:17:51 -04:00
Urtzi Odriozola
375bf86a22 Update displaySettings.template.html
"Euskara" is the native way of naming Basque
2022-10-13 10:31:38 -04:00
Bill Thornton
fb39a56700 Merge pull request #4039 from thornbill/fix-details-card-crop
Fix card cropping on item details page
2022-10-13 10:17:53 -04:00
Bill Thornton
0b9415a041 Fix card cropping on item details page 2022-10-13 00:47:06 -04:00
Bill Thornton
b14d76a3d4 Merge pull request #4005 from dmitrylyzo/fix-last-seen
Fix locale with suffix
2022-10-06 17:04:16 -04:00
Bill Thornton
1263468d49 Merge pull request #4004 from cvium/add_basque_10.8
chore: add Basque display language option
2022-10-06 11:26:41 -04:00
Dmitry Lyzo
fe4ee0c101 fix locale with suffix
"Locale with suffix" is constructed only once with the initial
(browser) locale.
2022-10-06 11:48:15 +03:00
Claus Vium
30df221bbb Update dfnshelper.js 2022-10-06 10:07:23 +02:00
cvium
dfa9b33949 chore: add Basque display language option 2022-10-06 09:42:04 +02:00
Joshua M. Boniface
354157c003 Bump version to 10.8.5 2022-09-24 22:02:30 -04:00
Bill Thornton
72d538e902 Merge pull request #3877 from Callum17/bugfix/release-fix_itemcontextmenu_fails_to_update_for_items_with_no_image_metadata 2022-09-23 23:39:54 -04:00
callum
f1574e0f42 Merge branch 'bugfix/release-fix_itemcontextmenu_fails_to_update_for_items_with_no_image_metadata' of github.com:Callum17/jellyfin-web into bugfix/release-fix_itemcontextmenu_fails_to_update_for_items_with_no_image_metadata 2022-09-10 20:21:46 +01:00
callum
ebfd28d396 Avoid unnecessary DOM update if concurrent played items lack image metadata. 2022-09-10 20:21:37 +01:00
Callum
1861605958 Merge branch 'release-10.8.z' into bugfix/release-fix_itemcontextmenu_fails_to_update_for_items_with_no_image_metadata 2022-09-06 23:35:29 +01:00
Joshua M. Boniface
f85f7d2fe6 Merge pull request #3878 from thornbill/fix-sdk-imports 2022-09-06 01:20:16 -04:00
Bill Thornton
03ce4210af Fix sdk imports to improve build size 2022-09-06 00:43:08 -04:00
callum
689a65cc92 Style. Prefer to use nowPlayingImageUrl to load image in order to make intent clear. 2022-09-04 08:50:20 +01:00
callum
44a5d7bb8d Ensure nowPlayingBar updates correctly when navigating from a song with no image back to a previous song with an image. 2022-09-04 08:50:20 +01:00
callum
072e20b585 Fixed: itemContextMenu opened from bottom media control bar applying operations to the wrong item when items have no image metadata. Can manifest as the wrong song being added to a playlist, deleted, or receiving user supplied metadata, etc. 2022-09-04 08:50:20 +01:00
Bill Thornton
89ec4f4e8a Merge pull request #3849 from dmitrylyzo/fix-cursor-hide 2022-08-24 12:58:33 -04:00
Bill Thornton
89d92e738f Merge pull request #3848 from dmitrylyzo/fix-screensaver 2022-08-24 12:56:45 -04:00
Dmitry Lyzo
c9f4e3c301 Fix unexpected cursor hiding
Hide cursor in case of:
- TV layout
- Active playback
- Active screensaver
2022-08-24 15:56:27 +03:00
Dmitry Lyzo
a80fa25a68 Fix idle function call 2022-08-24 15:55:58 +03:00
Joshua Boniface
bb040b90d1 Bump version to 10.8.4 2022-08-13 21:52:06 -04:00
Bill Thornton
747f7beae7 Merge pull request #3789 from thornbill/fix-cards-xss
Fix XSS in card aria labels
2022-08-02 14:36:36 -04:00
Bill Thornton
eb4159788d Fix XSS in card aria labels 2022-08-02 13:51:20 -04:00
Joshua Boniface
2feaff3648 Bump version to 10.8.3 2022-08-01 20:22:02 -04:00
Joshua M. Boniface
45fe89c26f Bump version to 10.8.2 2022-08-01 14:27:58 -04:00
Bill Thornton
b167bf2d37 Merge pull request #3781 from yahuli/fix-type-error 2022-07-29 18:32:10 -04:00
Bill Thornton
6fe43e45e9 Merge pull request #3778 from nielsvanvelzen/itemdetails-download-missing-args
Add title and filename to download request in item details
2022-07-27 09:41:40 -04:00
dumbfox
d23aa6ada4 Fix TypeError 2022-07-27 10:49:13 +00:00
Niels van Velzen
7936502047 Fix serverId in item details download request
Co-authored-by: Bill Thornton <thornbill@users.noreply.github.com>
2022-07-27 08:59:47 +02:00
Bill Thornton
79c53c6458 Merge pull request #3775 from daullmer/remote-user-config
Fix user specific remote access
2022-07-27 00:15:35 -04:00
Niels van Velzen
5489f34b92 Add title and filename to download request in item details 2022-07-26 22:16:06 +02:00
David Ullmer
5f659b0ef6 Fix user specific remote access 2022-07-23 14:30:41 +02:00
Bill Thornton
db346e4c05 Merge pull request #3760 from nielsvanvelzen/fix-logs-submit
Fix saving log settings not working
2022-07-11 14:28:24 -04:00
Niels van Velzen
b05c7e3309 Fix saving log settings not working 2022-07-10 17:21:59 +02:00
Bill Thornton
8e7a3045dd Merge pull request #3751 from nielsvanvelzen/splashscreen-config-fix
Fix splash screen checkbox always unchecked
2022-07-06 16:28:23 -04:00
Niels van Velzen
7c33579260 Fix splash screen checkbox always unchecked 2022-07-06 13:34:56 +02:00
Bill Thornton
a38d0bbacc Merge pull request #3730 from SenorSmartyPants/CardbuilderEpisodeWithNoName
CardBuilder: Test episode number in addition to episode name
2022-07-02 00:30:31 -04:00
SenorSmartyPants
f4ee7076dd Test episode number in addition to episode name
Display episode number on card if present. Some episodes can have no name, but are still numbered (in my EPG data)
2022-06-28 14:16:09 -05:00
Bill Thornton
42bec6c11e Merge pull request #3729 from SenorSmartyPants/SeriesImageDownload
Save series images under season, when browsing parent images for season
2022-06-28 13:43:36 -04:00
SenorSmartyPants
b93c244e2d Save series images under season, when browsing parent images for season 2022-06-27 14:41:53 -05:00
Joshua M. Boniface
e1ed816a13 Bump version to 10.8.1 2022-06-26 21:00:43 -04:00
Joshua M. Boniface
bc48691738 Merge pull request #3724 from samcon/fix_resume_webos 2022-06-26 20:58:23 -04:00
Joshua M. Boniface
ae83d1d356 Merge pull request #3720 from Shadowghost/device-logo-fix 2022-06-26 20:56:17 -04:00
Joshua M. Boniface
66b86044a9 Merge pull request #3719 from nyanmisaka/dovi-meta 2022-06-26 20:55:44 -04:00
Joshua M. Boniface
d967ce860c Merge pull request #3721 from Shadowghost/fix-stream-autoselect 2022-06-26 20:54:51 -04:00
Joshua M. Boniface
d11b51d0f1 Update src/plugins/htmlVideoPlayer/plugin.js
Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com>
2022-06-26 20:54:12 -04:00
Joshua M. Boniface
0e0dd46c1b Merge pull request #3722 from nyanmisaka/fix-audio-ch 2022-06-26 20:53:47 -04:00
Nyanmisaka
5978d157e7 Remove unnecessary check 2022-06-27 06:23:00 +08:00
Nyanmisaka
ba34384b71 Apply suggestion from code review 2022-06-27 05:54:36 +08:00
Shadowghost
a792737add Use stream defaults in stream auto selection if previous source had no stream of that kind 2022-06-26 23:30:42 +02:00
Shadowghost
22a77ce54e Apply suggestions from code review
Co-authored-by: Dmitry Lyzo <56478732+dmitrylyzo@users.noreply.github.com>
2022-06-26 22:40:28 +02:00
Samcon
cab6e34390 Fix setCurrentTimeIfNeeded calc + change preload to auto to resume in WebOS 2022-06-26 14:37:04 +03:00
Cody Robibero
c20243c8bf Merge pull request #3571 from mihawk90/fedora-spec-rework
Cleanup and standardise Fedora build (web)
2022-06-23 08:06:21 -06:00
nyanmisaka
54e3276ba2 Fix 6ch audio is disabled on AC3 supported browsers 2022-06-23 21:44:18 +08:00
Joshua M. Boniface
f1ff81884a Merge pull request #3717 from dmitrylyzo/resolution 2022-06-20 10:01:01 -04:00
Shadowghost
2b0091eca2 Fix stream selection remembering 2022-06-19 17:42:00 +02:00
Shadowghost
37b4203967 Fix device logos for various clients 2022-06-19 16:34:25 +02:00
Tarulia
08d77664a7 Add make in Fedora Docker install
Fedora 36 doesn't seem to ship make, so add it manually.
2022-06-18 20:29:32 +02:00
nyanmisaka
b0ab4d3e63 Expose DoVi metadata and VideoRangeType 2022-06-18 22:16:10 +08:00
Dmitry Lyzo
f06cd961d5 Add maximum allowed video resolution selector 2022-06-17 22:14:14 +03:00
Bill Thornton
c8590d37ed Merge pull request #3713 from nyanmisaka/video-range-condition
Add VideoRangeType condition for web client
2022-06-17 13:10:36 -04:00
Nyanmisaka
8544bf08ac Merge branch 'release-10.8.z' into video-range-condition 2022-06-18 00:58:08 +08:00
Bill Thornton
6142283e99 Merge pull request #3714 from nyanmisaka/vpp-tm-configs
Expose VPP TM brightness/contrast gain options
2022-06-17 12:52:15 -04:00
Bill Thornton
721bc54dbd Merge pull request #3716 from Orhideous/fix/mimetype
Recognize MIME for transcoded audio in streams
2022-06-17 10:05:22 -04:00
Andriy Kushnir (Orhideous)
f2a826bb5d Left old MIME type for backward compatibility 2022-06-17 13:38:43 +03:00
nyanmisaka
8660f72915 Add translate 2022-06-17 17:06:37 +08:00
Andriy Kushnir (Orhideous)
8d531976a1 Recognize MIME for transcoded audio in streams
Fixes: #3663
See: jellyfin/jellyfin#6941
2022-06-16 22:40:31 +03:00
nyanmisaka
cbfa0acfb3 Expose VPP TM brightness/contrast gain options 2022-06-16 23:55:55 +08:00
nyanmisaka
7195e8a15d Add VideoRangeType condition for HDR content 2022-06-16 21:34:52 +08:00
Tarulia
26ec0e8e4a Use Fedora 36 image in Fedora Docker builds
* fixes #2351
2022-06-16 03:30:47 +02:00
Tarulia
5d5be25008 Move web-files to default location
* when running Jellyfin as a user from a terminal without passing
  arguments, it would not find the web-files. This moves them to the
  expected/default location.
* fixes #2059
2022-06-16 03:29:49 +02:00
Tarulia
390a0edf70 Standardise Fedora spec to packaging guidelines
* move actual building process to %build
* remove AutoReqProv as the package purely contains text files and
  fonts. There's no dependencies to begin with. This feature is also
  intended as sort of a "last resort" and we don't need this here.
* define LICENSE as %license, which automatically puts it in a
  standardised directory
2022-06-16 03:29:49 +02:00
Tarulia
2896cfbdb2 Adjust license in Fedora Spec according to LICENSE 2022-06-16 03:29:49 +02:00
Tarulia
2ff0a67e10 Rewrite Fedora build version detection
Rewrite so we don't need to constantly update with every new Fedora
release. This is especially useful when Fedora and Jellyfin release
cycles don't line up.

Version selection is as follows:
* TARGET environment variable, which is currently used already
* Currently running Fedora version
* Hardcoded Fallback version that can be updated occasionally
2022-06-16 03:29:49 +02:00
Bill Thornton
0bd774dd45 Merge pull request #3694 from crobibero/disable-splashscreen
Add the ability to enable/disable the splashscreen
2022-06-14 10:28:33 -04:00
Bill Thornton
4fdfcde018 Merge pull request #3701 from nyanmisaka/patch-1
Remove MPEG4 hwaccel from AMF
2022-06-14 10:14:00 -04:00
Bill Thornton
eaefc7bea1 Merge pull request #3704 from zhuangzhuang/release-10.8.z
Fix empty  avatar when upload same profile image
2022-06-14 10:12:22 -04:00
zhuangzhuang1988
427bdb2203 Fix empty avatar when upload same profile image
(cherry picked from commit 9cb5b03edc605fb62d0b664dee26df27540ae532)
2022-06-14 09:57:30 +08:00
Nyanmisaka
e8e531ffb2 Remove MPEG4 hwaccel from AMF 2022-06-14 02:19:09 +08:00
Cody Robibero
a3f9f45c54 Add strings 2022-06-12 09:22:58 -06:00
Cody Robibero
801d656d48 Add the ability to enable/disable the splashscreen 2022-06-12 08:50:49 -06:00
Joshua M. Boniface
e727eed1d1 Bump version to 10.8.0 2022-06-10 22:17:34 -04:00
Joshua M. Boniface
0701c4dff3 Merge pull request #3668 from dmitrylyzo/show-play-settings 2022-06-06 11:52:20 -04:00
Bill Thornton
57312e5cd5 Merge pull request #3662 from 1337joe/fix-tv-guide-search-2
Support searching for tv programs
2022-05-25 11:46:16 -04:00
Joe Rogers
07d2537de6 Restrict series/programs to uncategorized items 2022-05-24 18:43:06 +02:00
Bill Thornton
46f31b3f15 Merge pull request #3664 from dmitrylyzo/fix-memory-leak 2022-05-23 08:03:05 -04:00
Dmitry Lyzo
abbc5963f2 Show PlaySettings button even if transcoding isn't supported 2022-05-22 14:55:59 +03:00
Cody Robibero
f1274041ce Merge pull request #3667 from dmitrylyzo/release-lint-job
Enable Lint job on release branches
2022-05-22 05:27:54 -06:00
grafixeyehero
f2c747ce19 Fix lint 2022-05-22 13:57:22 +03:00
Dmitry Lyzo
8f4e87dd1f Enable Lint job on release branches 2022-05-22 13:51:24 +03:00
Dmitry Lyzo
3df8bd8be8 Bump @jellyfin/libass-wasm from 4.1.0 to 4.1.1
Bumps [@jellyfin/libass-wasm](https://github.com/jellyfin/JavascriptSubtitlesOctopus) from 4.1.0 to 4.1.1.
    - [Release notes](https://github.com/jellyfin/JavascriptSubtitlesOctopus/releases)
    - [Commits](https://github.com/jellyfin/JavascriptSubtitlesOctopus/compare/v4.1.0...v4.1.1)
2022-05-21 19:24:49 +03:00
Joe Rogers
46daea5238 Support searching for tv programs 2022-05-21 12:26:35 +02:00
Bill Thornton
1be3d30027 Merge pull request #3658 from thornbill/fix-channel-cards
Add workaround for channel card images
2022-05-20 15:34:01 -04:00
Bill Thornton
6b362fb591 Merge pull request #3657 from dmitrylyzo/unhighlight-play
Remove play button highlighting
2022-05-20 13:33:53 -04:00
Bill Thornton
cd31ae7afe Add workaround for channel card images 2022-05-20 13:26:57 -04:00
Dmitry Lyzo
a3fd4ba62f Remove play button highlighting 2022-05-20 10:32:14 +03:00
Bill Thornton
9b8507706c Merge pull request #3647 from 1337joe/disable_auto_collection
Disable "Automatically add to collection" by default
2022-05-17 11:38:34 -04:00
Joe Rogers
ea0161d132 Disable "Automatically add to collection" by default 2022-05-16 16:47:24 +02:00
Joshua M. Boniface
a36f515b30 Merge pull request #3644 from dmitrylyzo/bump-jso 2022-05-15 22:10:42 -04:00
Joshua M. Boniface
3ed1a9d098 Merge branch 'release-10.8.z' into bump-jso 2022-05-15 20:29:25 -04:00
Joshua M. Boniface
a07d5b7bd1 Merge pull request #3642 from taku0/font-family-by-language 2022-05-15 20:27:54 -04:00
taku0
88e129f793 Change CSS font-family by language preference
Fixes https://github.com/jellyfin/jellyfin-web/issues/913.

- Update `lang` attribute of `html` element on user preference change.
- Choose appropriate `font-family` depending on `lang` attribute using
  attribute selectors in CSS.
- Add `Noto Sans TC`, the Traditional Chinese (Taiwan variant).
- Fix event listener registration in `globalize.js`.
2022-05-16 09:18:06 +09:00
Joshua M. Boniface
4582e6185a Bump version to 10.8.0-beta3 2022-05-15 20:17:00 -04:00
Dmitry Lyzo
3ef4622772 Migrate to @jellyfin/libass-wasm@4.1.0 2022-05-16 01:51:47 +03:00
Bill Thornton
0895163344 Merge pull request #3643 from dmitrylyzo/fix-tizen5 2022-05-15 15:20:46 -04:00
Dmitry Lyzo
44b990e331 Resolve worker URLs
Worker in Tizen 5 doesn't resolve relative path with async request.
2022-05-15 18:10:55 +03:00
Bill Thornton
ae32ece346 Merge pull request #3637 from MinecraftPlaye/add-webp 2022-05-14 10:43:29 -04:00
Patrick Farwick
c929b6946d Add WebP to the page detection list for comics
The WebP image format can be used to store comic pages.
2022-05-14 11:09:19 +00:00
Bill Thornton
ff95eba35f Merge pull request #3639 from rhld16/unused-languages
Add missing languages
2022-05-14 02:26:29 -04:00
Bill Thornton
38ef0e2bf2 Merge pull request #3635 from dmitrylyzo/fix-ssa-font
Fix SSA/ASS missing font
2022-05-14 02:19:13 -04:00
rhld16
a8c20ca35b Add other date-fns valid languages 2022-05-13 19:30:19 +00:00
rhld16
790b2a0b14 Add Welsh (Cymraeg) to Web 2022-05-13 19:30:09 +00:00
Dmitry Lyzo
cf7a93cd80 Add font MIME type 2022-05-13 17:15:06 +03:00
Bill Thornton
d606a2aad5 Merge pull request #3628 from thornbill/actually-fix-artist-albums 2022-05-11 23:13:02 -04:00
Bill Thornton
91c9a26e4d Fix artists album lists 2022-05-11 12:40:42 -04:00
Bill Thornton
add924e35e Merge pull request #3622 from thornbill/restore-external-links
Restore external links on mobile
2022-05-09 10:00:48 -04:00
Bill Thornton
3d901d3680 Restore external links on mobile 2022-05-09 09:30:46 -04:00
Bill Thornton
f2226ee745 Merge pull request #3604 from thornbill/fix-custom-elements
Fix custom element creation
2022-05-06 13:30:10 -04:00
Bill Thornton
f2c27dc1b5 Merge pull request #3618 from thornbill/fix-backdrop-mobile
Fix backdrop being used on large screens in mobile layout
2022-05-05 16:01:34 -04:00
Bill Thornton
d43418bf05 Merge pull request #3617 from thornbill/fix-ipados-icon
Fix iPadOS icon
2022-05-05 16:00:27 -04:00
Bill Thornton
d0ee66c2ce Fix backdrop being used on large screens in mobile layout 2022-05-05 10:38:45 -04:00
Bill Thornton
bd25a4bdab Merge pull request #3616 from thornbill/revert-translations
Revert some poor translations
2022-05-05 10:19:13 -04:00
Bill Thornton
a088f6afb3 Fix iPad app icon 2022-05-05 10:07:28 -04:00
Bill Thornton
bc5c7817a8 Fix custom element creation 2022-05-05 02:12:38 -04:00
Bill Thornton
76ca94094b Merge pull request #3614 from dmitrylyzo/fix-undefined-streaminfo-url
Check undefined streamInfo.url
2022-05-05 01:51:08 -04:00
Bill Thornton
191c6dd678 Revert some poor translations 2022-05-05 01:38:35 -04:00
Dmitry Lyzo
a5163d0be4 fix: Check undefined streamInfo.url
When remuxing and transcoding are disabled and the media
cannot be played direct, `streamInfo.url` is not set.
2022-05-03 21:47:52 +03:00
Bill Thornton
4792631f06 Merge pull request #3543 from Shadowghost/strm-display-fix 2022-04-29 14:09:05 -04:00
Bill Thornton
f7e2f07c05 Merge pull request #3597 from nielsvanvelzen/qc-text-input
Use text input in Quick Connect page
2022-04-27 15:23:47 -04:00
Joshua M. Boniface
bc0288e57f Merge pull request #3601 from thornbill/fix-repositories-xss 2022-04-27 12:37:16 -04:00
Bill Thornton
ee3c4a2681 Fix XSS in repositories list 2022-04-27 10:19:14 -04:00
Niels van Velzen
b00e93dd3d Require at least 6 characters in Quick Connect input 2022-04-27 10:25:00 +02:00
Niels van Velzen
d03aed23c8 Use text input in Quick Connect page 2022-04-26 19:35:57 +02:00
Bill Thornton
9b697ce832 Merge pull request #3577 from thornbill/fix-sd-filter-backport
Fix SD filter state
2022-04-25 10:37:29 -04:00
Bill Thornton
f2b9dc3aaa Fix SD filter state 2022-04-20 00:14:17 -04:00
Joshua M. Boniface
9c9b2721c5 Bump version to 10.8.0-beta2 2022-04-17 15:53:31 -04:00
Bill Thornton
dace55907f Merge pull request #3535 from dmitrylyzo/fix-poster-resume
Fix 'resume' when clicking on item details poster
2022-04-12 16:08:42 -04:00
Bill Thornton
5ede3c8e47 Merge pull request #3547 from thornbill/touching-books
Fix touch events in epub player
2022-04-12 16:08:00 -04:00
Shadowghost
b69b9227c4 Add track sorting to mediainfo and player track selection 2022-04-07 11:37:57 +02:00
Shadowghost
dc956eb48c Restore sort order after jellyfin/jellyfin#7529, allow subtitle selector display whithout video stream 2022-04-06 23:53:42 +02:00
Bill Thornton
5820416ede Fix touch events in epub player 2022-04-05 17:02:17 -04:00
Dmitry Lyzo
e7b80b7fa2 fix: Fix 'resume' when clicking on item details poster
Find the element with 'action'.
2022-03-31 21:05:01 +03:00
Bill Thornton
33b1f039ea Merge pull request #3527 from thornbill/fix-rewatching-next-up
Fix rewatching next up status
2022-03-31 13:58:40 -04:00
Bill Thornton
a514d168bf Fix rewatching in next up checked when disabled 2022-03-31 11:56:55 -04:00
Bill Thornton
847a81afd3 Merge pull request #3525 from whiteowl3/patch-3
Correct Typo
2022-03-31 11:43:25 -04:00
Bill Thornton
ef811e699c Merge pull request #3519 from dmitrylyzo/fix-escapehtml
Escape HTML
2022-03-31 11:42:34 -04:00
whiteowl3
d13ea90c23 Correct Typo 2022-03-29 06:43:12 -04:00
Dmitry Lyzo
9338dd082b fix: Escape HTML 2022-03-29 02:25:54 +03:00
Joshua M. Boniface
b4fce063b0 Bump packaging version to 10.8.0~beta1 2022-03-27 12:12:46 -04:00
107 changed files with 2751 additions and 583 deletions

View File

@@ -2,9 +2,9 @@ name: Lint
on:
push:
branches: [ master ]
branches: [ master, release* ]
pull_request:
branches: [ master ]
branches: [ master, release* ]
jobs:
run-eslint:

View File

@@ -51,6 +51,8 @@
- [GodTamIt](https://github.com/GodTamIt)
- [MinecraftPlaye](https://github.com/MinecraftPlaye)
- [Matthew Jones](https://github.com/matthew-jones-uk)
- [taku0](https://github.com/taku0)
- [Peter Spenler](https://github.com/peterspenler)
# Emby Contributors
@@ -115,3 +117,5 @@
- [Tim Hobbs](https://github.com/timhobbs)
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
- [jomp16](https://github.com/jomp16)
- [Leon de Klerk](https://github.com/leondeklerk)
- [CrispyBaguette](https://github.com/CrispyBaguette)

View File

@@ -1,7 +1,7 @@
---
# We just wrap `build` so this is really it
name: "jellyfin-web"
version: "10.8.0"
version: "10.8.13"
packages:
- debian.all
- fedora.all

84
debian/changelog vendored
View File

@@ -1,12 +1,90 @@
jellyfin-web (10.8.13-1) unstable; urgency=medium
* New upstream version 10.8.13; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.13
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 28 Nov 2023 22:21:29 -0500
jellyfin-web (10.8.12-1) unstable; urgency=medium
* New upstream version 10.8.12; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.12
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 04 Nov 2023 14:42:41 -0400
jellyfin-web (10.8.11-1) unstable; urgency=medium
* New upstream version 10.8.11; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.11
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 23 Sep 2023 21:41:40 -0400
jellyfin-web (10.8.10-1) unstable; urgency=medium
* New upstream version 10.8.10; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.10
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 23 Apr 2023 11:01:33 -0400
jellyfin-web (10.8.9-1) unstable; urgency=medium
* New upstream version 10.8.9; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.9
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 22 Jan 2023 14:09:13 -0500
jellyfin-web (10.8.8-1) unstable; urgency=medium
* New upstream version 10.8.8; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.8
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 29 Nov 2022 13:42:54 -0500
jellyfin-web (10.8.7-1) unstable; urgency=medium
* New upstream version 10.8.7; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.7
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 31 Oct 2022 23:06:34 -0400
jellyfin-web (10.8.6-1) unstable; urgency=medium
* New upstream version 10.8.6; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.6
-- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 28 Oct 2022 22:44:15 -0400
jellyfin-web (10.8.5-1) unstable; urgency=medium
* New upstream version 10.8.5; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.5
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 24 Sep 2022 22:02:26 -0400
jellyfin-web (10.8.4-1) unstable; urgency=medium
* New upstream version 10.8.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.4
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 13 Aug 2022 21:52:04 -0400
jellyfin-web (10.8.3-1) unstable; urgency=medium
* New upstream version 10.8.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.3
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 01 Aug 2022 20:22:00 -0400
jellyfin-web (10.8.2-1) unstable; urgency=medium
* New upstream version 10.8.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.2
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 01 Aug 2022 14:27:56 -0400
jellyfin-web (10.8.1-1) unstable; urgency=medium
* New upstream version 10.8.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.1
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 26 Jun 2022 20:59:39 -0400
jellyfin-web (10.8.0-1) unstable; urgency=medium
* Forthcoming stable release
* New upstream version 10.8.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.0
-- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 04 Dec 2020 21:58:23 -0500
-- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 10 Jun 2022 22:16:40 -0400
jellyfin-web (10.7.0-1) unstable; urgency=medium
* Forthcoming stable release
* New upstream version 10.7.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.0
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 27 Jul 2020 19:13:31 -0400

View File

@@ -1,4 +1,4 @@
FROM fedora:33
FROM fedora:36
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
@@ -11,7 +11,7 @@ ENV IS_DOCKER=YES
# Prepare Fedora environment
RUN dnf update -y \
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs autoconf automake glibc-devel
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs autoconf automake glibc-devel make
# Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora /build.sh

View File

@@ -9,8 +9,12 @@ TARBALL :=$(NAME)-$(subst -,~,$(VERSION)).tar.gz
epel-7-x86_64_repos := https://rpm.nodesource.com/pub_16.x/el/\$$releasever/\$$basearch/
fed_ver := $(shell rpm -E %fedora)
# fallback when not running on Fedora
fed_ver ?= 36
TARGET ?= fedora-$(fed_ver)-x86_64
outdir ?= $(PWD)/$(DIR)/
TARGET ?= fedora-35-x86_64
srpm: $(DIR)/$(SRPM)
tarball: $(DIR)/$(TARBALL)

View File

@@ -1,10 +1,10 @@
%global debug_package %{nil}
Name: jellyfin-web
Version: 10.8.0
Version: 10.8.13
Release: 1%{?dist}
Summary: The Free Software Media System web client
License: GPLv3
License: GPLv2
URL: https://jellyfin.org
# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%%{version}.tar.gz`
Source0: jellyfin-web-%{version}.tar.gz
@@ -17,9 +17,6 @@ BuildRequires: git
BuildRequires: npm
%endif
# Disable Automatic Dependency Processing
AutoReqProv: no
%description
Jellyfin is a free software media system that puts you in control of managing and streaming your media.
@@ -27,27 +24,53 @@ Jellyfin is a free software media system that puts you in control of managing an
%prep
%autosetup -n jellyfin-web-%{version} -b 0
%build
%install
%if 0%{?rhel} > 0 && 0%{?rhel} < 8
# Required for CentOS build
chown root:root -R .
%endif
%build
npm ci --no-audit --unsafe-perm
%{__mkdir} -p %{buildroot}%{_datadir}
mv dist %{buildroot}%{_datadir}/jellyfin-web
%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
%install
%{__mkdir} -p %{buildroot}%{_libdir}/jellyfin/jellyfin-web
%{__cp} -r dist/* %{buildroot}%{_libdir}/jellyfin/jellyfin-web
%files
%defattr(644,root,root,755)
%{_datadir}/jellyfin-web
%{_datadir}/licenses/jellyfin/LICENSE
%{_libdir}/jellyfin/jellyfin-web
%license LICENSE
%changelog
* Fri Dec 04 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- Forthcoming stable release
* Mon Jul 27 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- Forthcoming stable release
* Mon Mar 23 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- Forthcoming stable release
* Tue Nov 28 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.13; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.13
* Sat Nov 04 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.12; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.12
* Sat Sep 23 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.11; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.11
* Sun Apr 23 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.10; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.10
* Sun Jan 22 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.9; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.9
* Tue Nov 29 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.8; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.8
* Mon Oct 31 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.7; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.7
* Fri Oct 28 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.6; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.6
* Sat Sep 24 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.5; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.5
* Sat Aug 13 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.4
* Mon Aug 01 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.3
* Mon Aug 01 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.2
* Sun Jun 26 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.1
* Fri Jun 10 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.8.0

22
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "jellyfin-web",
"version": "10.8.0",
"version": "10.8.13",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2450,6 +2450,11 @@
"resolved": "https://registry.npmjs.org/@fontsource/noto-sans-sc/-/noto-sans-sc-4.5.2.tgz",
"integrity": "sha512-q9a61ND72RZdb4S1SG3w0c8POLS7s7QtfMMbfFG/jlsKxidsBnUS66jAE+h+OhaQCl8FeQrY8yMnHOff/4paFw=="
},
"@fontsource/noto-sans-tc": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/@fontsource/noto-sans-tc/-/noto-sans-tc-4.5.2.tgz",
"integrity": "sha512-K7c/1Uh7SlBdxRBNsYONtC/oPG/6wJ4xsbnYY4Q+XqpwggJkKYB9dmAgdC1XTDHcY9dZX8AO+oH+wyB4rvdI/w=="
},
"@humanwhocodes/config-array": {
"version": "0.9.5",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz",
@@ -2467,6 +2472,11 @@
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"dev": true
},
"@jellyfin/libass-wasm": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@jellyfin/libass-wasm/-/libass-wasm-4.1.1.tgz",
"integrity": "sha512-xQVJw+lZUg4U1TmLS80reBECfPtpCgRF8hhUSvUUQM9g68OvINyUU3K2yqRH+8tomGpghiRaIcr/bUJ83e0veA=="
},
"@jridgewell/resolve-uri": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz",
@@ -5243,9 +5253,9 @@
"dev": true
},
"epubjs": {
"version": "0.3.90",
"resolved": "https://registry.npmjs.org/epubjs/-/epubjs-0.3.90.tgz",
"integrity": "sha512-8S9Zi9aE3QHvkJbq1mJTfaE1++UysKxgeb2GEv3oR9PEsK+Sx3tzxs3QaRselAVPoTlP5gfLyEgp9BQIyAu8lA==",
"version": "0.3.93",
"resolved": "https://registry.npmjs.org/epubjs/-/epubjs-0.3.93.tgz",
"integrity": "sha512-c06pNSdBxcXv3dZSbXAVLE1/pmleRhOT6mXNZo6INKmvuKpYB65MwU/lO7830czCtjIiK9i+KR+3S+p0wtljrw==",
"requires": {
"@types/localforage": "0.0.34",
"@xmldom/xmldom": "^0.7.5",
@@ -8041,10 +8051,6 @@
"resolved": "https://registry.npmjs.org/libarchive.js/-/libarchive.js-1.3.0.tgz",
"integrity": "sha512-EkQfRXt9DhWwj6BnEA2TNpOf4jTnzSTUPGgE+iFxcdNqjktY8GitbDeHnx8qZA0/IukNyyBUR3oQKRdYkO+HFg=="
},
"libass-wasm": {
"version": "git+https://github.com/jellyfin/JavascriptSubtitlesOctopus.git#f4625ac313b318bd5d2e0ae18679ff516370bae6",
"from": "libass-wasm@git+https://github.com/jellyfin/JavascriptSubtitlesOctopus.git#4.0.0-jf-4"
},
"lie": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "jellyfin-web",
"version": "10.8.0",
"version": "10.8.13",
"description": "Web interface for Jellyfin",
"repository": "https://github.com/jellyfin/jellyfin-web",
"license": "GPL-2.0-or-later",
@@ -69,13 +69,15 @@
"@fontsource/noto-sans-jp": "4.5.2",
"@fontsource/noto-sans-kr": "4.5.2",
"@fontsource/noto-sans-sc": "4.5.2",
"@fontsource/noto-sans-tc": "4.5.2",
"@jellyfin/libass-wasm": "4.1.1",
"blurhash": "1.1.4",
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
"classnames": "2.3.1",
"core-js": "3.20.2",
"date-fns": "2.28.0",
"dompurify": "2.3.4",
"epubjs": "0.3.90",
"epubjs": "0.3.93",
"escape-html": "1.0.3",
"fast-text-encoding": "1.0.3",
"flv.js": "1.6.2",
@@ -86,7 +88,6 @@
"jquery": "3.6.0",
"jstree": "3.3.12",
"libarchive.js": "1.3.0",
"libass-wasm": "git+https://github.com/jellyfin/JavascriptSubtitlesOctopus.git#4.0.0-jf-4",
"lodash-es": "4.17.21",
"marked": "4.0.10",
"material-design-icons-iconfont": "6.1.1",

2
src/apiclient.d.ts vendored
View File

@@ -1,7 +1,7 @@
// TODO: Move to jellyfin-apiclient
/* eslint-disable @typescript-eslint/no-explicit-any */
declare module 'jellyfin-apiclient' {
import {
import type {
AllThemeMediaResult,
AuthenticationResult,
BaseItemDto,

View File

@@ -1,18 +1,38 @@
@import "../../styles/noto-sans/index.scss";
@mixin font($weight: null, $size: null) {
font-family: "Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", sans-serif;
font-weight: $weight;
font-size: $size;
}
html {
@include font($size: 93%);
font-family: "Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", "Noto Sans TC", sans-serif;
text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
html[lang|="ja"] {
font-family: "Noto Sans", "Noto Sans JP", "Noto Sans HK", "Noto Sans KR", "Noto Sans SC", "Noto Sans TC", sans-serif;
}
html[lang|="ko"] {
font-family: "Noto Sans", "Noto Sans KR", "Noto Sans HK", "Noto Sans JP", "Noto Sans SC", "Noto Sans TC", sans-serif;
}
html[lang|="zh-CN"] {
font-family: "Noto Sans", "Noto Sans SC", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans TC", sans-serif;
}
html[lang|="zh-TW"] {
font-family: "Noto Sans", "Noto Sans TC", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", sans-serif;
}
html[lang|="zh-HK"] {
font-family: "Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", "Noto Sans TC", sans-serif;
}
h1 {
@include font(400, 1.8em);
}

View File

@@ -439,7 +439,7 @@
.itemBackdrop {
background-size: cover;
background-repeat: no-repeat;
background-position: center 0;
background-position: center center;
background-attachment: fixed;
height: 40vh;
position: relative;
@@ -447,6 +447,7 @@
.layout-mobile & {
background-attachment: initial;
background-position: top center;
margin-top: 3rem;
@media all and (orientation: portrait) and (max-width: 40em) {
@@ -523,10 +524,6 @@
margin: -0.25em 0 0.25em;
}
.layout-mobile .itemExternalLinks {
display: none;
}
.mainDetailButtons {
display: flex;
align-items: center;
@@ -742,6 +739,8 @@
left: 3.3%;
top: -80%;
width: 25vw;
// FIXME: the fixed width + max height cause the card to be cropped this needs a proper fix
max-height: none;
}
.layout-tv & {

View File

@@ -7,3 +7,8 @@
padding-left: 0.5em;
}
}
// FIXME: background sizing for cards really needs revisited, but these are particularly terrible
#channelsTab .cardImageContainer {
background-size: contain;
}

View File

@@ -59,8 +59,6 @@ class AppRouter {
this.baseRoute = this.baseRoute.substring(0, this.baseRoute.length - 1);
}
this.setBaseRoute();
// paths that start with a hashbang (i.e. /#!/page.html) get transformed to starting with //
// we need to strip one "/" for our routes to work
page('//*', (ctx) => {
@@ -68,18 +66,6 @@ class AppRouter {
});
}
/**
* @private
*/
setBaseRoute() {
let baseRoute = window.location.pathname.replace(this.getRequestFile(), '');
if (baseRoute.lastIndexOf('/') === baseRoute.length - 1) {
baseRoute = baseRoute.substring(0, baseRoute.length - 1);
}
console.debug('setting page base to ' + baseRoute);
page.base(baseRoute);
}
addRoute(path, newRoute) {
page(path, this.getHandler(newRoute));
this.allRoutes.push(newRoute);

View File

@@ -39,7 +39,8 @@ function getDeviceProfile(item) {
profile = profileBuilder(builderOpts);
}
const maxTranscodingVideoWidth = appHost.screen()?.maxAllowedWidth;
const maxVideoWidth = appSettings.maxVideoWidth();
const maxTranscodingVideoWidth = maxVideoWidth < 0 ? appHost.screen()?.maxAllowedWidth : maxVideoWidth;
if (maxTranscodingVideoWidth) {
profile.TranscodingProfiles.forEach((transcodingProfile) => {

View File

@@ -820,7 +820,7 @@ import ServerConnections from '../ServerConnections';
if (isUsingLiveTvNaming(item)) {
lines.push(escapeHtml(item.Name));
if (!item.EpisodeTitle) {
if (!item.EpisodeTitle && !item.IndexNumber) {
titleAdded = true;
}
} else {
@@ -949,7 +949,7 @@ import ServerConnections from '../ServerConnections';
}, item.ChannelName));
} else {
lines.push(escapeHtml(item.ChannelName) || '&nbsp;');
lines.push(escapeHtml(item.ChannelName || '') || '&nbsp;');
}
}
@@ -981,7 +981,7 @@ import ServerConnections from '../ServerConnections';
if (item.RecordAnyChannel) {
lines.push(globalize.translate('AllChannels'));
} else {
lines.push(escapeHtml(item.ChannelName) || globalize.translate('OneChannel'));
lines.push(escapeHtml(item.ChannelName || '') || globalize.translate('OneChannel'));
}
}
@@ -1347,7 +1347,7 @@ import ServerConnections from '../ServerConnections';
cardImageContainerClose = '</div>';
} else {
const cardImageContainerAriaLabelAttribute = ` aria-label="${item.Name}"`;
const cardImageContainerAriaLabelAttribute = ` aria-label="${escapeHtml(item.Name)}"`;
// Don't use the IMG tag with safari because it puts a white border around it
cardImageContainerOpen = imgUrl ? ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + cardImageContainerAriaLabelAttribute + '>') : ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction"' + cardImageContainerAriaLabelAttribute + '>');
@@ -1430,7 +1430,7 @@ import ServerConnections from '../ServerConnections';
if (tagName === 'button') {
className += ' itemAction';
actionAttribute = ' data-action="' + action + '"';
ariaLabelAttribute = ` aria-label="${item.Name}"`;
ariaLabelAttribute = ` aria-label="${escapeHtml(item.Name)}"`;
} else {
actionAttribute = '';
}

View File

@@ -1,3 +1,4 @@
import escapeHtml from 'escape-html';
import React, { FunctionComponent } from 'react';
type IProps = {
@@ -17,7 +18,7 @@ const createCheckBoxElement = ({className, Name, dataAttributes, AppName, checke
class="${className}"
${dataAttributes} ${checkedAttribute}
/>
<span>${Name} ${AppName}</span>
<span>${escapeHtml(Name || '')} ${AppName}</span>
</label>`
});

View File

@@ -1,7 +1,7 @@
import { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import type { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import React, { FunctionComponent } from 'react';
import { formatDistanceToNow } from 'date-fns';
import { localeWithSuffix } from '../../../scripts/dfnshelper';
import { getLocaleWithSuffix } from '../../../scripts/dfnshelper';
import globalize from '../../../scripts/globalize';
import cardBuilder from '../../cardbuilder/cardBuilder';
@@ -31,7 +31,7 @@ type IProps = {
const getLastSeenText = (lastActivityDate?: string | null) => {
if (lastActivityDate) {
return globalize.translate('LastSeen', formatDistanceToNow(Date.parse(lastActivityDate), localeWithSuffix));
return globalize.translate('LastSeen', formatDistanceToNow(Date.parse(lastActivityDate), getLocaleWithSuffix()));
}
return '';

View File

@@ -1,4 +1,4 @@
import { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import type { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import React, { FunctionComponent, useCallback, useEffect, useRef } from 'react';
import Dashboard from '../../../scripts/clientUtils';
import globalize from '../../../scripts/globalize';

View File

@@ -43,7 +43,7 @@ function refreshDirectoryBrowser(page, path, fileOptions, updatePathOnError) {
Promise.all(promises).then(
responses => {
const folders = responses[0];
const parentPath = responses[1] || '';
const parentPath = (responses[1] ? JSON.parse(responses[1]) : '') || '';
let html = '';
page.querySelector('.results').scrollTop = 0;
@@ -267,7 +267,7 @@ class DirectoryBrowser {
html += '<div class="formDialogHeader">';
html += `<button is="paper-icon-button-light" class="btnCloseDialog autoSize" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
html += '<h3 class="formDialogHeaderTitle">';
html += escapeHtml(options.header) || globalize.translate('HeaderSelectPath');
html += escapeHtml(options.header || '') || globalize.translate('HeaderSelectPath');
html += '</h3>';
html += '</div>';
html += getEditorHtml(options, systemInfo);

View File

@@ -26,6 +26,7 @@
<option value="es_DO">Español (Dominicana)</option>
<option value="es-MX">Español (México)</option>
<option value="et">Eesti</option>
<option value="eu">Euskara</option>
<option value="fa">فارسی</option>
<option value="fi">Suomi</option>
<option value="fil">Filipino</option>

View File

@@ -87,7 +87,7 @@ import template from './filterdialog.template.html';
context.querySelector('.chk3DFilter').checked = query.Is3D === true;
context.querySelector('.chkHDFilter').checked = query.IsHD === true;
context.querySelector('.chk4KFilter').checked = query.Is4K === true;
context.querySelector('.chkSDFilter').checked = query.IsHD === true;
context.querySelector('.chkSDFilter').checked = query.IsHD === false;
context.querySelector('#chkSubtitle').checked = query.HasSubtitles === true;
context.querySelector('#chkTrailer').checked = query.HasTrailer === true;
context.querySelector('#chkThemeSong').checked = query.HasThemeSong === true;
@@ -272,15 +272,25 @@ import template from './filterdialog.template.html';
triggerChange(this);
});
const chkHDFilter = context.querySelector('.chkHDFilter');
const chkSDFilter = context.querySelector('.chkSDFilter');
chkHDFilter.addEventListener('change', () => {
query.StartIndex = 0;
query.IsHD = chkHDFilter.checked ? true : null;
if (chkHDFilter.checked) {
chkSDFilter.checked = false;
query.IsHD = true;
} else {
query.IsHD = null;
}
triggerChange(this);
});
const chkSDFilter = context.querySelector('.chkSDFilter');
chkSDFilter.addEventListener('change', () => {
query.StartIndex = 0;
query.IsHD = chkSDFilter.checked ? false : null;
if (chkSDFilter.checked) {
chkHDFilter.checked = false;
query.IsHD = false;
} else {
query.IsHD = null;
}
triggerChange(this);
});
for (const elem of context.querySelectorAll('.chkStatus')) {

View File

@@ -72,11 +72,14 @@ import ServerConnections from '../ServerConnections';
promises.push(loadSection(elem, apiClient, user, userSettings, userViews, sections, i));
}
return Promise.all(promises).then(function () {
return resume(elem, {
refresh: true
return Promise.all(promises)
// Timeout for polyfilled CustomElements (webOS 1.2)
.then(() => new Promise((resolve) => setTimeout(resolve, 0)))
.then(() => {
return resume(elem, {
refresh: true
});
});
});
} else {
let noLibDescription;
if (user['Policy'] && user['Policy']['IsAdministrator']) {

View File

@@ -131,7 +131,8 @@ import { Events } from 'jellyfin-apiclient';
}
function setCurrentTimeIfNeeded(element, seconds) {
if (Math.abs(element.currentTime || 0, seconds) <= 1) {
// If it's worth skipping (1 sec or less of a difference)
if (Math.abs((element.currentTime || 0) - seconds) >= 1) {
element.currentTime = seconds;
}
}

View File

@@ -33,10 +33,10 @@ import template from './imageDownloader.template.html';
let selectedProvider;
let browsableParentId;
function getBaseRemoteOptions(page) {
function getBaseRemoteOptions(page, forceCurrentItemId = false) {
const options = {};
if (page.querySelector('#chkShowParentImages').checked && browsableParentId) {
if (!forceCurrentItemId && page.querySelector('#chkShowParentImages').checked && browsableParentId) {
options.itemId = browsableParentId;
} else {
options.itemId = currentItemId;
@@ -140,7 +140,7 @@ import template from './imageDownloader.template.html';
}
function downloadRemoteImage(page, apiClient, url, type, provider) {
const options = getBaseRemoteOptions(page);
const options = getBaseRemoteOptions(page, true);
options.Type = type;
options.ImageUrl = url;

View File

@@ -83,7 +83,7 @@ worker.addEventListener(
source = entry;
}
if (entry.intersectionRatio > 0) {
if (entry.isIntersecting) {
if (source) {
fillImageElement(target, source);
}

View File

@@ -123,7 +123,7 @@ export function canEdit(user, item) {
}
export function isLocalItem(item) {
if (item && item.Id && item.Id.indexOf('local') === 0) {
if (item && item.Id && typeof item.Id === 'string' && item.Id.indexOf('local') === 0) {
return true;
}
@@ -331,6 +331,17 @@ export function supportsMediaSourceSelection (item) {
return true;
}
export function sortTracks (trackA, trackB) {
let cmp = trackA.IsExternal - trackB.IsExternal;
if (cmp != 0) return cmp;
cmp = trackB.IsForced - trackA.IsForced;
if (cmp != 0) return cmp;
cmp = trackB.IsDefault - trackA.IsDefault;
if (cmp != 0) return cmp;
return trackA.Index - trackB.Index;
}
export default {
getDisplayName: getDisplayName,
supportsAddingToCollection: supportsAddingToCollection,
@@ -346,5 +357,6 @@ export default {
canRate: canRate,
canConvert: canConvert,
canRefreshMetadata: canRefreshMetadata,
supportsMediaSourceSelection: supportsMediaSourceSelection
supportsMediaSourceSelection: supportsMediaSourceSelection,
sortTracks: sortTracks
};

View File

@@ -12,6 +12,7 @@ import toast from '../toast/toast';
import { copy } from '../../scripts/clipboard';
import dom from '../../scripts/dom';
import globalize from '../../scripts/globalize';
import itemHelper from '../../components/itemHelper';
import loading from '../loading/loading';
import '../../elements/emby-select/emby-select';
import '../listview/listview.scss';
@@ -71,8 +72,8 @@ const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </s
const size = `${(version.Size / (1024 * 1024)).toFixed(0)} MB`;
html += `${createAttribute(globalize.translate('MediaInfoSize'), size)}<br/>`;
}
for (let i = 0, length = version.MediaStreams.length; i < length; i++) {
const stream = version.MediaStreams[i];
version.MediaStreams.sort(itemHelper.sortTracks);
for (const stream of version.MediaStreams) {
if (stream.Type === 'Data') {
continue;
}
@@ -112,7 +113,7 @@ const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </s
if (stream.Profile) {
attributes.push(createAttribute(globalize.translate('MediaInfoProfile'), stream.Profile));
}
if (stream.Level) {
if (stream.Level > 0) {
attributes.push(createAttribute(globalize.translate('MediaInfoLevel'), stream.Level));
}
if (stream.Width || stream.Height) {
@@ -127,7 +128,7 @@ const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </s
}
attributes.push(createAttribute(globalize.translate('MediaInfoInterlaced'), (stream.IsInterlaced ? 'Yes' : 'No')));
}
if (stream.AverageFrameRate || stream.RealFrameRate) {
if ((stream.AverageFrameRate || stream.RealFrameRate) && stream.Type === 'Video') {
attributes.push(createAttribute(globalize.translate('MediaInfoFramerate'), (stream.AverageFrameRate || stream.RealFrameRate)));
}
if (stream.ChannelLayout) {
@@ -136,7 +137,7 @@ const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </s
if (stream.Channels) {
attributes.push(createAttribute(globalize.translate('MediaInfoChannels'), `${stream.Channels} ch`));
}
if (stream.BitRate && stream.Codec !== 'mjpeg') {
if (stream.BitRate) {
attributes.push(createAttribute(globalize.translate('MediaInfoBitrate'), `${parseInt(stream.BitRate / 1000)} kbps`));
}
if (stream.SampleRate) {
@@ -148,6 +149,36 @@ const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </s
if (stream.VideoRange) {
attributes.push(createAttribute(globalize.translate('MediaInfoVideoRange'), stream.VideoRange));
}
if (stream.VideoRangeType) {
attributes.push(createAttribute(globalize.translate('MediaInfoVideoRangeType'), stream.VideoRangeType));
}
if (stream.VideoDoViTitle) {
attributes.push(createAttribute(globalize.translate('MediaInfoDoViTitle'), stream.VideoDoViTitle));
if (stream.DvVersionMajor != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMajor'), stream.DvVersionMajor));
}
if (stream.DvVersionMinor != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMinor'), stream.DvVersionMinor));
}
if (stream.DvProfile != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoDvProfile'), stream.DvProfile));
}
if (stream.DvLevel != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoDvLevel'), stream.DvLevel));
}
if (stream.RpuPresentFlag != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoRpuPresentFlag'), stream.RpuPresentFlag));
}
if (stream.ElPresentFlag != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoElPresentFlag'), stream.ElPresentFlag));
}
if (stream.BlPresentFlag != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoBlPresentFlag'), stream.BlPresentFlag));
}
if (stream.DvBlSignalCompatibilityId != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoDvBlSignalCompatibilityId'), stream.DvBlSignalCompatibilityId));
}
}
if (stream.ColorSpace) {
attributes.push(createAttribute(globalize.translate('MediaInfoColorSpace'), stream.ColorSpace));
}

View File

@@ -246,7 +246,7 @@ import template from './itemidentifier.template.html';
} else {
html += '<div class="cardText cardText-secondary cardTextCentered">';
}
html += escapeHtml(lines[i]) || '&nbsp;';
html += escapeHtml(lines[i] || '') || '&nbsp;';
html += '</div>';
}

View File

@@ -50,7 +50,7 @@
<div class="checkboxContainer checkboxContainer-withDescription chkAutomaticallyAddToCollectionContainer hide advanced">
<label>
<input is="emby-checkbox" type="checkbox" id="chkAutomaticallyAddToCollection" checked />
<input is="emby-checkbox" type="checkbox" id="chkAutomaticallyAddToCollection" />
<span>${LabelAutomaticallyAddToCollection}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelAutomaticallyAddToCollectionHelp}</div>

View File

@@ -69,7 +69,7 @@
</div>
</div>
<div id="fldCommunityRating" class="hide inputContainer">
<input is="emby-input" id="txtCommunityRating" type="number" step=".1" min="0" max="10" label="${LabelCommunityRating}" />
<input is="emby-input" id="txtCommunityRating" type="number" step="any" min="0" max="10" label="${LabelCommunityRating}" />
</div>
<div id="fldCriticRating" class="hide inputContainer">
<input is="emby-input" id="txtCriticRating" type="number" step=".1" label="${LabelCriticRating}" />

View File

@@ -24,6 +24,7 @@ import { appRouter } from '../appRouter';
let currentTimeElement;
let nowPlayingImageElement;
let nowPlayingImageUrl;
let nowPlayingTextElement;
let nowPlayingUserData;
let muteButton;
@@ -488,7 +489,6 @@ import { appRouter } from '../appRouter';
return null;
}
let currentImgUrl;
function updateNowPlayingInfo(state) {
const nowPlayingItem = state.NowPlayingItem;
@@ -502,14 +502,14 @@ import { appRouter } from '../appRouter';
textLines[1].secondary = true;
if (textLines[1].text) {
const text = document.createElement('a');
text.innerHTML = textLines[1].text;
text.innerText = textLines[1].text;
secondaryText.appendChild(text);
}
}
if (textLines[0].text) {
const text = document.createElement('a');
text.innerHTML = textLines[0].text;
text.innerText = textLines[0].text;
itemText.appendChild(text);
}
nowPlayingTextElement.appendChild(itemText);
@@ -524,17 +524,14 @@ import { appRouter } from '../appRouter';
height: imgHeight
})) : null;
let isRefreshing = false;
if (url !== currentImgUrl) {
currentImgUrl = url;
isRefreshing = true;
if (url !== nowPlayingImageUrl) {
if (url) {
imageLoader.lazyImage(nowPlayingImageElement, url);
nowPlayingImageUrl = url;
imageLoader.lazyImage(nowPlayingImageElement, nowPlayingImageUrl);
nowPlayingImageElement.style.display = null;
nowPlayingTextElement.style.marginLeft = null;
} else {
nowPlayingImageUrl = null;
nowPlayingImageElement.style.backgroundImage = '';
nowPlayingImageElement.style.display = 'none';
nowPlayingTextElement.style.marginLeft = '1em';
@@ -542,36 +539,34 @@ import { appRouter } from '../appRouter';
}
if (nowPlayingItem.Id) {
if (isRefreshing) {
const apiClient = ServerConnections.getApiClient(nowPlayingItem.ServerId);
apiClient.getItem(apiClient.getCurrentUserId(), nowPlayingItem.Id).then(function (item) {
const userData = item.UserData || {};
const 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
const contextButtonClone = contextButton.cloneNode(true);
contextButton.parentNode.replaceChild(contextButtonClone, contextButton);
contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu');
const options = {
play: false,
queue: false,
stopPlayback: true,
clearQueue: true,
positionTo: contextButton
};
apiClient.getCurrentUser().then(function (user) {
contextButton.addEventListener('click', function () {
itemContextMenu.show(Object.assign({
item: item,
user: user
}, options));
});
const apiClient = ServerConnections.getApiClient(nowPlayingItem.ServerId);
apiClient.getItem(apiClient.getCurrentUserId(), nowPlayingItem.Id).then(function (item) {
const userData = item.UserData || {};
const 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
const contextButtonClone = contextButton.cloneNode(true);
contextButton.parentNode.replaceChild(contextButtonClone, contextButton);
contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu');
const options = {
play: false,
queue: false,
stopPlayback: true,
clearQueue: true,
positionTo: contextButton
};
apiClient.getCurrentUser().then(function (user) {
contextButton.addEventListener('click', function () {
itemContextMenu.show(Object.assign({
item: item,
user: user
}, options));
});
}
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" aria-hidden="true"></span></button>';
});
}
});
}
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" aria-hidden="true"></span></button>';
});
} else {
nowPlayingUserData.innerHTML = '';
}

View File

@@ -1,4 +1,4 @@
import { SyncPlayUserAccessType, UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import type { SyncPlayUserAccessType, UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react';
import Dashboard from '../../scripts/clientUtils';
import globalize from '../../scripts/globalize';
@@ -263,7 +263,7 @@ const UserEditPage: FunctionComponent = () => {
}
});
window.ApiClient.getServerConfiguration().then(function (config) {
window.ApiClient.getNamedConfiguration('network').then(function (config) {
const fldRemoteAccess = page.querySelector('.fldRemoteAccess') as HTMLDivElement;
config.EnableRemoteAccess ? fldRemoteAccess.classList.remove('hide') : fldRemoteAccess.classList.add('hide');
});

View File

@@ -1,4 +1,4 @@
import { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import type { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react';
import loading from '../loading/loading';

View File

@@ -1,4 +1,5 @@
import { AccessSchedule, DynamicDayOfWeek, UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import type { AccessSchedule, UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import { DynamicDayOfWeek } from '@thornbill/jellyfin-sdk/dist/generated-client/models/dynamic-day-of-week';
import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react';
import globalize from '../../scripts/globalize';
import LibraryMenu from '../../scripts/libraryMenu';

View File

@@ -1,4 +1,5 @@
import { ImageType, UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import type { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import { ImageType } from '@thornbill/jellyfin-sdk/dist/generated-client/models/image-type';
import React, { FunctionComponent, useEffect, useState, useRef, useCallback } from 'react';
import Dashboard from '../../scripts/clientUtils';
@@ -134,7 +135,9 @@ const UserProfilePage: FunctionComponent<IProps> = ({userId}: IProps) => {
});
(page.querySelector('.btnAddImage') as HTMLButtonElement).addEventListener('click', function () {
(page.querySelector('#uploadImage') as HTMLInputElement).click();
const uploadImage = page.querySelector('#uploadImage') as HTMLInputElement;
uploadImage.value = '';
uploadImage.click();
});
(page.querySelector('#uploadImage') as HTMLInputElement).addEventListener('change', function (evt: Event) {

View File

@@ -1,4 +1,4 @@
import { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import type { UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import React, {FunctionComponent, useEffect, useState, useRef} from 'react';
import Dashboard from '../../scripts/clientUtils';
import globalize from '../../scripts/globalize';

View File

@@ -1,5 +1,3 @@
import escapeHtml from 'escape-html';
export function getNowPlayingNames(nowPlayingItem, includeNonNameInfo) {
let topItem = nowPlayingItem;
let bottomItem = null;
@@ -61,13 +59,13 @@ export function getNowPlayingNames(nowPlayingItem, includeNonNameInfo) {
const list = [];
list.push({
text: escapeHtml(topText),
text: topText,
item: topItem
});
if (bottomText) {
list.push({
text: escapeHtml(bottomText),
text: bottomText,
item: bottomItem
});
}

View File

@@ -11,6 +11,7 @@ import { appHost } from '../apphost';
import Screenfull from 'screenfull';
import ServerConnections from '../ServerConnections';
import alert from '../alert';
import { includesAny } from '../../utils/container.ts';
const UNLIMITED_ITEMS = -1;
@@ -1306,6 +1307,7 @@ class PlaybackManager {
return false;
}
const container = mediaSource.Container.toLowerCase();
const codec = (mediaStream.Codec || '').toLowerCase();
if (!codec) {
@@ -1314,22 +1316,11 @@ class PlaybackManager {
const profiles = deviceProfile.DirectPlayProfiles || [];
return profiles.filter(function (p) {
if (p.Type === 'Video') {
if (!p.AudioCodec) {
return true;
}
// This is an exclusion filter
if (p.AudioCodec.indexOf('-') === 0) {
return p.AudioCodec.toLowerCase().indexOf(codec) === -1;
}
return p.AudioCodec.toLowerCase().indexOf(codec) !== -1;
}
return false;
}).length > 0;
return profiles.some(function (p) {
return p.Type === 'Video'
&& includesAny((p.Container || '').toLowerCase(), container)
&& includesAny((p.AudioCodec || '').toLowerCase(), codec);
});
}
self.setAudioStreamIndex = function (index, player) {
@@ -1686,6 +1677,7 @@ class PlaybackManager {
const streamInfo = createStreamInfo(apiClient, currentItem.MediaType, currentItem, currentMediaSource, ticks, player);
streamInfo.fullscreen = currentPlayOptions.fullscreen;
streamInfo.lastMediaInfoQuery = lastMediaInfoQuery;
streamInfo.resetSubtitleOffset = false;
if (!streamInfo.url) {
showPlaybackInfoErrorMessage(self, 'PlaybackErrorNoCompatibleStream');
@@ -1742,6 +1734,8 @@ class PlaybackManager {
}
function translateItemsForPlayback(items, options) {
if (!items.length) return Promise.resolve([]);
if (items.length > 1 && options && options.ids) {
// Use the original request id array for sorting the result in the proper order
items.sort(function (a, b) {
@@ -2275,7 +2269,7 @@ class PlaybackManager {
score += 1;
if (prevRelIndex == newRelIndex)
score += 1;
if (prevStream.Title && prevStream.Title == stream.Title)
if (prevStream.DisplayTitle && prevStream.DisplayTitle == stream.DisplayTitle)
score += 2;
if (prevStream.Language && prevStream.Language != 'und' && prevStream.Language == stream.Language)
score += 2;
@@ -2300,7 +2294,7 @@ class PlaybackManager {
}
}
function autoSetNextTracks(prevSource, mediaSource) {
function autoSetNextTracks(prevSource, mediaSource, audio, subtitle) {
try {
if (!prevSource) return;
@@ -2309,18 +2303,13 @@ class PlaybackManager {
return;
}
if (typeof prevSource.DefaultAudioStreamIndex != 'number'
|| typeof prevSource.DefaultSubtitleStreamIndex != 'number')
return;
if (typeof mediaSource.DefaultAudioStreamIndex != 'number'
|| typeof mediaSource.DefaultSubtitleStreamIndex != 'number') {
console.warn('AutoSet - No stream indexes (but prevSource has them)');
return;
if (audio && typeof prevSource.DefaultAudioStreamIndex == 'number') {
rankStreamType(prevSource.DefaultAudioStreamIndex, prevSource, mediaSource, 'Audio');
}
rankStreamType(prevSource.DefaultAudioStreamIndex, prevSource, mediaSource, 'Audio');
rankStreamType(prevSource.DefaultSubtitleStreamIndex, prevSource, mediaSource, 'Subtitle');
if (subtitle && typeof prevSource.DefaultSubtitleStreamIndex == 'number') {
rankStreamType(prevSource.DefaultSubtitleStreamIndex, prevSource, mediaSource, 'Subtitle');
}
} catch (e) {
console.error(`AutoSet - Caught unexpected error: ${e}`);
}
@@ -2384,9 +2373,9 @@ class PlaybackManager {
// this reference was only needed by sendPlaybackListToPlayer
playOptions.items = null;
return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(function (mediaSource) {
if (userSettings.enableSetUsingLastTracks())
autoSetNextTracks(prevSource, mediaSource);
return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(async (mediaSource) => {
const user = await apiClient.getCurrentUser();
autoSetNextTracks(prevSource, mediaSource, user.Configuration.RememberAudioSelections, user.Configuration.RememberSubtitleSelections);
const streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition, player);
@@ -3034,7 +3023,7 @@ class PlaybackManager {
const streamInfo = error.streamInfo || getPlayerData(player).streamInfo;
if (streamInfo) {
if (streamInfo?.url) {
const currentlyPreventsVideoStreamCopy = streamInfo.url.toLowerCase().indexOf('allowvideostreamcopy=false') !== -1;
const currentlyPreventsAudioStreamCopy = streamInfo.url.toLowerCase().indexOf('allowaudiostreamcopy=false') !== -1;
@@ -3657,7 +3646,7 @@ class PlaybackManager {
if (player.audioTracks) {
const result = player.audioTracks();
if (result) {
return result;
return result.sort(itemHelper.sortTracks);
}
}
@@ -3666,14 +3655,14 @@ class PlaybackManager {
const mediaStreams = (mediaSource || {}).MediaStreams || [];
return mediaStreams.filter(function (s) {
return s.Type === 'Audio';
});
}).sort(itemHelper.sortTracks);
}
subtitleTracks(player = this._currentPlayer) {
if (player.subtitleTracks) {
const result = player.subtitleTracks();
if (result) {
return result;
return result.sort(itemHelper.sortTracks);
}
}
@@ -3682,7 +3671,7 @@ class PlaybackManager {
const mediaStreams = (mediaSource || {}).MediaStreams || [];
return mediaStreams.filter(function (s) {
return s.Type === 'Subtitle';
});
}).sort(itemHelper.sortTracks);
}
getSupportedCommands(player) {

View File

@@ -199,7 +199,8 @@ function showWithUser(options, player, user) {
});
}
if (user && user.Policy.EnableVideoPlaybackTranscoding) {
if (options.quality && supportedCommands.includes('SetMaxStreamingBitrate')
&& user?.Policy?.EnableVideoPlaybackTranscoding) {
const secondaryQualityText = getQualitySecondaryText(player);
menuItems.push({

View File

@@ -1,4 +1,3 @@
import browser from '../../scripts/browser';
import appSettings from '../../scripts/settings/appSettings';
import { appHost } from '../apphost';
import focusManager from '../focusManager';
@@ -41,7 +40,7 @@ import template from './playbackSettings.template.html';
select.innerHTML = html;
}
function setMaxBitrateIntoField(select, isInNetwork, mediatype) {
function fillQuality(select, isInNetwork, mediatype, maxVideoWidth) {
const options = mediatype === 'Audio' ? qualityoptions.getAudioQualityOptions({
currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype),
@@ -52,7 +51,8 @@ import template from './playbackSettings.template.html';
currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype),
isAutomaticBitrateEnabled: appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype),
enableAuto: true
enableAuto: true,
maxVideoWidth
});
@@ -60,6 +60,10 @@ import template from './playbackSettings.template.html';
// render empty string instead of 0 for the auto option
return `<option value="${i.bitrate || ''}">${i.name}</option>`;
}).join('');
}
function setMaxBitrateIntoField(select, isInNetwork, mediatype) {
fillQuality(select, isInNetwork, mediatype);
if (appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype)) {
select.value = '';
@@ -68,12 +72,13 @@ import template from './playbackSettings.template.html';
}
}
function fillChromecastQuality(select) {
function fillChromecastQuality(select, maxVideoWidth) {
const options = qualityoptions.getVideoQualityOptions({
currentMaxBitrate: appSettings.maxChromecastBitrate(),
isAutomaticBitrateEnabled: !appSettings.maxChromecastBitrate(),
enableAuto: true
enableAuto: true,
maxVideoWidth
});
select.innerHTML = options.map(i => {
@@ -133,15 +138,6 @@ import template from './playbackSettings.template.html';
});
}
function showOrHideEpisodesField(context) {
if (browser.tizen || browser.web0s) {
context.querySelector('.fldEpisodeAutoPlay').classList.add('hide');
return;
}
context.querySelector('.fldEpisodeAutoPlay').classList.remove('hide');
}
function loadForm(context, user, userSettings, apiClient) {
const loggedInUserId = apiClient.getCurrentUserId();
const userId = user.Id;
@@ -180,7 +176,8 @@ import template from './playbackSettings.template.html';
context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer();
context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode();
context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay();
context.querySelector('.chkSetUsingLastTracks').checked = userSettings.enableSetUsingLastTracks();
context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false;
context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false;
context.querySelector('.chkExternalVideoPlayer').checked = appSettings.enableSystemExternalPlayers();
setMaxBitrateIntoField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video');
@@ -192,6 +189,9 @@ import template from './playbackSettings.template.html';
const selectChromecastVersion = context.querySelector('.selectChromecastVersion');
selectChromecastVersion.value = userSettings.chromecastVersion();
const selectLabelMaxVideoWidth = context.querySelector('.selectLabelMaxVideoWidth');
selectLabelMaxVideoWidth.value = appSettings.maxVideoWidth();
const selectSkipForwardLength = context.querySelector('.selectSkipForwardLength');
fillSkipLengths(selectSkipForwardLength);
selectSkipForwardLength.value = userSettings.skipForwardLength();
@@ -200,8 +200,6 @@ import template from './playbackSettings.template.html';
fillSkipLengths(selectSkipBackLength);
selectSkipBackLength.value = userSettings.skipBackLength();
showOrHideEpisodesField(context);
loading.hide();
}
@@ -209,6 +207,7 @@ import template from './playbackSettings.template.html';
appSettings.enableSystemExternalPlayers(context.querySelector('.chkExternalVideoPlayer').checked);
appSettings.maxChromecastBitrate(context.querySelector('.selectChromecastVideoQuality').value);
appSettings.maxVideoWidth(context.querySelector('.selectLabelMaxVideoWidth').value);
setMaxBitrateFromField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video');
setMaxBitrateFromField(context.querySelector('.selectVideoInternetQuality'), false, 'Video');
@@ -222,7 +221,8 @@ import template from './playbackSettings.template.html';
userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked);
userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked);
userSettingsInstance.enableSetUsingLastTracks(context.querySelector('.chkSetUsingLastTracks').checked);
user.Configuration.RememberAudioSelections = context.querySelector('.chkRememberAudioSelections').checked;
user.Configuration.RememberSubtitleSelections = context.querySelector('.chkRememberSubtitleSelections').checked;
userSettingsInstance.chromecastVersion(context.querySelector('.selectChromecastVersion').value);
userSettingsInstance.skipForwardLength(context.querySelector('.selectSkipForwardLength').value);
userSettingsInstance.skipBackLength(context.querySelector('.selectSkipBackLength').value);
@@ -247,6 +247,36 @@ import template from './playbackSettings.template.html';
});
}
function setSelectValue(select, value, defaultValue) {
select.value = value;
if (select.selectedIndex < 0) {
select.value = defaultValue;
}
}
function onMaxVideoWidthChange(e) {
const context = this.options.element;
const selectVideoInNetworkQuality = context.querySelector('.selectVideoInNetworkQuality');
const selectVideoInternetQuality = context.querySelector('.selectVideoInternetQuality');
const selectChromecastVideoQuality = context.querySelector('.selectChromecastVideoQuality');
const selectVideoInNetworkQualityValue = selectVideoInNetworkQuality.value;
const selectVideoInternetQualityValue = selectVideoInternetQuality.value;
const selectChromecastVideoQualityValue = selectChromecastVideoQuality.value;
const maxVideoWidth = parseInt(e.target.value || '0', 10) || 0;
fillQuality(selectVideoInNetworkQuality, true, 'Video', maxVideoWidth);
fillQuality(selectVideoInternetQuality, false, 'Video', maxVideoWidth);
fillChromecastQuality(selectChromecastVideoQuality, maxVideoWidth);
setSelectValue(selectVideoInNetworkQuality, selectVideoInNetworkQualityValue, '');
setSelectValue(selectVideoInternetQuality, selectVideoInternetQualityValue, '');
setSelectValue(selectChromecastVideoQuality, selectChromecastVideoQualityValue, '');
}
function onSubmit(e) {
const self = this;
const apiClient = ServerConnections.getApiClient(self.options.serverId);
@@ -274,6 +304,8 @@ import template from './playbackSettings.template.html';
options.element.querySelector('.btnSave').classList.remove('hide');
}
options.element.querySelector('.selectLabelMaxVideoWidth').addEventListener('change', onMaxVideoWidthChange.bind(self));
self.loadData();
if (options.autoFocus) {

View File

@@ -41,6 +41,19 @@
<div class="selectContainer fldChromecastQuality hide">
<select is="emby-select" class="selectChromecastVideoQuality" label="${LabelMaxChromecastBitrate}"></select>
</div>
<div class="selectContainer">
<select is="emby-select" class="selectLabelMaxVideoWidth" label="${LabelMaxVideoResolution}">
<option value="0">${Auto}</option>
<option value="-1">${ScreenResolution}</option>
<option value="640">360p</option>
<option value="852">480p</option>
<option value="1280">720p</option>
<option value="1920">1080p</option>
<option value="3840">4K</option>
<option value="7680">8K</option>
</select>
</div>
</div>
<div class="verticalSection verticalSection-extrabottompadding musicQualitySection hide">
@@ -75,7 +88,7 @@
<div class="fieldDescription checkboxFieldDescription">${CinemaModeConfigurationHelp}</div>
</div>
<div class="checkboxContainer fldEpisodeAutoPlay hide">
<div class="checkboxContainer fldEpisodeAutoPlay">
<label>
<input type="checkbox" is="emby-checkbox" class="chkEpisodeAutoPlay" />
<span>${PlayNextEpisodeAutomatically}</span>
@@ -84,10 +97,18 @@
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" class="chkSetUsingLastTracks" />
<span>${SetUsingLastTracks}</span>
<input type="checkbox" is="emby-checkbox" class="chkRememberAudioSelections" />
<span>${RememberAudioSelections}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${SetUsingLastTracksHelp}</div>
<div class="fieldDescription checkboxFieldDescription">${RememberAudioSelectionsHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" class="chkRememberSubtitleSelections" />
<span>${RememberSubtitleSelections}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${RememberSubtitleSelectionsHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldEnableNextVideoOverlay">

View File

@@ -269,31 +269,10 @@ import ServerConnections from '../ServerConnections';
});
}
if (videoStream.VideoRange) {
if (videoStream.VideoRangeType) {
sessionStats.push({
label: globalize.translate('LabelVideoRange'),
value: videoStream.VideoRange
});
}
if (videoStream.ColorSpace) {
sessionStats.push({
label: globalize.translate('LabelColorSpace'),
value: videoStream.ColorSpace
});
}
if (videoStream.ColorTransfer) {
sessionStats.push({
label: globalize.translate('LabelColorTransfer'),
value: videoStream.ColorTransfer
});
}
if (videoStream.ColorPrimaries) {
sessionStats.push({
label: globalize.translate('LabelColorPrimaries'),
value: videoStream.ColorPrimaries
label: globalize.translate('LabelVideoRangeType'),
value: videoStream.VideoRangeType
});
}

View File

@@ -1,5 +1,6 @@
import { appHost } from '../components/apphost';
import globalize from '../scripts/globalize';
import appSettings from '../scripts/settings/appSettings';
export function getVideoQualityOptions(options) {
const maxStreamingBitrate = options.currentMaxBitrate;
@@ -12,7 +13,9 @@ export function getVideoQualityOptions(options) {
videoWidth = videoHeight * (16 / 9);
}
const hostScreenWidth = appHost.screen()?.maxAllowedWidth || 4096;
const maxVideoWidth = options.maxVideoWidth == null ? appSettings.maxVideoWidth() : options.maxVideoWidth;
const hostScreenWidth = (maxVideoWidth < 0 ? appHost.screen()?.maxAllowedWidth : maxVideoWidth) || 4096;
const maxAllowedWidth = videoWidth || 4096;
const qualityOptions = [];

View File

@@ -13,9 +13,12 @@ import * as userSettings from '../../scripts/settings/userSettings';
import cardBuilder from '../cardbuilder/cardBuilder';
import itemContextMenu from '../itemContextMenu';
import '../cardbuilder/card.scss';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-itemscontainer/emby-itemscontainer';
import './remotecontrol.scss';
import '../../elements/emby-ratingbutton/emby-ratingbutton';
import '../../elements/emby-slider/emby-slider';
import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import { appRouter } from '../appRouter';
@@ -145,9 +148,7 @@ function updateNowPlayingInfo(context, state, serverId) {
if (item.Artists != null) {
if (item.ArtistItems != null) {
for (const artist of item.ArtistItems) {
const artistName = escapeHtml(artist.Name);
const artistId = artist.Id;
artistsSeries += `<a class="button-link emby-button" is="emby-linkbutton" href="#!/details?id=${artistId}&serverId=${nowPlayingServerId}">${escapeHtml(artistName)}</a>`;
artistsSeries += `<a class="button-link emby-button" is="emby-linkbutton" href="#!/details?id=${artist.Id}&serverId=${nowPlayingServerId}">${escapeHtml(artist.Name)}</a>`;
if (artist !== item.ArtistItems.slice(-1)[0]) {
artistsSeries += ', ';
}

View File

@@ -1,4 +1,4 @@
import { BaseItemDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import type { BaseItemDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import classNames from 'classnames';
import { ApiClient } from 'jellyfin-apiclient';
import React, { FunctionComponent, useEffect, useState } from 'react';
@@ -54,7 +54,6 @@ const LiveTVSearchResults: FunctionComponent<LiveTVSearchResultsProps> = ({ serv
IncludeArtists: false
});
// FIXME: This query does not support Live TV filters
const fetchItems = (apiClient: ApiClient, params = {}) => apiClient?.getItems(
apiClient?.getCurrentUserId(),
{
@@ -79,11 +78,7 @@ const LiveTVSearchResults: FunctionComponent<LiveTVSearchResultsProps> = ({ serv
// Movies row
fetchItems(apiClient, {
IncludeItemTypes: 'LiveTvProgram',
IsMovie: true,
IsSeries: false,
IsSports: false,
IsKids: false,
IsNews: false
IsMovie: true
}).then(result => setMovies(result.Items || []));
// Episodes row
fetchItems(apiClient, {
@@ -97,28 +92,16 @@ const LiveTVSearchResults: FunctionComponent<LiveTVSearchResultsProps> = ({ serv
// Sports row
fetchItems(apiClient, {
IncludeItemTypes: 'LiveTvProgram',
IsMovie: false,
IsSeries: false,
IsSports: true,
IsKids: false,
IsNews: false
IsSports: true
}).then(result => setSports(result.Items || []));
// Kids row
fetchItems(apiClient, {
IncludeItemTypes: 'LiveTvProgram',
IsMovie: false,
IsSeries: false,
IsSports: false,
IsKids: true,
IsNews: false
IsKids: true
}).then(result => setKids(result.Items || []));
// News row
fetchItems(apiClient, {
IncludeItemTypes: 'LiveTvProgram',
IsMovie: false,
IsSeries: false,
IsSports: false,
IsKids: false,
IsNews: true
}).then(result => setNews(result.Items || []));
// Programs row

View File

@@ -1,4 +1,4 @@
import { BaseItemDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import type { BaseItemDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import classNames from 'classnames';
import { ApiClient } from 'jellyfin-apiclient';
import React, { FunctionComponent, useEffect, useState } from 'react';

View File

@@ -1,4 +1,4 @@
import { BaseItemDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import type { BaseItemDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import React, { FunctionComponent, useEffect, useRef } from 'react';
import cardBuilder from '../cardbuilder/cardBuilder';

View File

@@ -1,4 +1,4 @@
import { BaseItemDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import type { BaseItemDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import escapeHtml from 'escape-html';
import React, { FunctionComponent, useEffect, useState } from 'react';

View File

@@ -54,7 +54,7 @@ function init(instance) {
playbackManager.setSubtitleOffset(inputOffset, player);
// synchronize with slider value
subtitleSyncSlider.updateOffset(
getPercentageFromOffset(inputOffset));
getSliderValueFromOffset(inputOffset));
} else {
this.textContent = (playbackManager.getPlayerSubtitleOffset(player) || 0) + 's';
}
@@ -79,17 +79,17 @@ function init(instance) {
}
};
subtitleSyncSlider.updateOffset = function (percent) {
// default value is 0s = 50%
this.value = percent === undefined ? 50 : percent;
subtitleSyncSlider.updateOffset = function (sliderValue) {
// default value is 0s = 0ms
this.value = sliderValue === undefined ? 0 : sliderValue;
};
subtitleSyncSlider.addEventListener('change', function () {
// set new offset
playbackManager.setSubtitleOffset(getOffsetFromPercentage(this.value), player);
playbackManager.setSubtitleOffset(getOffsetFromSliderValue(this.value), player);
// synchronize with textField value
subtitleSyncTextField.updateOffset(
getOffsetFromPercentage(this.value));
getOffsetFromSliderValue(this.value));
});
subtitleSyncSlider.getBubbleHtml = function (value) {
@@ -108,20 +108,22 @@ function init(instance) {
}
function getOffsetFromPercentage(value) {
// convert percent to fraction
// convert percentage to fraction
let offset = (value - 50) / 50;
// multiply by offset min/max range value (-x to +x) :
offset *= 30;
return offset.toFixed(1);
}
function getPercentageFromOffset(value) {
// divide by offset min/max range value (-x to +x) :
let percentValue = value / 30;
// convert fraction to percent
percentValue *= 50;
percentValue += 50;
return Math.min(100, Math.max(0, percentValue.toFixed(1)));
function getOffsetFromSliderValue(value) {
// convert slider value to offset
const offset = value / 10;
return offset.toFixed(1);
}
function getSliderValueFromOffset(value) {
const sliderValue = value * 10;
return Math.min(300, Math.max(-300, sliderValue.toFixed(1)));
}
class SubtitleSync {
@@ -152,8 +154,8 @@ class SubtitleSync {
if (playbackManager.isShowingSubtitleOffsetEnabled(player) && playbackManager.canHandleOffsetOnCurrentSubtitle(player)) {
// if no subtitle offset is defined or element has focus (offset being defined)
if (!(playbackManager.getPlayerSubtitleOffset(player) || subtitleSyncTextField.hasFocus)) {
// set default offset to '0' = 50%
subtitleSyncSlider.value = '50';
// set default offset to '0' = 0ms
subtitleSyncSlider.value = '0';
subtitleSyncTextField.textContent = '0s';
playbackManager.setSubtitleOffset(0, player);
}

View File

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

View File

@@ -1,8 +1,10 @@
import escapeHtml from 'escape-html';
import dialogHelper from '../../components/dialogHelper/dialogHelper';
import ServerConnections from '../ServerConnections';
import dom from '../../scripts/dom';
import loading from '../../components/loading/loading';
import scrollHelper from '../../libraries/scroller';
import scrollHelper from '../../scripts/scrollHelper';
import layoutManager from '../layoutManager';
import globalize from '../../scripts/globalize';
import template from './subtitleuploader.template.html';
@@ -61,7 +63,7 @@ function setFiles(page, files) {
reader.onload = (function (theFile) {
return function () {
// Render file.
const html = '<a><span class="material-icons subtitles" aria-hidden="true" style="transform: translateY(25%);"></span><span>' + escape(theFile.name) + '</span><a/>';
const html = `<div><span class="material-icons subtitles" aria-hidden="true" style="transform: translateY(25%);"></span><span>${escapeHtml(theFile.name)}</span></div>`;
page.querySelector('#subtitleOutput').innerHTML = html;
page.querySelector('#fldUpload').classList.remove('hide');

View File

@@ -33,7 +33,7 @@
</label>
</div>
<div class="selectContainer flex-grow">
<select is="emby-select" id="selectLanguage" required="required" label="${LabelLanguage}" autofocus></select>
<select is="emby-select" id="selectLanguage" required="required" label="${LabelLanguage}"></select>
</div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Upload}</span>

View File

@@ -108,7 +108,7 @@ function renderDevices(view, devices) {
function discoverDevices(view) {
loading.show();
view.querySelector('.loadingContent').classList.remove('hide');
return ApiClient.getJSON(ApiClient.getUrl('LiveTv/Tuners/Discvover', {
return ApiClient.getJSON(ApiClient.getUrl('LiveTv/Tuners/Discover', {
NewDevicesOnly: true
})).then(function (devices) {
currentDevices = devices;

View File

@@ -1,3 +1,5 @@
import escapeHTML from 'escape-html';
import datetime from '../../scripts/datetime';
import loading from '../../components/loading/loading';
import dom from '../../scripts/dom';
@@ -25,13 +27,13 @@ import { pageIdOn } from '../../scripts/clientUtils';
let html = '';
html += '<tr class="detailTableBodyRow detailTableBodyRow-shaded">';
html += '<td class="detailTableBodyCell">';
html += '<button type="button" is="emby-button" data-token="' + item.AccessToken + '" class="raised raised-mini btnRevoke" data-mini="true" title="' + globalize.translate('ButtonRevoke') + '" style="margin:0;">' + globalize.translate('ButtonRevoke') + '</button>';
html += '<button type="button" is="emby-button" data-token="' + escapeHTML(item.AccessToken) + '" class="raised raised-mini btnRevoke" data-mini="true" title="' + globalize.translate('ButtonRevoke') + '" style="margin:0;">' + globalize.translate('ButtonRevoke') + '</button>';
html += '</td>';
html += '<td class="detailTableBodyCell" style="vertical-align:middle;">';
html += item.AccessToken;
html += escapeHTML(item.AccessToken);
html += '</td>';
html += '<td class="detailTableBodyCell" style="vertical-align:middle;">';
html += item.AppName || '';
html += escapeHTML(item.AppName) || '';
html += '</td>';
html += '<td class="detailTableBodyCell" style="vertical-align:middle;">';
const date = datetime.parseISO8601Date(item.DateCreated, true);

View File

@@ -6,7 +6,7 @@ import serverNotifications from '../../scripts/serverNotifications';
import dom from '../../scripts/dom';
import globalize from '../../scripts/globalize';
import { formatDistanceToNow } from 'date-fns';
import { localeWithSuffix } from '../../scripts/dfnshelper';
import { getLocaleWithSuffix } from '../../scripts/dfnshelper';
import loading from '../../components/loading/loading';
import playMethodHelper from '../../components/playback/playmethodhelper';
import cardBuilder from '../../components/cardbuilder/cardBuilder';
@@ -476,7 +476,7 @@ import confirm from '../../components/confirm/confirm';
// how dates are returned by the server when the session is active and show something like 'Active now', instead of past/future sentences
if (!nowPlayingItem) {
return {
html: globalize.translate('LastSeen', formatDistanceToNow(Date.parse(session.LastActivityDate), localeWithSuffix)),
html: globalize.translate('LastSeen', formatDistanceToNow(Date.parse(session.LastActivityDate), getLocaleWithSuffix())),
image: imgUrl
};
}

View File

@@ -5,7 +5,7 @@ import dom from '../../../scripts/dom';
import globalize from '../../../scripts/globalize';
import imageHelper from '../../../scripts/imagehelper';
import { formatDistanceToNow } from 'date-fns';
import { localeWithSuffix } from '../../../scripts/dfnshelper';
import { getLocaleWithSuffix } from '../../../scripts/dfnshelper';
import '../../../elements/emby-button/emby-button';
import '../../../elements/emby-itemscontainer/emby-itemscontainer';
import '../../../components/cardbuilder/card.scss';
@@ -91,14 +91,17 @@ import confirm from '../../../components/confirm/confirm';
}
function load(page, devices) {
const localeWithSuffix = getLocaleWithSuffix();
let html = '';
html += devices.map(function (device) {
let deviceHtml = '';
deviceHtml += "<div data-id='" + device.Id + "' class='card backdropCard'>";
deviceHtml += "<div data-id='" + escapeHtml(device.Id) + "' class='card backdropCard'>";
deviceHtml += '<div class="cardBox visualCardBox">';
deviceHtml += '<div class="cardScalable">';
deviceHtml += '<div class="cardPadder cardPadder-backdrop"></div>';
deviceHtml += `<a is="emby-linkbutton" href="${canEdit ? '#!/device.html?id=' + device.Id : '#'}" class="cardContent cardImageContainer ${cardBuilder.getDefaultBackgroundClass()}">`;
deviceHtml += `<a is="emby-linkbutton" href="${canEdit ? '#!/device.html?id=' + escapeHtml(device.Id) : '#'}" class="cardContent cardImageContainer ${cardBuilder.getDefaultBackgroundClass()}">`;
// audit note: getDeviceIcon returns static text
const iconUrl = imageHelper.getDeviceIcon(device);
if (iconUrl) {
@@ -114,7 +117,7 @@ import confirm from '../../../components/confirm/confirm';
if (canEdit || canDelete(device.Id)) {
deviceHtml += '<div style="text-align:right; float:right;padding-top:5px;">';
deviceHtml += '<button type="button" is="paper-icon-button-light" data-id="' + device.Id + '" title="' + globalize.translate('Menu') + '" class="btnDeviceMenu"><span class="material-icons more_vert" aria-hidden="true"></span></button>';
deviceHtml += '<button type="button" is="paper-icon-button-light" data-id="' + escapeHtml(device.Id) + '" title="' + globalize.translate('Menu') + '" class="btnDeviceMenu"><span class="material-icons more_vert" aria-hidden="true"></span></button>';
deviceHtml += '</div>';
}

View File

@@ -46,7 +46,7 @@
<span>MPEG2</span>
</label>
<label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg4" data-types="amf,nvenc,videotoolbox" />
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg4" data-types="nvenc,videotoolbox" />
<span>MPEG4</span>
</label>
<label>
@@ -110,7 +110,7 @@
<span>${EnableIntelLowPowerHevcHwEncoder}</span>
</label>
<div class="fieldDescription">
<a is="emby-linkbutton" rel="noopener noreferrer" class="button-link" href="https://01.org/linuxgraphics/downloads/firmware" target="_blank">${IntelLowPowerEncHelp}</a>
<a is="emby-linkbutton" rel="noopener noreferrer" class="button-link" href="https://jellyfin.org/docs/general/administration/hardware-acceleration/intel#configure-and-verify-lp-mode-on-linux" target="_blank">${IntelLowPowerEncHelp}</a>
</div>
</div>
</div>
@@ -125,13 +125,24 @@
</div>
</div>
<div class="checkboxListContainer checkboxContainer-withDescription fldVppTonemapping hide">
<label>
<input type="checkbox" is="emby-checkbox" id="chkVppTonemapping" />
<span>${EnableVppTonemapping}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${AllowVppTonemappingHelp}</div>
<div class="vppTonemappingOptions hide">
<div class="checkboxListContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkVppTonemapping" />
<span>${EnableVppTonemapping}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${AllowVppTonemappingHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtVppTonemappingBrightness" pattern="[0-9]*" min="0" max="100" step=".00001" label="${LabelVppTonemappingBrightness}" />
<div class="fieldDescription">${LabelVppTonemappingBrightnessHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtVppTonemappingContrast" pattern="[0-9]*" min="1" max="2" step=".00001" label="${LabelVppTonemappingContrast}" />
<div class="fieldDescription">${LabelVppTonemappingContrastHelp}</div>
</div>
</div>
<div class="tonemappingOptions hide">
<div class="checkboxListContainer checkboxContainer-withDescription">
<label>
@@ -155,6 +166,14 @@
<a is="emby-linkbutton" rel="noopener noreferrer" class="button-link" href="http://ffmpeg.org/ffmpeg-all.html#tonemap_005fopencl" target="_blank">${TonemappingAlgorithmHelp}</a>
</div>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectTonemappingMode" label="${LabelTonemappingMode}">
<option value="auto">${Auto}</option>
<option value="max">MAX</option>
<option value="rgb">RGB</option>
</select>
<div class="fieldDescription">${TonemappingModeHelp}</div>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectTonemappingRange" label="${LabelTonemappingRange}">
<option value="auto">${Auto}</option>
@@ -167,10 +186,6 @@
<input is="emby-input" type="number" id="txtTonemappingDesat" pattern="[0-9]*" min="0" max="1.79769e+308" step=".00001" label="${LabelTonemappingDesat}" />
<div class="fieldDescription">${LabelTonemappingDesatHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtTonemappingThreshold" pattern="[0-9]*" min="0" max="1.79769e+308" step=".00001" label="${LabelTonemappingThreshold}" />
<div class="fieldDescription">${LabelTonemappingThresholdHelp}</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="txtTonemappingPeak" pattern="[0-9]*" min="0" max="1.79769e+308" step=".00001" label="${LabelTonemappingPeak}" />
<div class="fieldDescription">${LabelTonemappingPeakHelp}</div>
@@ -208,9 +223,8 @@
<div class="inputContainer fldEncoderPath">
<div style="display: flex; align-items: center;">
<div style="flex-grow:1;">
<input is="emby-input" class="txtEncoderPath" label="${LabelffmpegPath}" autocomplete="off" />
<input is="emby-input" class="txtEncoderPath" label="${LabelffmpegPath}" autocomplete="off" disabled />
</div>
<button type="button" is="paper-icon-button-light" id="btnSelectEncoderPath" class="emby-input-iconbutton"><span class="material-icons search" aria-hidden="true"></span></button>
</div>
<div class="fieldDescription">
<div>${LabelffmpegPathHelp}</div>

View File

@@ -32,11 +32,13 @@ import alert from '../../components/alert';
page.querySelector('#chkTonemapping').checked = config.EnableTonemapping;
page.querySelector('#chkVppTonemapping').checked = config.EnableVppTonemapping;
page.querySelector('#selectTonemappingAlgorithm').value = config.TonemappingAlgorithm;
page.querySelector('#selectTonemappingMode').value = config.TonemappingMode;
page.querySelector('#selectTonemappingRange').value = config.TonemappingRange;
page.querySelector('#txtTonemappingDesat').value = config.TonemappingDesat;
page.querySelector('#txtTonemappingThreshold').value = config.TonemappingThreshold;
page.querySelector('#txtTonemappingPeak').value = config.TonemappingPeak;
page.querySelector('#txtTonemappingParam').value = config.TonemappingParam || '';
page.querySelector('#txtVppTonemappingBrightness').value = config.VppTonemappingBrightness;
page.querySelector('#txtVppTonemappingContrast').value = config.VppTonemappingContrast;
page.querySelector('#selectEncoderPreset').value = config.EncoderPreset || '';
page.querySelector('#txtH264Crf').value = config.H264Crf || '';
page.querySelector('#txtH265Crf').value = config.H265Crf || '';
@@ -86,11 +88,13 @@ import alert from '../../components/alert';
config.EnableTonemapping = form.querySelector('#chkTonemapping').checked;
config.EnableVppTonemapping = form.querySelector('#chkVppTonemapping').checked;
config.TonemappingAlgorithm = form.querySelector('#selectTonemappingAlgorithm').value;
config.TonemappingMode = form.querySelector('#selectTonemappingMode').value;
config.TonemappingRange = form.querySelector('#selectTonemappingRange').value;
config.TonemappingDesat = form.querySelector('#txtTonemappingDesat').value;
config.TonemappingThreshold = form.querySelector('#txtTonemappingThreshold').value;
config.TonemappingPeak = form.querySelector('#txtTonemappingPeak').value;
config.TonemappingParam = form.querySelector('#txtTonemappingParam').value || '0';
config.VppTonemappingBrightness = form.querySelector('#txtVppTonemappingBrightness').value;
config.VppTonemappingContrast = form.querySelector('#txtVppTonemappingContrast').value;
config.EncoderPreset = form.querySelector('#selectEncoderPreset').value;
config.H264Crf = parseInt(form.querySelector('#txtH264Crf').value || '0');
config.H265Crf = parseInt(form.querySelector('#txtH265Crf').value || '0');
@@ -205,9 +209,9 @@ import alert from '../../components/alert';
}
if (systemInfo.OperatingSystem.toLowerCase() === 'linux' && (this.value == 'qsv' || this.value == 'vaapi')) {
page.querySelector('.fldVppTonemapping').classList.remove('hide');
page.querySelector('.vppTonemappingOptions').classList.remove('hide');
} else {
page.querySelector('.fldVppTonemapping').classList.add('hide');
page.querySelector('.vppTonemappingOptions').classList.add('hide');
}
if (this.value == 'qsv') {
@@ -230,21 +234,6 @@ import alert from '../../components/alert';
setDecodingCodecsVisible(page, this.value);
});
$('#btnSelectEncoderPath', page).on('click.selectDirectory', function () {
import('../../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
const picker = new DirectoryBrowser();
picker.show({
includeFiles: true,
callback: function (path) {
if (path) {
$('.txtEncoderPath', page).val(path);
}
picker.close();
}
});
});
});
$('#btnSelectTranscodingTempPath', page).on('click.selectDirectory', function () {
import('../../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
const picker = new DirectoryBrowser();

View File

@@ -72,6 +72,12 @@
<textarea is="emby-textarea" id="txtCustomCss" label="${LabelCustomCss}" class="textarea-mono"></textarea>
<div class="fieldDescription">${LabelCustomCssHelp}</div>
</div>
<div class="checkboxList paperList" style="padding:.5em 1em;">
<label>
<input type="checkbox" is="emby-checkbox" id="chkSplashScreenAvailable" />
<span>${EnableSplashScreen}</span>
</label>
</div>
</div>
<br />
<div>

View File

@@ -39,6 +39,7 @@ import alert from '../../components/alert';
ApiClient.getNamedConfiguration(brandingConfigKey).then(function(brandingConfig) {
brandingConfig.LoginDisclaimer = form.querySelector('#txtLoginDisclaimer').value;
brandingConfig.CustomCss = form.querySelector('#txtCustomCss').value;
brandingConfig.SplashscreenEnabled = form.querySelector('#chkSplashScreenAvailable').checked;
ApiClient.updateNamedConfiguration(brandingConfigKey, brandingConfig).then(function () {
Dashboard.processServerConfigurationUpdateResult();
@@ -106,6 +107,7 @@ import alert from '../../components/alert';
ApiClient.getNamedConfiguration(brandingConfigKey).then(function (config) {
view.querySelector('#txtLoginDisclaimer').value = config.LoginDisclaimer || '';
view.querySelector('#txtCustomCss').value = config.CustomCss || '';
view.querySelector('#chkSplashScreenAvailable').checked = config.SplashscreenEnabled === true;
});
});
}

View File

@@ -9,7 +9,8 @@ import alert from '../../components/alert';
/* eslint-disable indent */
function onSubmit() {
function onSubmit(event) {
event.preventDefault();
loading.show();
const form = this;
ApiClient.getServerConfiguration().then(function (config) {

View File

@@ -38,7 +38,7 @@ function reload(page) {
$('.monitorUsers', page).hide();
}
$('.notificationType', page).html(escapeHtml(typeInfo.Name) || 'Unknown Notification');
$('.notificationType', page).html(escapeHtml(typeInfo.Name || '') || 'Unknown Notification');
if (!notificationConfig) {
notificationConfig = {

View File

@@ -34,7 +34,9 @@
<div class="readOnlyContent">
<div is="emby-collapse" title="${HeaderDeveloperInfo}">
<div class="collapseContent">
<p id="developer"></p>
<p>${LabelDeveloper}: <span id="developer"></span></p>
<p>${LabelRepositoryName}: <span id="repositoryName"></span></p>
<p>${LabelRepositoryUrl}: <span id="repositoryUrl"></span></p>
</div>
</div>

View File

@@ -53,24 +53,34 @@ function renderPackage(pkg, installedPlugins, page) {
populateVersions(pkg, page, installedPlugin);
populateHistory(pkg, page);
$('.pluginName', page).html(pkg.name);
$('.pluginName', page).text(pkg.name);
$('#btnInstallDiv', page).removeClass('hide');
$('#pSelectVersion', page).removeClass('hide');
if (pkg.overview) {
$('#overview', page).show().html(pkg.overview);
$('#overview', page).show().text(pkg.overview);
} else {
$('#overview', page).hide();
}
$('#description', page).html(pkg.description);
$('#developer', page).html(pkg.owner);
$('#description', page).text(pkg.description);
$('#developer', page).text(pkg.owner);
// This is a hack; the repository name and URL should be part of the global values
// for the plugin, not each individual version. So we just use the top (latest)
// version to get this information. If it's missing (no versions), then say so.
if (pkg.versions.length) {
$('#repositoryName', page).text(pkg.versions[0].repositoryName);
$('#repositoryUrl', page).text(pkg.versions[0].repositoryUrl);
} else {
$('#repositoryName', page).text(globalize.translate('Unknown'));
$('#repositoryUrl', page).text(globalize.translate('Unknown'));
}
if (installedPlugin) {
const currentVersionText = globalize.translate('MessageYouHaveVersionInstalled', '<strong>' + installedPlugin.Version + '</strong>');
$('#pCurrentVersion', page).show().html(currentVersionText);
} else {
$('#pCurrentVersion', page).hide().html('');
$('#pCurrentVersion', page).hide().text('');
}
loading.hide();
@@ -81,7 +91,7 @@ function alertText(options) {
}
function performInstallation(page, name, guid, version) {
const developer = $('#developer', page).html().toLowerCase();
const repositoryUrl = $('#repositoryUrl', page).html().toLowerCase();
const alertCallback = function () {
loading.show();
@@ -94,7 +104,9 @@ function performInstallation(page, name, guid, version) {
});
};
if (developer !== 'jellyfin') {
// Check the repository URL for the official Jellyfin repository domain, or
// present the warning for 3rd party plugins.
if (!repositoryUrl.startsWith('https://repo.jellyfin.org/')) {
loading.hide();
let msg = globalize.translate('MessagePluginInstallDisclaimer');
msg += '<br/>';

View File

@@ -1,3 +1,5 @@
import escapeHTML from 'escape-html';
import loading from '../../../../components/loading/loading';
import libraryMenu from '../../../../scripts/libraryMenu';
import globalize from '../../../../scripts/globalize';
@@ -73,7 +75,7 @@ function populateList(options) {
html += '</div>';
}
html += '<div class="verticalSection">';
html += '<h2 class="sectionTitle sectionTitle-cards">' + category + '</h2>';
html += '<h2 class="sectionTitle sectionTitle-cards">' + escapeHTML(category) + '</h2>';
html += '<div class="itemsContainer vertical-wrap">';
currentCategory = category;
}
@@ -107,7 +109,7 @@ function getPluginHtml(plugin, options, installedPlugins) {
html += `<a class="cardImageContainer" is="emby-linkbutton" style="margin:0;padding:0" href="${href}" ${target}>`;
if (plugin.imageUrl) {
html += `<img src="${plugin.imageUrl}" style="width:100%" />`;
html += `<img src="${escapeHTML(plugin.imageUrl)}" style="width:100%" />`;
} else {
html += `<div class="cardImage flex align-items-center justify-content-center ${cardBuilder.getDefaultBackgroundClass()}">`;
html += '<span class="cardImageIcon material-icons extension" aria-hidden="true"></span>';
@@ -119,11 +121,9 @@ function getPluginHtml(plugin, options, installedPlugins) {
html += '</div>';
html += '<div class="cardFooter">';
html += "<div class='cardText'>";
html += plugin.name;
html += escapeHTML(plugin.name);
html += '</div>';
const installedPlugin = installedPlugins.filter(function (ip) {
return ip.Id == plugin.guid;
})[0];
const installedPlugin = installedPlugins.find(installed => installed.Id === plugin.guid);
html += "<div class='cardText cardText-secondary'>";
html += installedPlugin ? globalize.translate('LabelVersionInstalled', installedPlugin.Version) : '&nbsp;';
html += '</div>';

View File

@@ -2,9 +2,12 @@ import loading from '../../../../components/loading/loading';
import libraryMenu from '../../../../scripts/libraryMenu';
import globalize from '../../../../scripts/globalize';
import dialogHelper from '../../../../components/dialogHelper/dialogHelper';
import confirm from '../../../../components/confirm/confirm';
import '../../../../elements/emby-button/emby-button';
import '../../../../elements/emby-checkbox/emby-checkbox';
import '../../../../elements/emby-select/emby-select';
import '../../../../components/formdialog.scss';
import '../../../../components/listview/listview.scss';
@@ -19,8 +22,8 @@ function reloadList(page) {
noneElement: page.querySelector('#none'),
repositories: repositories
});
}).catch(() => {
console.error('error loading repositories');
}).catch(e => {
console.error('error loading repositories', e);
page.querySelector('#none').classList.remove('hide');
loading.hide();
});
@@ -35,46 +38,69 @@ function saveList(page) {
contentType: 'application/json'
}).then(() => {
reloadList(page);
}).catch(() => {
console.error('error saving repositories');
}).catch(e => {
console.error('error saving repositories', e);
loading.hide();
});
}
function populateList(options) {
let html = '';
const paperList = document.createElement('div');
paperList.className = 'paperList';
html += '<div class="paperList">';
for (let i = 0; i < options.repositories.length; i++) {
html += getRepositoryHtml(options.repositories[i]);
}
options.repositories.forEach(repo => {
paperList.appendChild(getRepositoryElement(repo));
});
html += '</div>';
if (!options.repositories.length) {
options.noneElement.classList.remove('hide');
} else {
options.noneElement.classList.add('hide');
}
options.listElement.innerHTML = html;
options.listElement.innerHTML = '';
options.listElement.appendChild(paperList);
loading.hide();
}
function getRepositoryHtml(repository) {
let html = '';
function getRepositoryElement(repository) {
const listItem = document.createElement('div');
listItem.className = 'listItem listItem-border';
html += '<div class="listItem listItem-border">';
html += `<a is="emby-linkbutton" style="margin:0;padding:0" class="clearLink listItemIconContainer" href="${repository.Url}" rel="noopener noreferrer" target="_blank">`;
html += '<span class="material-icons listItemIcon open_in_new" aria-hidden="true"></span>';
html += '</a>';
html += '<div class="listItemBody two-line">';
html += `<h3 class="listItemBodyText">${repository.Name}</h3>`;
html += `<div class="listItemBodyText secondary">${repository.Url}</div>`;
html += '</div>';
html += `<button type="button" is="paper-icon-button-light" id="${repository.Url}" class="btnDelete" title="${globalize.translate('Delete')}"><span class="material-icons delete" aria-hidden="true"></span></button>`;
html += '</div>';
const repoLink = document.createElement('a', 'emby-linkbutton');
repoLink.classList.add('clearLink', 'listItemIconContainer');
repoLink.style.margin = '0';
repoLink.style.padding = '0';
repoLink.rel = 'noopener noreferrer';
repoLink.target = '_blank';
repoLink.href = repository.Url;
repoLink.innerHTML = '<span class="material-icons listItemIcon open_in_new" aria-hidden="true"></span>';
listItem.appendChild(repoLink);
return html;
const body = document.createElement('div');
body.className = 'listItemBody two-line';
const name = document.createElement('h3');
name.className = 'listItemBodyText';
name.innerText = repository.Name;
body.appendChild(name);
const url = document.createElement('div');
url.className = 'listItemBodyText secondary';
url.innerText = repository.Url;
body.appendChild(url);
listItem.appendChild(body);
const button = document.createElement('button', 'paper-icon-button-light');
button.type = 'button';
button.classList.add('btnDelete');
button.id = repository.Url;
button.title = globalize.translate('Delete');
button.innerHTML = '<span class="material-icons delete" aria-hidden="true"></span>';
listItem.appendChild(button);
return listItem;
}
function getTabs() {
@@ -141,14 +167,36 @@ export default function(view) {
dialog.querySelector('.newPluginForm').addEventListener('submit', e => {
e.preventDefault();
repositories.push({
Name: dialog.querySelector('#txtRepositoryName').value,
Url: dialog.querySelector('#txtRepositoryUrl').value,
Enabled: true
});
const repositoryUrl = dialog.querySelector('#txtRepositoryUrl').value.toLowerCase();
const alertCallback = function () {
repositories.push({
Name: dialog.querySelector('#txtRepositoryName').value,
Url: dialog.querySelector('#txtRepositoryUrl').value,
Enabled: true
});
saveList(view);
dialogHelper.close(dialog);
};
// Check the repository URL for the official Jellyfin repository domain, or
// present the warning for 3rd party plugins.
if (!repositoryUrl.startsWith('https://repo.jellyfin.org/')) {
let msg = globalize.translate('MessageRepositoryInstallDisclaimer');
msg += '<br/>';
msg += '<br/>';
msg += globalize.translate('PleaseConfirmRepositoryInstallation');
confirm(msg, globalize.translate('HeaderConfirmRepositoryInstallation')).then(function () {
alertCallback();
}).catch(() => {
console.debug('repository not installed');
dialogHelper.close(dialog);
});
} else {
alertCallback();
}
saveList(view);
dialogHelper.close(dialog);
return false;
});

View File

@@ -58,13 +58,13 @@ import confirm from '../../../components/confirm/confirm';
html += '<div class="listItem listItem-border">';
html += '<span class="material-icons listItemIcon schedule" aria-hidden="true"></span>';
if (trigger.MaxRuntimeMs) {
if (trigger.MaxRuntimeTicks) {
html += '<div class="listItemBody two-line">';
} else {
html += '<div class="listItemBody">';
}
html += "<div class='listItemBodyText'>" + ScheduledTaskPage.getTriggerFriendlyName(trigger) + '</div>';
if (trigger.MaxRuntimeMs) {
if (trigger.MaxRuntimeTicks) {
html += '<div class="listItemBodyText secondary">';
const hours = trigger.MaxRuntimeTicks / 36e9;
if (hours == 1) {
@@ -202,7 +202,7 @@ import confirm from '../../../components/confirm/confirm';
let timeLimit = $('#txtTimeLimit', page).val() || '0';
timeLimit = parseFloat(timeLimit) * 3600000;
trigger.MaxRuntimeMs = timeLimit || null;
trigger.MaxRuntimeTicks = timeLimit * 1e4 || null;
return trigger;
}

View File

@@ -4,7 +4,7 @@ import { Events } from 'jellyfin-apiclient';
import globalize from '../../../scripts/globalize';
import serverNotifications from '../../../scripts/serverNotifications';
import { formatDistance, formatDistanceToNow } from 'date-fns';
import { getLocale, localeWithSuffix } from '../../../scripts/dfnshelper';
import { getLocale, getLocaleWithSuffix } from '../../../scripts/dfnshelper';
import '../../../components/listview/listview.scss';
import '../../../elements/emby-button/emby-button';
@@ -77,7 +77,7 @@ import '../../../elements/emby-button/emby-button';
if (task.LastExecutionResult) {
const endtime = Date.parse(task.LastExecutionResult.EndTimeUtc);
const starttime = Date.parse(task.LastExecutionResult.StartTimeUtc);
html += globalize.translate('LabelScheduledTaskLastRan', formatDistanceToNow(endtime, localeWithSuffix),
html += globalize.translate('LabelScheduledTaskLastRan', formatDistanceToNow(endtime, getLocaleWithSuffix()),
formatDistance(starttime, endtime, { locale: getLocale() }));
if (task.LastExecutionResult.Status === 'Failed') {
html += " <span style='color:#FF0000;'>(" + globalize.translate('LabelFailed') + ')</span>';

View File

@@ -13,7 +13,7 @@
</div>
<div class="mainDetailButtons focuscontainer-x">
<button is="emby-button" type="button" class="button-flat btnPlay hide detailButton raised" title="${ButtonResume}" data-action="resume">
<button is="emby-button" type="button" class="button-flat btnPlay hide detailButton" title="${ButtonResume}" data-action="resume">
<div class="detailButton-content">
<span class="material-icons detailButton-icon play_arrow" aria-hidden="true"></span>
</div>

View File

@@ -188,7 +188,7 @@ function renderTrackSelections(page, instance, item, forceReload) {
});
resolutionNames.sort((a, b) => parseInt(b.Name, 10) - parseInt(a.Name, 10));
sourceNames.sort(function(a, b) {
sourceNames.sort((a, b) => {
const nameA = a.Name.toUpperCase();
const nameB = b.Name.toUpperCase();
if (nameA < nameB) {
@@ -274,6 +274,7 @@ function renderAudioSelections(page, mediaSources) {
const tracks = mediaSource.MediaStreams.filter(function (m) {
return m.Type === 'Audio';
});
tracks.sort(itemHelper.sortTracks);
const select = page.querySelector('.selectAudio');
select.setLabel(globalize.translate('Audio'));
const selectedId = mediaSource.DefaultAudioStreamIndex;
@@ -303,31 +304,26 @@ function renderSubtitleSelections(page, mediaSources) {
const tracks = mediaSource.MediaStreams.filter(function (m) {
return m.Type === 'Subtitle';
});
tracks.sort(itemHelper.sortTracks);
const select = page.querySelector('.selectSubtitles');
select.setLabel(globalize.translate('Subtitles'));
const selectedId = mediaSource.DefaultSubtitleStreamIndex == null ? -1 : mediaSource.DefaultSubtitleStreamIndex;
const videoTracks = mediaSource.MediaStreams.filter(function (m) {
return m.Type === 'Video';
});
let selected = selectedId === -1 ? ' selected' : '';
select.innerHTML = '<option value="-1">' + globalize.translate('Off') + '</option>' + tracks.map(function (v) {
selected = v.Index === selectedId ? ' selected' : '';
return '<option value="' + v.Index + '" ' + selected + '>' + v.DisplayTitle + '</option>';
}).join('');
// This only makes sense on Video items
if (videoTracks.length) {
let selected = selectedId === -1 ? ' selected' : '';
select.innerHTML = '<option value="-1">' + globalize.translate('Off') + '</option>' + tracks.map(function (v) {
selected = v.Index === selectedId ? ' selected' : '';
return '<option value="' + v.Index + '" ' + selected + '>' + v.DisplayTitle + '</option>';
}).join('');
if (tracks.length > 0) {
select.removeAttribute('disabled');
} else {
select.setAttribute('disabled', 'disabled');
}
if (tracks.length > 0) {
select.removeAttribute('disabled');
} else {
select.setAttribute('disabled', 'disabled');
}
if (tracks.length) {
page.querySelector('.selectSubtitlesContainer').classList.remove('hide');
} else {
select.innerHTML = '';
page.querySelector('.selectSubtitlesContainer').classList.add('hide');
}
}
@@ -450,7 +446,7 @@ function renderName(item, container, context) {
} else if (item.ParentIndexNumber != null && item.Type === 'Episode') {
parentNameHtml.push(`<a style="color:inherit;" class="button-link itemAction" is="emby-linkbutton" href="#" data-action="link" data-id="${item.SeasonId}" data-serverid="${item.ServerId}" data-type="Season" data-isfolder="true">${escapeHtml(item.SeasonName)}</a>`);
} else if (item.ParentIndexNumber != null && item.IsSeries) {
parentNameHtml.push(escapeHtml(item.SeasonName) || 'S' + item.ParentIndexNumber);
parentNameHtml.push(escapeHtml(item.SeasonName || 'S' + item.ParentIndexNumber));
} else if (item.Album && item.AlbumId && (item.Type === 'MusicVideo' || item.Type === 'Audio')) {
parentNameHtml.push(`<a style="color:inherit;" class="button-link itemAction" is="emby-linkbutton" href="#" data-action="link" data-id="${item.AlbumId}" data-serverid="${item.ServerId}" data-type="MusicAlbum" data-isfolder="true">${escapeHtml(item.Album)}</a>`);
} else if (item.Album) {
@@ -513,7 +509,7 @@ function setTrailerButtonVisibility(page, item) {
}
function renderBackdrop(item) {
if (dom.getWindowSize().innerWidth >= 1000) {
if (!layoutManager.mobile && dom.getWindowSize().innerWidth >= 1000) {
backdrop.setBackdrops([item]);
} else {
backdrop.clearBackdrop();
@@ -1175,9 +1171,9 @@ function renderMoreFromArtist(view, item, apiClient) {
};
if (item.Type === 'MusicArtist') {
query.AlbumArtistIds = item.Id;
query.ContributingArtistIds = item.Id;
} else {
query.AlbumArtistIds = item.AlbumArtists[0].Id;
query.ContributingArtistIds = item.AlbumArtists.map(artist => artist.Id).join(',');
}
apiClient.getItems(apiClient.getCurrentUserId(), query).then(function (result) {
@@ -1926,7 +1922,15 @@ export default function (view, params) {
}
function onPlayClick() {
playCurrentItem(this, this.getAttribute('data-action'));
let actionElem = this;
let action = actionElem.getAttribute('data-action');
if (!action) {
actionElem = actionElem.querySelector('[data-action]') || actionElem;
action = actionElem.getAttribute('data-action');
}
playCurrentItem(actionElem, action);
}
function onInstantMixClick() {
@@ -1962,7 +1966,9 @@ export default function (view, params) {
download([{
url: downloadHref,
itemId: currentItem.Id,
serverId: currentItem.serverId
serverId: currentItem.ServerId,
title: currentItem.Name,
filename: currentItem.Path.replace(/^.*[\\/]/, '')
}]);
}

View File

@@ -1,6 +1,11 @@
import remotecontrolFactory from '../../../components/remotecontrol/remotecontrol';
import libraryMenu from '../../../scripts/libraryMenu';
import '../../../elements/emby-button/emby-button';
import '../../../elements/emby-button/paper-icon-button-light';
import '../../../elements/emby-collapse/emby-collapse';
import '../../../elements/emby-input/emby-input';
import '../../../elements/emby-itemscontainer/emby-itemscontainer';
import '../../../elements/emby-slider/emby-slider';
export default function (view) {
const remoteControl = new remotecontrolFactory();

View File

@@ -77,7 +77,7 @@
<input is="emby-slider" type="range" step="1" min="0" max="100" value="0" class="osdVolumeSlider" />
</div>
</div>
<button is="paper-icon-button-light" class="btnVideoOsdSettings hide autoSize" title="${Settings}">
<button is="paper-icon-button-light" class="btnVideoOsdSettings autoSize" title="${Settings}">
<span class="largePaperIconButton material-icons settings" aria-hidden="true"></span>
</button>
<button is="paper-icon-button-light" class="btnAirPlay hide autoSize" title="${AirPlay}">

View File

@@ -1,4 +1,5 @@
import escapeHtml from 'escape-html';
import debounce from 'lodash-es/debounce';
import { playbackManager } from '../../../components/playback/playbackmanager';
import SyncPlay from '../../../components/syncPlay/core';
import browser from '../../../scripts/browser';
@@ -217,9 +218,9 @@ import { appRouter } from '../../../components/appRouter';
let mouseIsDown = false;
function showOsd() {
function showOsd(focusElement) {
slideDownToShow(headerElement);
showMainOsdControls();
showMainOsdControls(focusElement);
resetIdle();
}
@@ -273,7 +274,9 @@ import { appRouter } from '../../../components/appRouter';
});
}
function showMainOsdControls() {
const _focus = debounce((focusElement) => focusManager.focus(focusElement), 50);
function showMainOsdControls(focusElement) {
if (!currentVisibleMenu) {
const elem = osdBottomElement;
currentVisibleMenu = 'osd';
@@ -281,12 +284,14 @@ import { appRouter } from '../../../components/appRouter';
elem.classList.remove('hide');
elem.classList.remove('videoOsdBottom-hidden');
focusElement ||= elem.querySelector('.btnPause');
if (!layoutManager.mobile) {
setTimeout(function () {
focusManager.focus(elem.querySelector('.btnPause'));
}, 50);
_focus(focusElement);
}
toggleSubtitleSync();
} else if (currentVisibleMenu === 'osd' && focusElement && !layoutManager.mobile) {
_focus(focusElement);
}
}
@@ -510,11 +515,11 @@ import { appRouter } from '../../../components/appRouter';
}
function onBeginFetch() {
document.querySelector('.osdMediaStatus').classList.remove('hide');
view.querySelector('.osdMediaStatus').classList.remove('hide');
}
function onEndFetch() {
document.querySelector('.osdMediaStatus').classList.add('hide');
view.querySelector('.osdMediaStatus').classList.add('hide');
}
function bindToPlayer(player) {
@@ -677,12 +682,6 @@ import { appRouter } from '../../../components/appRouter';
updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playState.PlaybackStartTimeTicks, playState.PlaybackRate, playState.BufferedRanges || []);
updateNowPlayingInfo(player, state);
if (state.MediaSource && state.MediaSource.SupportsTranscoding && supportedCommands.indexOf('SetMaxStreamingBitrate') !== -1) {
view.querySelector('.btnVideoOsdSettings').classList.remove('hide');
} else {
view.querySelector('.btnVideoOsdSettings').classList.add('hide');
}
const isProgressClear = state.MediaSource && state.MediaSource.RunTimeTicks == null;
nowPlayingPositionSlider.setIsClear(isProgressClear);
@@ -860,6 +859,8 @@ import { appRouter } from '../../../components/appRouter';
const player = currentPlayer;
if (player) {
const state = playbackManager.getPlayerState(player);
// show subtitle offset feature only if player and media support it
const showSubOffset = playbackManager.supportSubtitleOffset(player) &&
playbackManager.canHandleOffsetOnCurrentSubtitle(player);
@@ -868,6 +869,7 @@ import { appRouter } from '../../../components/appRouter';
mediaType: 'Video',
player: player,
positionTo: btn,
quality: state.MediaSource?.SupportsTranscoding,
stats: true,
suboffset: showSubOffset,
onOption: onSettingsOption
@@ -1038,18 +1040,37 @@ import { appRouter } from '../../../components/appRouter';
const key = keyboardnavigation.getKeyName(e);
const isKeyModified = e.ctrlKey || e.altKey || e.metaKey;
const btnPlayPause = osdBottomElement.querySelector('.btnPause');
if (e.keyCode === 32) {
if (e.target.tagName !== 'BUTTON' || !layoutManager.tv) {
playbackManager.playPause(currentPlayer);
showOsd(btnPlayPause);
e.preventDefault();
e.stopPropagation();
// Trick Firefox with a null element to skip next click
clickedElement = null;
} else {
showOsd();
}
showOsd();
return;
}
if (layoutManager.tv && !currentVisibleMenu) {
// Change the behavior of some keys when the OSD is hidden
switch (key) {
case 'ArrowLeft':
case 'ArrowRight':
showOsd(nowPlayingPositionSlider);
nowPlayingPositionSlider.dispatchEvent(new KeyboardEvent(e.type, e));
return;
case 'Enter':
playbackManager.playPause(currentPlayer);
showOsd(btnPlayPause);
return;
}
}
if (layoutManager.tv && keyboardnavigation.isNavigationKey(key)) {
showOsd();
return;
@@ -1069,7 +1090,7 @@ import { appRouter } from '../../../components/appRouter';
break;
case 'k':
playbackManager.playPause(currentPlayer);
showOsd();
showOsd(btnPlayPause);
break;
case 'ArrowUp':
case 'Up':
@@ -1083,23 +1104,21 @@ import { appRouter } from '../../../components/appRouter';
case 'ArrowRight':
case 'Right':
playbackManager.fastForward(currentPlayer);
showOsd();
showOsd(btnFastForward);
break;
case 'j':
case 'ArrowLeft':
case 'Left':
playbackManager.rewind(currentPlayer);
showOsd();
showOsd(btnRewind);
break;
case 'f':
if (!e.ctrlKey && !e.metaKey) {
playbackManager.toggleFullscreen(currentPlayer);
showOsd();
}
break;
case 'm':
playbackManager.toggleMute(currentPlayer);
showOsd();
break;
case 'p':
case 'P':
@@ -1119,7 +1138,7 @@ import { appRouter } from '../../../components/appRouter';
// Ignores gamepad events that are always triggered, even when not focused.
if (document.hasFocus()) { /* eslint-disable-line compat/compat */
playbackManager.rewind(currentPlayer);
showOsd();
showOsd(btnRewind);
}
break;
case 'NavigationRight':
@@ -1128,7 +1147,7 @@ import { appRouter } from '../../../components/appRouter';
// Ignores gamepad events that are always triggered, even when not focused.
if (document.hasFocus()) { /* eslint-disable-line compat/compat */
playbackManager.fastForward(currentPlayer);
showOsd();
showOsd(btnFastForward);
}
break;
case 'Home':
@@ -1311,7 +1330,7 @@ import { appRouter } from '../../../components/appRouter';
const btnFastForward = view.querySelector('.btnFastForward');
const transitionEndEventName = dom.whichTransitionEvent();
const headerElement = document.querySelector('.skinHeader');
const osdBottomElement = document.querySelector('.videoOsdBottom-maincontrols');
const osdBottomElement = view.querySelector('.videoOsdBottom-maincontrols');
nowPlayingPositionSlider.enableKeyboardDragging();
nowPlayingVolumeSlider.enableKeyboardDragging();

View File

@@ -289,6 +289,7 @@ import './login.scss';
disclaimer.innerHTML = DOMPurify.sanitize(marked(options.LoginDisclaimer || ''));
for (const elem of disclaimer.querySelectorAll('a')) {
elem.rel = 'noopener noreferrer';
elem.target = '_blank';
elem.classList.add('button-link');
elem.setAttribute('is', 'emby-linkbutton');

View File

@@ -5,7 +5,7 @@
<div>${QuickConnectDescription}</div>
<br />
<div class="inputContainer">
<input is="emby-input" type="number" min="0" max="999999" required id="txtQuickConnectCode" label="${LabelQuickConnectCode}" autocomplete="off" />
<input is="emby-input" type="text" inputmode="numeric" pattern="[0-9\s]*" minlength="6" required id="txtQuickConnectCode" label="${LabelQuickConnectCode}" autocomplete="off" />
</div>
<button id="btnQuickConnectAuthorize" is="emby-button" type="submit" class="raised button-submit block">
<span>${Authorize}</span>

View File

@@ -15,7 +15,9 @@ export default function (view) {
return;
}
authorize(codeElement.value);
// Remove spaces from code
const normalizedCode = codeElement.value.replace(/\s/g, '');
authorize(normalizedCode);
});
});
}

View File

@@ -123,20 +123,20 @@
@keyframes repaintChrome {
from {
padding: 0;
margin: 0;
}
to {
padding: 0;
margin: 0;
}
}
@-webkit-keyframes repaintChrome {
from {
padding: 0;
margin: 0;
}
to {
padding: 0;
margin: 0;
}
}

View File

@@ -20,6 +20,27 @@ import '../emby-input/emby-input';
}
}
/**
* Returns normalized slider step.
*
* @param {HTMLInputElement} range slider itself
* @param {number|undefined} step step
* @returns {number} normalized slider step.
*/
function normalizeSliderStep(range, step) {
if (step > 0) {
return step;
}
step = parseFloat(range.step);
if (step > 0) {
return step;
}
return 1;
}
/**
* Returns slider fraction corresponding to client position.
*
@@ -35,7 +56,7 @@ import '../emby-input/emby-input';
// Snap to step
const valueRange = range.max - range.min;
if (range.step !== 'any' && valueRange !== 0) {
const step = (range.step || 1) / valueRange;
const step = normalizeSliderStep(range) / valueRange;
fraction = Math.round(fraction / step) * step;
}
@@ -54,7 +75,7 @@ import '../emby-input/emby-input';
// Snap to step
if (range.step !== 'any') {
const step = range.step || 1;
const step = normalizeSliderStep(range);
value = Math.round(value / step) * step;
}
@@ -305,6 +326,8 @@ import '../emby-input/emby-input';
} else {
startInterval(this);
}
updateValues.call(this);
};
/**
@@ -375,16 +398,23 @@ import '../emby-input/emby-input';
switch (keyboardnavigation.getKeyName(e)) {
case 'ArrowLeft':
case 'Left':
stepKeyboard(this, -this.keyboardStepDown || -1);
stepKeyboard(this, -normalizeSliderStep(this, this.keyboardStepDown));
e.preventDefault();
e.stopPropagation();
break;
case 'ArrowRight':
case 'Right':
stepKeyboard(this, this.keyboardStepUp || 1);
stepKeyboard(this, normalizeSliderStep(this, this.keyboardStepUp));
e.preventDefault();
e.stopPropagation();
break;
case 'Enter':
if (this.keyboardDragging) {
finishKeyboardDragging(this);
e.preventDefault();
e.stopPropagation();
}
break;
}
}

View File

@@ -224,6 +224,7 @@
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
}
.sliderBubbleText {

View File

@@ -75,13 +75,27 @@
background-color: transparent !important;
}
.mouseIdle,
.mouseIdle button,
.mouseIdle select,
.mouseIdle input,
.mouseIdle textarea,
.mouseIdle a,
.mouseIdle label {
.layout-tv .mouseIdle,
.layout-tv .mouseIdle button,
.layout-tv .mouseIdle select,
.layout-tv .mouseIdle input,
.layout-tv .mouseIdle textarea,
.layout-tv .mouseIdle a,
.layout-tv .mouseIdle label,
.transparentDocument .mouseIdle,
.transparentDocument .mouseIdle button,
.transparentDocument .mouseIdle select,
.transparentDocument .mouseIdle input,
.transparentDocument .mouseIdle textarea,
.transparentDocument .mouseIdle a,
.transparentDocument .mouseIdle label,
.screensaver-noScroll.mouseIdle,
.screensaver-noScroll.mouseIdle button,
.screensaver-noScroll.mouseIdle select,
.screensaver-noScroll.mouseIdle input,
.screensaver-noScroll.mouseIdle textarea,
.screensaver-noScroll.mouseIdle a,
.screensaver-noScroll.mouseIdle label {
cursor: none !important;
}

View File

@@ -0,0 +1,48 @@
/**
* Polyfill for KeyboardEvent
* - Constructor.
*/
(function (window) {
'use strict';
try {
new window.KeyboardEvent('event', { bubbles: true, cancelable: true });
} catch (e) {
// We can't use `KeyboardEvent` in old WebKit because `initKeyboardEvent`
// doesn't seem to populate some properties (`keyCode`, `which`) that
// are read-only.
const KeyboardEventOriginal = window.Event;
const KeyboardEvent = function (eventName, options) {
options = options || {};
const event = document.createEvent('Event');
event.initEvent(eventName, !!options.bubbles, !!options.cancelable);
event.view = options.view || document.defaultView;
event.key = options.key || options.keyIdentifier || '';
event.keyCode = options.keyCode || 0;
event.code = options.code || '';
event.charCode = options.charCode || 0;
event.char = options.char || '';
event.which = options.which || 0;
event.location = options.location || options.keyLocation || 0;
event.ctrlKey = !!options.ctrlKey;
event.altKey = !!options.altKey;
event.shiftKey = !!options.shiftKey;
event.metaKey = !!options.metaKey;
event.repeat = !!options.repeat;
return event;
};
KeyboardEvent.prototype = KeyboardEventOriginal.prototype;
window.KeyboardEvent = KeyboardEvent;
}
}(window));

View File

@@ -116,7 +116,7 @@ function ScreenSaverManager() {
return;
}
if (getFunctionalEventIdleTime < getMinIdleTime()) {
if (getFunctionalEventIdleTime() < getMinIdleTime()) {
return;
}

View File

@@ -287,7 +287,9 @@ export class BookPlayer {
width: '100%',
height: renderHeight,
// TODO: Add option for scrolled-doc
flow: 'paginated'
flow: 'paginated',
// Scripted content is required to allow touch event passthrough in Safari
allowScriptedContent: true
});
this.currentSrc = downloadHref;

View File

@@ -242,7 +242,7 @@ export class ComicsPlayer {
}
// the comic book archive supports any kind of image format as it's just a zip archive
const supportedFormats = ['jpg', 'jpeg', 'jpe', 'jif', 'jfif', 'jfi', 'png', 'avif', 'gif', 'bmp', 'dib', 'tiff', 'tif'];
const supportedFormats = ['jpg', 'jpeg', 'jpe', 'jif', 'jfif', 'jfi', 'png', 'avif', 'gif', 'bmp', 'dib', 'tiff', 'tif', 'webp'];
class ArchiveSource {
constructor(url) {

View File

@@ -74,7 +74,7 @@ function enableHlsPlayer(url, item, mediaSource, mediaType) {
type: 'HEAD'
}).then(function (response) {
const contentType = (response.headers.get('Content-Type') || '').toLowerCase();
if (contentType === 'application/x-mpegurl') {
if (contentType === 'application/vnd.apple.mpegurl' || contentType === 'application/x-mpegurl') {
resolve();
} else {
reject();

View File

@@ -1,3 +1,5 @@
import DOMPurify from 'dompurify';
import browser from '../../scripts/browser';
import { Events } from 'jellyfin-apiclient';
import { appHost } from '../../components/apphost';
@@ -28,8 +30,29 @@ import itemHelper from '../../components/itemHelper';
import Screenfull from 'screenfull';
import globalize from '../../scripts/globalize';
import ServerConnections from '../../components/ServerConnections';
import profileBuilder from '../../scripts/browserDeviceProfile';
import profileBuilder, { canPlaySecondaryAudio } from '../../scripts/browserDeviceProfile';
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
import { includesAny } from '../../utils/container.ts';
/**
* Returns resolved URL.
* @param {string} url - URL.
* @returns {string} Resolved URL or `url` if resolving failed.
*/
function resolveUrl(url) {
return new Promise((resolve) => {
const xhr = new XMLHttpRequest();
xhr.open('HEAD', url, true);
xhr.onload = function () {
resolve(xhr.responseURL || url);
};
xhr.onerror = function (e) {
console.error(e);
resolve(url);
};
xhr.send(null);
});
}
/* eslint-disable indent */
@@ -344,7 +367,7 @@ function tryRemoveElement(elem) {
this.#currentTime = null;
this.resetSubtitleOffset();
if (options.resetSubtitleOffset !== false) this.resetSubtitleOffset();
return this.createMediaElement(options).then(elem => {
return this.updateVideoUrl(options).then(() => {
@@ -582,7 +605,7 @@ function tryRemoveElement(elem) {
/**
* @private
*/
isAudioStreamSupported(stream, deviceProfile) {
isAudioStreamSupported(stream, deviceProfile, container) {
const codec = (stream.Codec || '').toLowerCase();
if (!codec) {
@@ -596,17 +619,11 @@ function tryRemoveElement(elem) {
const profiles = deviceProfile.DirectPlayProfiles || [];
return profiles.filter(function (p) {
if (p.Type === 'Video') {
if (!p.AudioCodec) {
return true;
}
return p.AudioCodec.toLowerCase().includes(codec);
}
return false;
}).length > 0;
return profiles.some(function (p) {
return p.Type === 'Video'
&& includesAny((p.Container || '').toLowerCase(), container)
&& includesAny((p.AudioCodec || '').toLowerCase(), codec);
});
}
/**
@@ -615,8 +632,11 @@ function tryRemoveElement(elem) {
getSupportedAudioStreams() {
const profile = this.#lastProfile;
return getMediaStreamAudioTracks(this._currentPlayOptions.mediaSource).filter((stream) => {
return this.isAudioStreamSupported(stream, profile);
const mediaSource = this._currentPlayOptions.mediaSource;
const container = mediaSource.Container.toLowerCase();
return getMediaStreamAudioTracks(mediaSource).filter((stream) => {
return this.isAudioStreamSupported(stream, profile, container);
});
}
@@ -1048,7 +1068,7 @@ function tryRemoveElement(elem) {
* @private
*/
renderSsaAss(videoElement, track, item) {
const supportedFonts = ['application/x-truetype-font', 'font/otf', 'font/ttf', 'font/woff', 'font/woff2'];
const supportedFonts = ['application/vnd.ms-opentype', 'application/x-truetype-font', 'font/otf', 'font/ttf', 'font/woff', 'font/woff2'];
const avaliableFonts = [];
const attachments = this._currentPlayOptions.mediaSource.MediaAttachments || [];
const apiClient = ServerConnections.getApiClient(item);
@@ -1081,19 +1101,27 @@ function tryRemoveElement(elem) {
timeOffset: (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000,
// new octopus options; override all, even defaults
renderMode: 'blend',
renderMode: 'wasm-blend',
dropAllAnimations: false,
libassMemoryLimit: 40,
libassGlyphLimit: 40,
targetFps: 24,
prescaleTradeoff: 0.8,
softHeightLimit: 1080,
hardHeightLimit: 2160,
prescaleFactor: 0.8,
prescaleHeightLimit: 1080,
maxRenderHeight: 2160,
resizeVariation: 0.2,
renderAhead: 90
};
import('libass-wasm').then(({default: SubtitlesOctopus}) => {
apiClient.getNamedConfiguration('encoding').then(config => {
import('@jellyfin/libass-wasm').then(({default: SubtitlesOctopus}) => {
Promise.all([
apiClient.getNamedConfiguration('encoding'),
// Worker in Tizen 5 doesn't resolve relative path with async request
resolveUrl(options.workerUrl),
resolveUrl(options.legacyWorkerUrl)
]).then(([config, workerUrl, legacyWorkerUrl]) => {
options.workerUrl = workerUrl;
options.legacyWorkerUrl = legacyWorkerUrl;
if (config.EnableFallbackFont) {
apiClient.getJSON(fallbackFontList).then((fontFiles = []) => {
fontFiles.forEach(font => {
@@ -1291,7 +1319,8 @@ function tryRemoveElement(elem) {
}
if (selectedTrackEvent && selectedTrackEvent.Text) {
subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text, true);
subtitleTextElement.innerHTML = DOMPurify.sanitize(
normalizeTrackEventText(selectedTrackEvent.Text, true));
subtitleTextElement.classList.remove('hide');
} else {
subtitleTextElement.classList.add('hide');
@@ -1347,6 +1376,9 @@ function tryRemoveElement(elem) {
// Can't autoplay in these browsers so we need to use the full controls, at least until playback starts
if (!appHost.supports('htmlvideoautoplay')) {
html += '<video class="' + cssClass + '" preload="metadata" autoplay="autoplay" controls="controls" webkit-playsinline playsinline>';
} else if (browser.web0s) {
// in webOS, setting preload auto allows resuming videos
html += '<video class="' + cssClass + '" preload="auto" autoplay="autoplay" webkit-playsinline playsinline>';
} else {
// Chrome 35 won't play with preload none
html += '<video class="' + cssClass + '" preload="metadata" autoplay="autoplay" webkit-playsinline playsinline>';
@@ -1528,15 +1560,9 @@ function tryRemoveElement(elem) {
}
canSetAudioStreamIndex() {
if (browser.tizen || browser.orsay) {
return true;
}
const video = this.#mediaElement;
if (video) {
if (video.audioTracks) {
return true;
}
return canPlaySecondaryAudio(video);
}
return false;

View File

@@ -297,7 +297,9 @@ if (userAgent.toLowerCase().indexOf('xbox') !== -1) {
browser.tv = true;
}
browser.animate = typeof document !== 'undefined' && document.documentElement.animate != null;
browser.hisense = userAgent.toLowerCase().includes('hisense');
browser.tizen = userAgent.toLowerCase().indexOf('tizen') !== -1 || window.tizen != null;
browser.vidaa = userAgent.toLowerCase().includes('vidaa');
browser.web0s = isWeb0s();
browser.edgeUwp = browser.edge && (userAgent.toLowerCase().indexOf('msapphost') !== -1 || userAgent.toLowerCase().indexOf('webview') !== -1);

View File

@@ -130,7 +130,7 @@ import browser from './browser';
typeString = 'audio/ogg; codecs="opus"';
} else if (format === 'alac') {
if (browser.iOS || browser.osx) {
if (browser.iOS || browser.osx && browser.safari) {
return true;
}
} else if (format === 'mp2') {
@@ -294,19 +294,27 @@ import browser from './browser';
(browser.tizen && isTizenFhd ? 20000000 : null)));
}
let maxChannelCount = null;
function getSpeakerCount() {
if (maxChannelCount != null) {
return maxChannelCount;
}
maxChannelCount = -1;
const AudioContext = window.AudioContext || window.webkitAudioContext || false; /* eslint-disable-line compat/compat */
if (AudioContext) {
const audioCtx = new AudioContext();
return audioCtx.destination.maxChannelCount;
maxChannelCount = audioCtx.destination.maxChannelCount;
}
return -1;
return maxChannelCount;
}
function getPhysicalAudioChannels(options) {
function getPhysicalAudioChannels(options, videoTestElement) {
const allowedAudioChannels = parseInt(userSettings.allowedAudioChannels(), 10);
if (allowedAudioChannels > 0) {
@@ -318,8 +326,14 @@ import browser from './browser';
}
const isSurroundSoundSupportedBrowser = browser.safari || browser.chrome || browser.edgeChromium || browser.firefox || browser.tv || browser.ps4 || browser.xboxOne;
const isAc3Eac3Supported = supportsAc3(videoTestElement) || supportsEac3(videoTestElement);
const speakerCount = getSpeakerCount();
// AC3/EAC3 hinted that device is able to play dolby surround sound.
if (isAc3Eac3Supported && isSurroundSoundSupportedBrowser) {
return speakerCount > 6 ? speakerCount : 6;
}
if (speakerCount > 2) {
if (isSurroundSoundSupportedBrowser) {
return speakerCount;
@@ -339,15 +353,32 @@ import browser from './browser';
return 2;
}
/**
* Checks if the web engine supports secondary audio.
* @param {HTMLVideoElement} videoTestElement The video test element
* @returns {boolean} _true_ if the web engine supports secondary audio.
*/
export function canPlaySecondaryAudio(videoTestElement) {
// We rely on HTMLMediaElement.audioTracks
// It works in Chrome 79+ with "Experimental Web Platform features" enabled
return !!videoTestElement.audioTracks
// It doesn't work in Firefox 108 even with "media.track.enabled" enabled (it only sees the first audio track)
&& !browser.firefox
// It seems to work on Tizen 5.5+ (2020, Chrome 69+). See https://developer.tizen.org/forums/web-application-development/video-tag-not-work-audiotracks
&& (browser.tizenVersion >= 5.5 || !browser.tizen)
// Assume webOS 5+ (2020, Chrome 68+) supports secondary audio like Tizen 5.5+
&& (browser.web0sVersion >= 5.0 || !browser.web0sVersion);
}
export default function (options) {
options = options || {};
const physicalAudioChannels = getPhysicalAudioChannels(options);
const bitrateSetting = getMaxBitrate();
const videoTestElement = document.createElement('video');
const physicalAudioChannels = getPhysicalAudioChannels(options, videoTestElement);
const canPlayVp8 = videoTestElement.canPlayType('video/webm; codecs="vp8"').replace(/no/, '');
const canPlayVp9 = videoTestElement.canPlayType('video/webm; codecs="vp9"').replace(/no/, '');
const webmAudioCodecs = ['vorbis'];
@@ -466,7 +497,8 @@ import browser from './browser';
}
}
if (canPlayAudioFormat('flac')) {
// FLAC audio in video plays with a delay on Tizen
if (canPlayAudioFormat('flac') && !browser.tizen) {
videoAudioCodecs.push('flac');
hlsInFmp4VideoAudioCodecs.push('flac');
}
@@ -530,7 +562,6 @@ import browser from './browser';
}
if (canPlayVp8) {
mp4VideoCodecs.push('vp8');
webmVideoCodecs.push('vp8');
}
@@ -659,30 +690,6 @@ import browser from './browser';
});
});
if (canPlayMkv && !browser.tizen && options.enableMkvProgressive !== false) {
profile.TranscodingProfiles.push({
Container: 'mkv',
Type: 'Video',
AudioCodec: videoAudioCodecs.join(','),
VideoCodec: mp4VideoCodecs.join(','),
Context: 'Streaming',
MaxAudioChannels: physicalAudioChannels.toString(),
CopyTimestamps: true
});
}
if (canPlayMkv) {
profile.TranscodingProfiles.push({
Container: 'mkv',
Type: 'Video',
AudioCodec: videoAudioCodecs.join(','),
VideoCodec: mp4VideoCodecs.join(','),
Context: 'Static',
MaxAudioChannels: physicalAudioChannels.toString(),
CopyTimestamps: true
});
}
if (canPlayHls() && options.enableHls !== false) {
if (hlsInFmp4VideoCodecs.length && hlsInFmp4VideoAudioCodecs.length && userSettings.preferFmp4HlsContainer() && (browser.safari || browser.tizen || browser.web0s)) {
profile.TranscodingProfiles.push({
@@ -713,35 +720,11 @@ import browser from './browser';
}
}
if (webmAudioCodecs.length && webmVideoCodecs.length) {
profile.TranscodingProfiles.push({
Container: 'webm',
Type: 'Video',
AudioCodec: webmAudioCodecs.join(','),
// TODO: Remove workaround when servers migrate away from 'vpx' for transcoding profiles.
VideoCodec: (canPlayVp8 ? webmVideoCodecs.concat('vpx') : webmVideoCodecs).join(','),
Context: 'Streaming',
Protocol: 'http',
// If audio transcoding is needed, limit channels to number of physical audio channels
// Trying to transcode to 5 channels when there are only 2 speakers generally does not sound good
MaxAudioChannels: physicalAudioChannels.toString()
});
}
profile.TranscodingProfiles.push({
Container: 'mp4',
Type: 'Video',
AudioCodec: videoAudioCodecs.join(','),
VideoCodec: 'h264',
Context: 'Static',
Protocol: 'http'
});
profile.ContainerProfiles = [];
profile.CodecProfiles = [];
const supportsSecondaryAudio = browser.tizen || videoTestElement.audioTracks;
const supportsSecondaryAudio = canPlaySecondaryAudio(videoTestElement);
const aacCodecProfileConditions = [];
@@ -800,10 +783,9 @@ import browser from './browser';
maxH264Level = 52;
}
if (browser.tizen ||
videoTestElement.canPlayType('video/mp4; codecs="avc1.6e0033"').replace(/no/, '')) {
if (videoTestElement.canPlayType('video/mp4; codecs="avc1.6e0033"').replace(/no/, '')) {
// These tests are passing in safari, but playback is failing
if (!browser.safari && !browser.iOS && !browser.web0s && !browser.edge && !browser.mobile) {
if (!browser.safari && !browser.iOS && !browser.web0s && !browser.edge && !browser.mobile && !browser.tizen) {
h264Profiles += '|high 10';
}
}
@@ -838,6 +820,30 @@ import browser from './browser';
hevcProfiles = 'main|main 10';
}
const h264VideoRangeTypes = 'SDR';
let hevcVideoRangeTypes = 'SDR';
let vp9VideoRangeTypes = 'SDR';
let av1VideoRangeTypes = 'SDR';
if (browser.safari && ((browser.iOS && browser.iOSVersion >= 11) || browser.osx)) {
hevcVideoRangeTypes += '|HDR10|HLG';
if ((browser.iOS && browser.iOSVersion >= 13) || browser.osx) {
hevcVideoRangeTypes += '|DOVI';
}
}
if (browser.tizen || browser.web0s) {
hevcVideoRangeTypes += '|HDR10|HLG';
if (browser.web0s) hevcVideoRangeTypes += '|DOVI';
vp9VideoRangeTypes += '|HDR10|HLG';
av1VideoRangeTypes += '|HDR10|HLG';
}
if (browser.edgeChromium || browser.chrome || browser.firefox) {
vp9VideoRangeTypes += '|HDR10|HLG';
av1VideoRangeTypes += '|HDR10|HLG';
}
const h264CodecProfileConditions = [
{
Condition: 'NotEquals',
@@ -851,6 +857,12 @@ import browser from './browser';
Value: h264Profiles,
IsRequired: false
},
{
Condition: 'EqualsAny',
Property: 'VideoRangeType',
Value: h264VideoRangeTypes,
IsRequired: false
},
{
Condition: 'LessThanEqual',
Property: 'VideoLevel',
@@ -872,6 +884,12 @@ import browser from './browser';
Value: hevcProfiles,
IsRequired: false
},
{
Condition: 'EqualsAny',
Property: 'VideoRangeType',
Value: hevcVideoRangeTypes,
IsRequired: false
},
{
Condition: 'LessThanEqual',
Property: 'VideoLevel',
@@ -880,6 +898,24 @@ import browser from './browser';
}
];
const vp9CodecProfileConditions = [
{
Condition: 'EqualsAny',
Property: 'VideoRangeType',
Value: vp9VideoRangeTypes,
IsRequired: false
}
];
const av1CodecProfileConditions = [
{
Condition: 'EqualsAny',
Property: 'VideoRangeType',
Value: av1VideoRangeTypes,
IsRequired: false
}
];
if (!browser.edgeUwp && !browser.tizen && !browser.web0s) {
h264CodecProfileConditions.push({
Condition: 'NotEquals',
@@ -969,6 +1005,18 @@ import browser from './browser';
Conditions: hevcCodecProfileConditions
});
profile.CodecProfiles.push({
Type: 'Video',
Codec: 'vp9',
Conditions: vp9CodecProfileConditions
});
profile.CodecProfiles.push({
Type: 'Video',
Codec: 'av1',
Conditions: av1CodecProfileConditions
});
const globalVideoConditions = [];
if (globalMaxVideoBitrate) {

View File

@@ -1,5 +1,5 @@
import { af, arDZ, be, bg, bn, ca, cs, da, de, el, enGB, enUS, eo, es, faIR, fi, fr, frCA, he, hi, hr, hu, gl, id, is, it, ja, kk, ko, lt, ms, nb,
nl, pl, ptBR, pt, ro, ru, sk, sl, sv, ta, th, tr, uk, vi, zhCN, zhTW } from 'date-fns/locale';
import { af, arDZ, be, bg, bn, ca, cs, cy, da, de, el, enGB, enUS, eo, es, et, eu, faIR, fi, fr, frCA, gl, he, hi, hr, hu, id, is, it, ja, kk, ko, lt, lv, ms, nb,
nl, nn, pl, ptBR, pt, ro, ru, sk, sl, sv, ta, th, tr, uk, vi, zhCN, zhTW } from 'date-fns/locale';
import globalize from './globalize';
const dateLocales = (locale) => ({
@@ -10,6 +10,7 @@ const dateLocales = (locale) => ({
'bn': bn,
'ca': ca,
'cs': cs,
'cy': cy,
'da': da,
'de': de,
'el': el,
@@ -20,6 +21,8 @@ const dateLocales = (locale) => ({
'es-ar': es,
'es-do': es,
'es-mx': es,
'et': et,
'eu': eu,
'fa': faIR,
'fi': fi,
'fr': fr,
@@ -37,9 +40,11 @@ const dateLocales = (locale) => ({
'kk': kk,
'ko': ko,
'lt-lt': lt,
'lv': lv,
'ms': ms,
'nb': nb,
'nl': nl,
'nn': nn,
'pl': pl,
'pt': pt,
'pt-br': ptBR,
@@ -63,9 +68,14 @@ export function getLocale() {
return dateLocales(globalize.getCurrentLocale()) || dateLocales(globalize.getCurrentLocale().replace(/-.*/, '')) || enUS;
}
export const localeWithSuffix = { addSuffix: true, locale: getLocale() };
export function getLocaleWithSuffix() {
return {
addSuffix: true,
locale: getLocale()
};
}
export default {
getLocale: getLocale,
localeWithSuffix: localeWithSuffix
getLocaleWithSuffix
};

View File

@@ -38,7 +38,7 @@ const _GAMEPAD_LEFT_THUMBSTICK_UP_KEY = 'GamepadLeftThumbStickUp';
const _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEY = 'GamepadLeftThumbStickDown';
const _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEY = 'GamepadLeftThumbStickLeft';
const _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEY = 'GamepadLeftThumbStickRight';
const _GAMEPAD_A_KEYCODE = 0;
const _GAMEPAD_A_KEYCODE = 13;
const _GAMEPAD_B_KEYCODE = 27;
const _GAMEPAD_DPAD_UP_KEYCODE = 38;
const _GAMEPAD_DPAD_DOWN_KEYCODE = 40;

View File

@@ -1,7 +1,7 @@
import { Events } from 'jellyfin-apiclient';
import isEmpty from 'lodash-es/isEmpty';
import * as userSettings from './settings/userSettings';
import { currentSettings as userSettings } from './settings/userSettings';
/* eslint-disable indent */

View File

@@ -1,5 +1,6 @@
/* eslint-disable indent */
// audit note: this module is expected to return safe text for use in HTML
export function getDeviceIcon(device) {
const baseUrl = 'assets/img/devices/';
@@ -11,12 +12,16 @@
case 'Sony PS4':
return baseUrl + 'playstation.svg';
case 'Kodi':
case 'Kodi JellyCon':
return baseUrl + 'kodi.svg';
case 'Jellyfin Android':
case 'AndroidTV':
case 'Android TV':
return baseUrl + 'android.svg';
case 'Jellyfin Mobile (iOS)':
case 'Jellyfin Mobile (iPadOS)':
case 'Jellyfin iOS':
case 'Infuse':
return baseUrl + 'apple.svg';
case 'Jellyfin Web':
switch (device.Name || device.DeviceName) {

View File

@@ -265,6 +265,12 @@ function renderSection(page, item, element, type) {
function loadItems(element, item, type, query, listOptions) {
query = getQuery(query, item);
getItemsFunction(query, item)(query.StartIndex, query.Limit, query.Fields).then(function (result) {
// If results are empty, hide the section
if (!result.Items?.length) {
element.classList.add('hide');
return;
}
let html = '';
if (query.Limit && result.TotalRecordCount > query.Limit) {
@@ -327,7 +333,7 @@ function addCurrentItemToQuery(query, item) {
} else if (item.Type === 'Studio') {
query.StudioIds = item.Id;
} else if (item.Type === 'MusicArtist') {
query.ArtistIds = item.Id;
query.AlbumArtistIds = item.Id;
}
}

View File

@@ -3,6 +3,7 @@
* @module components/input/keyboardnavigation
*/
import browser from './browser';
import inputManager from './inputManager';
import layoutManager from '../components/layoutManager';
import appSettings from './settings/appSettings';
@@ -44,6 +45,16 @@ const KeyNames = {
*/
const NavigationKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
/**
* Elements for which navigation should be constrained.
*/
const InteractiveElements = ['INPUT', 'TEXTAREA'];
/**
* Types of INPUT element for which navigation shouldn't be constrained.
*/
const NonInteractiveInputElements = ['button', 'checkbox', 'color', 'file', 'hidden', 'image', 'radio', 'reset', 'submit'];
let hasFieldKey = false;
try {
hasFieldKey = 'key' in new KeyboardEvent('keydown');
@@ -78,6 +89,24 @@ export function isNavigationKey(key) {
return NavigationKeys.indexOf(key) != -1;
}
/**
* Returns _true_ if the element is interactive.
*
* @param {Element} element - Element.
* @return {boolean} _true_ if the element is interactive.
*/
export function isInteractiveElement(element) {
if (element && InteractiveElements.includes(element.tagName)) {
if (element.tagName === 'INPUT') {
return !NonInteractiveInputElements.includes(element.type);
}
return true;
}
return false;
}
export function enable() {
window.addEventListener('keydown', function (e) {
const key = getKeyName(e);
@@ -91,13 +120,21 @@ export function enable() {
switch (key) {
case 'ArrowLeft':
inputManager.handleCommand('left');
if (!isInteractiveElement(document.activeElement)) {
inputManager.handleCommand('left');
} else {
capture = false;
}
break;
case 'ArrowUp':
inputManager.handleCommand('up');
break;
case 'ArrowRight':
inputManager.handleCommand('right');
if (!isInteractiveElement(document.activeElement)) {
inputManager.handleCommand('right');
} else {
capture = false;
}
break;
case 'ArrowDown':
inputManager.handleCommand('down');
@@ -107,6 +144,15 @@ export function enable() {
inputManager.handleCommand('back');
break;
// HACK: Hisense TV (VIDAA OS) uses Backspace for Back action
case 'Backspace':
if (browser.tv && browser.hisense && browser.vidaa) {
inputManager.handleCommand('back');
} else {
capture = false;
}
break;
case 'Escape':
if (layoutManager.tv) {
inputManager.handleCommand('back');

View File

@@ -1,4 +1,6 @@
import escapeHtml from 'escape-html';
import Headroom from 'headroom.js';
import dom from './dom';
import layoutManager from '../components/layoutManager';
import inputManager from './inputManager';
@@ -12,13 +14,14 @@ import browser from './browser';
import globalize from './globalize';
import imageHelper from './imagehelper';
import { getMenuLinks } from '../scripts/settings/webSettings';
import Dashboard, { pageClassOn } from './clientUtils';
import ServerConnections from '../components/ServerConnections';
import '../elements/emby-button/paper-icon-button-light';
import 'material-design-icons-iconfont';
import '../assets/css/scrollstyles.scss';
import '../assets/css/flexstyles.scss';
import Dashboard, { pageClassOn } from './clientUtils';
import ServerConnections from '../components/ServerConnections';
import Headroom from 'headroom.js';
/* eslint-disable indent */
@@ -667,9 +670,8 @@ import Headroom from 'headroom.js';
if (customMenuOptions) {
getMenuLinks().then(links => {
links.forEach(link => {
const option = document.createElement('a');
option.setAttribute('is', 'emby-linkbutton');
option.className = 'navMenuOption lnkMediaFolder';
const option = document.createElement('a', 'emby-linkbutton');
option.classList.add('navMenuOption', 'lnkMediaFolder');
option.rel = 'noopener noreferrer';
option.target = '_blank';
option.href = link.url;

View File

@@ -92,6 +92,19 @@ class AppSettings {
return val ? parseInt(val) : null;
}
/**
* Get or set 'Maximum video width'
* @param {number|undefined} val - Maximum video width or undefined.
* @return {number} Maximum video width.
*/
maxVideoWidth(val) {
if (val !== undefined) {
return this.set('maxVideoWidth', val.toString());
}
return parseInt(this.get('maxVideoWidth') || '0', 10) || 0;
}
set(name, value, userId) {
const currentValue = this.get(name, userId);
AppStorage.setItem(this.#getKey(name, userId), value);

View File

@@ -166,19 +166,6 @@ export class UserSettings {
return val !== 'false';
}
/**
* Get or set 'SetUsingLastTracks' state.
* @param {boolean|undefined} val - Flag to enable 'SetUsingLastTracks' or undefined.
* @return {boolean} 'SetUsingLastTracks' state.
*/
enableSetUsingLastTracks(val) {
if (val !== undefined) {
return this.set('enableSetUsingLastTracks', val.toString());
}
return this.get('enableSetUsingLastTracks', false) !== 'false';
}
/**
* Get or set 'Theme Songs' state.
* @param {boolean|undefined} val - Flag to enable 'Theme Songs' or undefined.
@@ -468,7 +455,7 @@ export class UserSettings {
return this.set('enableRewatchingInNextUp', val, false);
}
return this.get('enableRewatchingInNextUp', false);
return this.get('enableRewatchingInNextUp', false) === 'true';
}
/**
@@ -570,7 +557,6 @@ export const allowedAudioChannels = currentSettings.allowedAudioChannels.bind(cu
export const preferFmp4HlsContainer = currentSettings.preferFmp4HlsContainer.bind(currentSettings);
export const enableCinemaMode = currentSettings.enableCinemaMode.bind(currentSettings);
export const enableNextVideoInfoOverlay = currentSettings.enableNextVideoInfoOverlay.bind(currentSettings);
export const enableSetUsingLastTracks = currentSettings.enableSetUsingLastTracks.bind(currentSettings);
export const enableThemeSongs = currentSettings.enableThemeSongs.bind(currentSettings);
export const enableThemeVideos = currentSettings.enableThemeVideos.bind(currentSettings);
export const enableFastFadein = currentSettings.enableFastFadein.bind(currentSettings);

View File

@@ -32,6 +32,7 @@ import '../components/playback/playerSelectionMenu';
import '../legacy/domParserTextHtml';
import '../legacy/focusPreventScroll';
import '../legacy/htmlMediaElement';
import '../legacy/keyboardEvent';
import '../legacy/vendorStyles';
import SyncPlay from '../components/syncPlay/core';
import { playbackManager } from '../components/playback/playbackmanager';
@@ -71,7 +72,7 @@ window.getParameterByName = function(name, url) {
};
function loadCoreDictionary() {
const languages = ['af', 'ar', 'be-by', 'bg-bg', 'bn_bd', 'ca', 'cs', 'da', 'de', 'el', 'en-gb', 'en-us', 'eo', 'es', 'es-419', 'es-ar', 'es_do', 'es-mx', 'fa', 'fi', 'fil', 'fr', 'fr-ca', 'gl', 'gsw', 'he', 'hi-in', 'hr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt-lt', 'mr', 'ms', 'nb', 'nl', 'pl', 'pr', 'pt', 'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl-si', 'sq', 'sv', 'ta', 'th', 'tr', 'uk', 'ur_pk', 'vi', 'zh-cn', 'zh-hk', 'zh-tw'];
const languages = ['af', 'ar', 'be-by', 'bg-bg', 'bn_bd', 'ca', 'cs', 'cy', 'da', 'de', 'el', 'en-gb', 'en-us', 'eo', 'es', 'es_419', 'es-ar', 'es_do', 'es-mx', 'et', 'eu', 'fa', 'fi', 'fil', 'fr', 'fr-ca', 'gl', 'gsw', 'he', 'hi-in', 'hr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt-lt', 'lv', 'mr', 'ms', 'nb', 'nl', 'nn', 'pl', 'pr', 'pt', 'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl-si', 'sq', 'sv', 'ta', 'th', 'tr', 'uk', 'ur_pk', 'vi', 'zh-cn', 'zh-hk', 'zh-tw'];
const translations = languages.map(function (language) {
return {
lang: language,
@@ -274,11 +275,24 @@ async function onAppReady() {
}
};
Events.on(ServerConnections, 'localusersignedin', handleStyleChange);
Events.on(ServerConnections, 'localusersignedout', handleStyleChange);
const handleLanguageChange = () => {
const locale = globalize.getCurrentLocale();
document.documentElement.setAttribute('lang', locale);
};
const handleUserChange = () => {
handleStyleChange();
handleLanguageChange();
};
Events.on(ServerConnections, 'localusersignedin', handleUserChange);
Events.on(ServerConnections, 'localusersignedout', handleUserChange);
Events.on(currentSettings, 'change', (e, prop) => {
if (prop == 'disableCustomCss' || prop == 'customCss') {
handleStyleChange();
} else if (prop == 'language') {
handleLanguageChange();
}
});

View File

@@ -32,7 +32,7 @@
"AllowOnTheFlySubtitleExtractionHelp": "Embedded subtitles can be extracted from videos and delivered to clients in plain text, in order to help prevent video transcoding. On some systems this can take a long time and cause video playback to stall during the extraction process. Disable this to have embedded subtitles burned in with video transcoding when they are not natively supported by the client device.",
"AllowRemoteAccess": "Allow remote connections to this server",
"AllowRemoteAccessHelp": "If unchecked, all remote connections will be blocked.",
"AllowTonemappingHelp": "Tone-mapping can transform the dynamic range of a video from HDR to SDR while maintaining image details and colors, which are very important information for representing the original scene. Currently works only with HDR10 or HLG videos. This requires the corresponding OpenCL or CUDA runtime.",
"AllowTonemappingHelp": "Tone-mapping can transform the dynamic range of a video from HDR to SDR while maintaining image details and colors, which are very important information for representing the original scene. Currently works only with 10bit HDR10HLG and DoVi videos. This requires the corresponding OpenCL or CUDA runtime.",
"AlwaysPlaySubtitles": "Always Play",
"AlwaysPlaySubtitlesHelp": "Subtitles matching the language preference will be loaded regardless of the audio language.",
"AnyLanguage": "Any Language",
@@ -333,6 +333,7 @@
"HeaderCodecProfileHelp": "Codec profiles indicate the limitations of a device when playing specific codecs. If a limitation applies then the media will be transcoded, even if the codec is configured for direct playback.",
"HeaderConfigureRemoteAccess": "Set up Remote Access",
"HeaderConfirmPluginInstallation": "Confirm Plugin Installation",
"HeaderConfirmRepositoryInstallation": "Confirm Plugin Repository Installation",
"HeaderConfirmProfileDeletion": "Confirm Profile Deletion",
"HeaderConfirmRevokeApiKey": "Revoke API Key",
"HeaderConnectionFailure": "Connection Failure",
@@ -359,7 +360,7 @@
"HeaderDirectPlayProfile": "Direct Playback Profile",
"HeaderDirectPlayProfileHelp": "Add direct playback profiles to indicate which formats the device can handle natively.",
"HeaderDownloadSync": "Download & Sync",
"HeaderDVR": "Digital Recorder",
"HeaderDVR": "DVR",
"HeaderEasyPinCode": "Easy PIN Code",
"HeaderEditImages": "Edit Images",
"HeaderEnabledFields": "Enabled Fields",
@@ -602,6 +603,7 @@
"LabelDefaultUser": "Default user:",
"LabelDefaultUserHelp": "Determine which user library should be displayed on connected devices. This can be overridden for each device using profiles.",
"LabelDeinterlaceMethod": "Deinterlacing method:",
"LabelDeveloper": "Developer",
"LabelDeviceDescription": "Device description:",
"LabelDidlMode": "DIDL mode:",
"LabelDisableCustomCss": "Disable custom CSS code for theming/branding provided from the server.",
@@ -666,7 +668,7 @@
"LabelFormat": "Format:",
"LabelFriendlyName": "Friendly name:",
"LabelGroupMoviesIntoCollections": "Group movies into collections",
"LabelGroupMoviesIntoCollectionsHelp": "If pick the movies list view, then the box sets will be shown as items with grouped movies.",
"LabelGroupMoviesIntoCollectionsHelp": "Movies in a collection will be displayed as one grouped item when displaying movie lists.",
"LabelH264Crf": "H.264 encoding CRF:",
"LabelH265Crf": "H.265 encoding CRF:",
"LabelHardwareAccelerationType": "Hardware acceleration:",
@@ -708,6 +710,7 @@
"LabelLibraryPageSizeHelp": "Set the amount of items to show on a library page. Set to 0 in order to disable paging.",
"LabelMaxDaysForNextUp": "Max days in 'Next Up':",
"LabelMaxDaysForNextUpHelp": "Set the maximum amount of days a show should stay in the 'Next Up' list without watching it.",
"LabelMaxVideoResolution": "Maximum Allowed Video Transcoding Resolution",
"LabelLineup": "Lineup:",
"LabelLocalCustomCss": "Custom CSS code for styling which applies to this client only. You may want to disable server custom CSS code.",
"LabelLocalHttpServerPortNumber": "Local HTTP port number:",
@@ -1077,10 +1080,11 @@
"MessagePleaseEnsureInternetMetadata": "Please ensure downloading of internet metadata is enabled.",
"MessagePleaseWait": "Please wait. This may take a minute.",
"MessagePluginConfigurationRequiresLocalAccess": "To set up this plugin please sign in to your local server directly.",
"MessagePluginInstallDisclaimer": "Plugins built by community members are a great way to enhance your experience with additional features and benefits. Before installing, please be aware of the effects they may have on your server, such as longer library scans, additional background processing, and decreased system stability.",
"MessagePluginInstallDisclaimer": "WARNING: Installing a third party plugin carries risks. It may contain unstable or malicious code, and may change at any time. Only install plugins from authors that you trust, and please be aware of the potential effects it may have, including external service queries, longer library scans, or additional background processing.",
"MessagePluginInstalled": "The plugin has been successfully installed. The server will need to be restarted for changes to take effect.",
"MessagePluginInstallError": "An error occurred while installing the plugin.",
"MessageReenableUser": "See below to reenable",
"MessageRepositoryInstallDisclaimer": "WARNING: Installing a third party plugin repository carries risks. It may contain unstable or malicious code, and may change at any time. Only install repositories from authors that you trust.",
"MessageSent": "Message sent.",
"MessageSyncPlayCreateGroupDenied": "Permission required to create a group.",
"MessageSyncPlayDisabled": "SyncPlay disabled.",
@@ -1295,6 +1299,7 @@
"PlayNextEpisodeAutomatically": "Play next episode automatically",
"PleaseAddAtLeastOneFolder": "Please add at least one folder to this library by clicking the '+' button in 'Folders' section.",
"PleaseConfirmPluginInstallation": "Please click OK to confirm you've read the above and wish to proceed with the plugin installation.",
"PleaseConfirmRepositoryInstallation": "Please click OK to confirm you've read the above and wish to proceed with the plugin repository installation.",
"PleaseEnterNameOrId": "Please enter a name or an external ID.",
"PleaseRestartServerName": "Please restart Jellyfin on {0}.",
"PleaseSelectTwoItems": "Please select at least two items.",
@@ -1353,7 +1358,11 @@
"RefreshQueued": "Refresh queued.",
"ReleaseDate": "Release date",
"ReleaseGroup": "Release Group",
"RememberAudioSelections": "Set audio track based on previous item",
"RememberAudioSelectionsHelp": "Try to set the audio track to the closest match to the last video.",
"RememberMe": "Remember Me",
"RememberSubtitleSelections": "Set subtitle track based on previous item",
"RememberSubtitleSelectionsHelp": "Try to set the subtitle track to the closest match to the last video.",
"Remixer": "Remixer",
"RemoveFromCollection": "Remove from collection",
"RemoveFromPlaylist": "Remove from playlist",
@@ -1377,6 +1386,7 @@
"ScanForNewAndUpdatedFiles": "Scan for new and updated files",
"ScanLibrary": "Scan library",
"Schedule": "Schedule",
"ScreenResolution": "Screen Resolution",
"Screenshot": "Screenshot",
"Screenshots": "Screenshots",
"Search": "Search",
@@ -1401,8 +1411,6 @@
"Settings": "Settings",
"SettingsSaved": "Settings saved.",
"SettingsWarning": "Changing these values may cause instability or connectivity failures. If you experience any problems, we recommend changing them back to default.",
"SetUsingLastTracks": "Set Subtitle/Audio Tracks with Previous Item",
"SetUsingLastTracksHelp": "Try to set the Subtitle/Audio track to the closest match to the last video.",
"Share": "Share",
"ShowAdvancedSettings": "Show advanced settings",
"ShowIndicatorsFor": "Show indicators for:",
@@ -1642,5 +1650,25 @@
"Scene": "Scene",
"Sample": "Sample",
"ThemeSong": "Theme Song",
"ThemeVideo": "Theme Video"
"ThemeVideo": "Theme Video",
"EnableSplashScreen": "Enable the splash screen",
"LabelVppTonemappingBrightness": "VPP Tone mapping brightness gain:",
"LabelVppTonemappingBrightnessHelp": "Apply brightness gain in VPP tone mapping. The recommended and default values are 16 and 0.",
"LabelVppTonemappingContrast": "VPP Tone mapping contrast gain:",
"LabelVppTonemappingContrastHelp": "Apply contrast gain in VPP tone mapping. Both recommended and default values are 1.",
"VideoRangeTypeNotSupported": "The video's range type is not supported",
"LabelVideoRangeType": "Video range type:",
"MediaInfoVideoRangeType": "Video range type",
"MediaInfoDoViTitle": "DV title",
"MediaInfoDvVersionMajor": "DV version major",
"MediaInfoDvVersionMinor": "DV version minor",
"MediaInfoDvProfile": "DV profile",
"MediaInfoDvLevel": "DV level",
"MediaInfoRpuPresentFlag": "DV rpu preset flag",
"MediaInfoElPresentFlag": "DV el preset flag",
"MediaInfoBlPresentFlag": "DV bl preset flag",
"MediaInfoDvBlSignalCompatibilityId": "DV bl signal compatibility id",
"LabelTonemappingMode": "Tone mapping mode:",
"TonemappingModeHelp": "Select the tone mapping mode. If you experience blown out highlights try switching to the RGB mode.",
"Unknown": "Unknown"
}

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