Compare commits

...

69 Commits

Author SHA1 Message Date
CodeDevMLH
538b0f2110 Update manifest.json for release v1.5.0.18 [skip ci] 2026-02-09 16:21:43 +00:00
CodeDevMLH
e717c07c54 Bump version to 1.5.0.18 and update changelog for recent changes
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 1m2s
2026-02-09 17:20:40 +01:00
CodeDevMLH
0fa2d01ca3 Update manifest.json for release v1.5.0.17 [skip ci] 2026-02-09 16:05:12 +00:00
CodeDevMLH
5299b2a9d5 Bump version to 1.5.0.17 and update changelog for recent changes
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 57s
2026-02-09 17:04:15 +01:00
CodeDevMLH
4f244988b9 Update manifest.json for release v1.5.0.16 [skip ci] 2026-02-09 15:54:25 +00:00
CodeDevMLH
5635a8f05e Bump version to 1.5.0.16 and update changelog for keyboard controls and sorting options
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-02-09 16:53:33 +01:00
CodeDevMLH
c901da4b0c Update manifest.json for release v1.5.0.15 [skip ci] 2026-02-09 15:41:45 +00:00
CodeDevMLH
c45cd0281f Bump version to 1.5.0.15 in project file and manifest.json
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 51s
2026-02-09 16:40:24 +01:00
CodeDevMLH
0c3e74829a Remove upstream trailer layout feature from configuration and UI 2026-02-09 16:40:01 +01:00
CodeDevMLH
e6b769f099 Update manifest.json for release v1.5.0.14 [skip ci] 2026-02-09 15:30:53 +00:00
CodeDevMLH
77371f7b98 Bump version to 1.5.0.14 and update changelog in manifest.json
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 58s
2026-02-09 16:29:56 +01:00
CodeDevMLH
988b800b6d Update manifest.json for release v1.5.0.13 [skip ci] 2026-02-09 15:21:45 +00:00
CodeDevMLH
4c6514ba9f Bump version to 1.5.0.13 and update changelog in manifest.json
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-02-09 16:20:52 +01:00
CodeDevMLH
6910eba7d6 Update manifest.json for release v1.5.0.12 [skip ci] 2026-02-09 15:10:07 +00:00
CodeDevMLH
3585b47b6c Bump version to 1.5.0.12 and update changelog in manifest.json
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 56s
2026-02-09 16:09:10 +01:00
CodeDevMLH
8170abdc94 Update manifest.json for release v1.5.0.11 [skip ci] 2026-02-09 14:56:05 +00:00
CodeDevMLH
535c0e17bf Add upstream trailer layout feature and update version to 1.5.0.11
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-02-09 15:55:14 +01:00
CodeDevMLH
df1cee0eeb Update manifest.json for release v1.5.0.10 [skip ci] 2026-02-09 13:31:00 +00:00
CodeDevMLH
16cc56030f Bump version to 1.5.0.10 in project file and manifest
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 55s
2026-02-09 14:30:05 +01:00
CodeDevMLH
13ecbac96e Update manifest.json for release v1.5.0.9 [skip ci] 2026-02-09 13:18:30 +00:00
CodeDevMLH
8bca6f9052 Bump version to 1.5.0.9 in project file and manifest
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 54s
2026-02-09 14:17:38 +01:00
CodeDevMLH
217db2a66d Fix video backdrop styling for full coverage and pointer events 2026-02-09 14:17:22 +01:00
CodeDevMLH
5fd7bcb8b6 Update manifest.json for release v1.5.0.8 [skip ci] 2026-02-09 13:11:30 +00:00
CodeDevMLH
0b0e41a9f9 Bump version to 1.5.0.8 in project file and manifest
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 56s
2026-02-09 14:10:35 +01:00
CodeDevMLH
370db55714 Enhance slideshow video styling for better positioning and responsiveness 2026-02-09 14:06:18 +01:00
CodeDevMLH
e5d4800ef1 Update manifest.json for release v1.5.0.7 [skip ci] 2026-02-09 12:28:45 +00:00
CodeDevMLH
b910e92364 Add spotlight.html: Initial implementation of Seth's Spotlight UI enhancement for Jellyfin with movie slideshow and trailer functionality
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 55s
2026-02-09 13:27:51 +01:00
CodeDevMLH
82283b1faf Merge branch 'main' of ssh://git.mahom03-spacecloud.de:44322/CodeDevMLH/jellyfin-plugin-media-bar-enhanced
Some checks failed
Auto Release Plugin / build-and-release (push) Has been cancelled
2026-02-09 13:27:36 +01:00
CodeDevMLH
1588b1a6b2 Add option to prefer local trailers over remote ones; update configuration and UI 2026-02-09 03:31:05 +01:00
CodeDevMLH
de19466341 Add script to fetch specific item details from Jellyfin API 2026-02-09 02:56:53 +01:00
CodeDevMLH
15054e314c refine sorting options description 2026-02-09 02:42:23 +01:00
CodeDevMLH
3877f96b09 Add sorting options for Production Year and Critic Rating; remove Date Created sorting 2026-02-09 02:39:42 +01:00
CodeDevMLH
abca7cb3b6 Refactor random item fetching logic and enhance error handling; add JSON download feature 2026-02-09 02:39:32 +01:00
CodeDevMLH
998a0cfc68 Add scripts to fetch random items from Jellyfin API with detailed logging [skip ci] 2026-02-09 02:21:12 +01:00
CodeDevMLH
63be8214d0 Update manifest.json for release v1.5.0.6 [skip ci] 2026-02-09 00:31:35 +00:00
CodeDevMLH
99411afffd Bump version to 1.5.0.6; update changelog and fix keyboard controls in TV mode
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 58s
2026-02-09 01:30:36 +01:00
CodeDevMLH
9510ae6ba7 Add sorting options for content in configuration and update sorting logic 2026-02-09 01:30:28 +01:00
CodeDevMLH
2030538acc Update manifest.json for release v1.5.0.5 [skip ci] 2026-02-08 02:20:04 +00:00
CodeDevMLH
463e9ef424 Bump version to 1.5.0.5; update keyboard controls and changelog
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 51s
2026-02-08 03:19:12 +01:00
CodeDevMLH
dbe9ce97d2 Update manifest.json for release v1.5.0.4 [skip ci] 2026-02-08 01:32:40 +00:00
CodeDevMLH
45c780c018 Bump version to 1.5.0.4 and update changelog for mediaBarEnhanced.js and manifest.json; enhance keyboard navigation controls
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 53s
2026-02-08 02:31:49 +01:00
CodeDevMLH
9de268caa7 , 2026-02-08 02:28:06 +01:00
CodeDevMLH
a8298461f9 Update manifest.json for release v1.5.0.3 [skip ci] 2026-02-08 00:41:45 +00:00
CodeDevMLH
aa369e5c7b Merge branch 'main' of ssh://git.mahom03-spacecloud.de:44322/CodeDevMLH/jellyfin-plugin-media-bar-enhanced
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 53s
2026-02-08 01:40:53 +01:00
CodeDevMLH
f62dc853be Bump version to 1.5.0.3 and update changelog for mediaBarEnhanced.js and manifest.json; improve keyboard controls in TV mode 2026-02-08 01:40:50 +01:00
CodeDevMLH
d0a7f5da8c Update manifest.json for release v1.5.0.2 [skip ci] 2026-02-08 00:10:29 +00:00
CodeDevMLH
1f140fd3c1 Bump version to 1.5.0.2 and update changelog for mediaBarEnhanced.js and manifest.json; enhance keyboard controls in TV mode
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-02-08 01:09:38 +01:00
CodeDevMLH
18474d4537 Update manifest.json for release v1.5.0.1 [skip ci] 2026-02-08 00:00:01 +00:00
CodeDevMLH
839424a960 Bump version to 1.5.0.1 and update changelog for mediaBarEnhanced.js and manifest.json; fix keyboard controls in TV mode
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 57s
2026-02-08 00:59:04 +01:00
CodeDevMLH
8050aa8e76 Update manifest.json for release v1.5.0.0 [skip ci] 2026-02-07 23:50:07 +00:00
CodeDevMLH
0c173b9685 Bump version to 1.5.0.0 and update changelog for mediaBarEnhanced.js and mediaBarEnhanced.css
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 56s
2026-02-08 00:49:12 +01:00
CodeDevMLH
90ecc7c0ef add without list.txt 2026-02-08 00:48:48 +01:00
CodeDevMLH
c80d3726a6 Enhance README.md with new features: added custom trailer URL option, client settings, and updated client platform support details. 2026-02-04 21:11:52 +01:00
CodeDevMLH
a8e63c935f Comment out seasonal manifest update steps in release automation workflow [skip ci] 2026-02-04 19:47:03 +01:00
CodeDevMLH
d931a5d589 Merge branch 'main' of ssh://git.mahom03-spacecloud.de:44322/CodeDevMLH/jellyfin-plugin-media-bar-enhanced 2026-02-04 19:08:14 +01:00
CodeDevMLH
629f13ab58 Comment out seasonal manifest update steps in release automation workflow 2026-02-04 19:08:13 +01:00
CodeDevMLH
6a91784401 Update manifest.json for release v1.4.0.12 [skip ci] 2026-02-04 18:07:40 +00:00
CodeDevMLH
3f8777db9f Bump version to 1.4.0.12
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 54s
2026-02-04 19:06:47 +01:00
CodeDevMLH
4ec337a893 Update button layout and text for improved clarity in settings UI 2026-02-04 19:05:27 +01:00
CodeDevMLH
7366f2ca30 Update button text for clarity in settings UI 2026-02-04 19:04:19 +01:00
CodeDevMLH
faabf13870 Update manifest.json for release v1.4.0.11 [skip ci] 2026-02-04 17:58:14 +00:00
CodeDevMLH
3e584fa419 Bump version to 1.4.0.11
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 54s
2026-02-04 18:57:23 +01:00
CodeDevMLH
13889c3c68 Enhance button layout in settings UI for better alignment and clarity 2026-02-04 18:57:09 +01:00
CodeDevMLH
7af9eb5004 Update manifest.json for release v1.4.0.10 [skip ci] 2026-02-04 17:51:26 +00:00
CodeDevMLH
1e57d8acef Bump version to 1.4.0.10
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 53s
2026-02-04 18:50:36 +01:00
CodeDevMLH
5964d2cb34 Enhance settings UI with reset functionality and improved button layout 2026-02-04 18:50:20 +01:00
CodeDevMLH
6f3fd23879 Update manifest.json for release v1.4.0.9 [skip ci] 2026-02-04 17:39:12 +00:00
CodeDevMLH
47949e9820 Bump version to 1.4.0.9
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 54s
2026-02-04 18:38:21 +01:00
CodeDevMLH
5fe8da8347 Update configuration descriptions for clarity and accuracy 2026-02-04 18:38:05 +01:00
14 changed files with 4277 additions and 192 deletions

View File

@@ -186,79 +186,79 @@ jobs:
fi
# Update Message in Seasonals Repository
- name: Checkout Seasonal Manifest Repo
uses: actions/checkout@v6
with:
repository: ${{ github.repository_owner }}/Jellyfin-Seasonals-Plugin
path: seasonal-manifest
token: ${{ secrets.JELLYFIN_PLUGIN_MANIFEST_UPDATER_PAT }}
# - name: Checkout Seasonal Manifest Repo
# uses: actions/checkout@v6
# with:
# repository: ${{ github.repository_owner }}/Jellyfin-Seasonals-Plugin
# path: seasonal-manifest
# token: ${{ secrets.JELLYFIN_PLUGIN_MANIFEST_UPDATER_PAT }}
- name: Update Seasonal Manifest
shell: bash
run: |
cd seasonal-manifest
# - name: Update Seasonal Manifest
# shell: bash
# run: |
# cd seasonal-manifest
REPO_OWNER="${{ github.repository_owner }}"
REPO_NAME="${{ github.event.repository.name }}"
# REPO_OWNER="${{ github.repository_owner }}"
# REPO_NAME="${{ github.event.repository.name }}"
# 1. Get info from previous steps
VERSION="${{ env.VERSION }}"
HASH="${{ env.ZIP_HASH }}"
TIME="${{ env.BUILD_TIME }}"
DOWNLOAD_URL="https://git.mahom03-spacecloud.de/$REPO_OWNER/$REPO_NAME/releases/download/v$VERSION/Jellyfin.Plugin.MediaBarEnhanced.zip"
# # 1. Get info from previous steps
# VERSION="${{ env.VERSION }}"
# HASH="${{ env.ZIP_HASH }}"
# TIME="${{ env.BUILD_TIME }}"
# DOWNLOAD_URL="https://git.mahom03-spacecloud.de/$REPO_OWNER/$REPO_NAME/releases/download/v$VERSION/Jellyfin.Plugin.MediaBarEnhanced.zip"
# 2. Get info from env
PLUGIN_GUID="${{ env.PLUGIN_GUID }}"
CHANGELOG="${{ env.CHANGELOG }}"
TARGET_ABI="${{ env.TARGET_ABI }}"
# # 2. Get info from env
# PLUGIN_GUID="${{ env.PLUGIN_GUID }}"
# CHANGELOG="${{ env.CHANGELOG }}"
# TARGET_ABI="${{ env.TARGET_ABI }}"
echo "Updating Seasonal Manifest for Plugin GUID: $PLUGIN_GUID"
# echo "Updating Seasonal Manifest for Plugin GUID: $PLUGIN_GUID"
# 3. Update/Prepend entry in seasonal manifest.json
# Logic:
# - If array is empty or new version != old version: PREPEND new entry
# - If new version == old version: OVERWRITE (update) existing entry (re-release)
# # 3. Update/Prepend entry in seasonal manifest.json
# # Logic:
# # - If array is empty or new version != old version: PREPEND new entry
# # - If new version == old version: OVERWRITE (update) existing entry (re-release)
jq --arg guid "$PLUGIN_GUID" \
--arg hash "$HASH" \
--arg time "$TIME" \
--arg url "$DOWNLOAD_URL" \
--arg ver "$VERSION" \
--arg changelog "$CHANGELOG" \
--arg abi "$TARGET_ABI" \
'map(if .guid == $guid then
if .versions[0].version != $ver then
# New Version -> Prepend
.versions = [{
"version": $ver,
"changelog": $changelog,
"targetAbi": $abi,
"sourceUrl": $url,
"checksum": $hash,
"timestamp": $time
}] + .versions
else
# Same Version -> Update existing (overwrite top)
.versions[0].changelog = $changelog |
.versions[0].targetAbi = $abi |
.versions[0].sourceUrl = $url |
.versions[0].checksum = $hash |
.versions[0].timestamp = $time
end
else . end)' \
manifest.json > manifest.json.tmp && mv manifest.json.tmp manifest.json
# jq --arg guid "$PLUGIN_GUID" \
# --arg hash "$HASH" \
# --arg time "$TIME" \
# --arg url "$DOWNLOAD_URL" \
# --arg ver "$VERSION" \
# --arg changelog "$CHANGELOG" \
# --arg abi "$TARGET_ABI" \
# 'map(if .guid == $guid then
# if .versions[0].version != $ver then
# # New Version -> Prepend
# .versions = [{
# "version": $ver,
# "changelog": $changelog,
# "targetAbi": $abi,
# "sourceUrl": $url,
# "checksum": $hash,
# "timestamp": $time
# }] + .versions
# else
# # Same Version -> Update existing (overwrite top)
# .versions[0].changelog = $changelog |
# .versions[0].targetAbi = $abi |
# .versions[0].sourceUrl = $url |
# .versions[0].checksum = $hash |
# .versions[0].timestamp = $time
# end
# else . end)' \
# manifest.json > manifest.json.tmp && mv manifest.json.tmp manifest.json
- name: Commit and Push Seasonal Manifest
run: |
cd seasonal-manifest
git config user.name "CodeDevMLH"
git config user.email "145071728+CodeDevMLH@users.noreply.github.com"
# - name: Commit and Push Seasonal Manifest
# run: |
# cd seasonal-manifest
# git config user.name "CodeDevMLH"
# git config user.email "145071728+CodeDevMLH@users.noreply.github.com"
# Check if there are changes
if [[ -n $(git status -s) ]]; then
git add manifest.json
git commit -m "Auto-Update MediaBar Enhanced to v${{ env.VERSION }}"
git push
else
echo "No changes to seasonal manifest."
fi
# # Check if there are changes
# if [[ -n $(git status -s) ]]; then
# git add manifest.json
# git commit -m "Auto-Update MediaBar Enhanced to v${{ env.VERSION }}"
# git push
# else
# echo "No changes to seasonal manifest."
# fi

View File

@@ -222,79 +222,79 @@ jobs:
fi
# Update Message in Seasonals Repository
- name: Checkout Seasonal Manifest Repo
uses: actions/checkout@v6
with:
repository: ${{ github.repository_owner }}/Jellyfin-Seasonals
path: seasonal-manifest
token: ${{ secrets.JELLYFIN_PLUGIN_MANIFEST_UPDATER_PAT }}
# - name: Checkout Seasonal Manifest Repo
# uses: actions/checkout@v6
# with:
# repository: ${{ github.repository_owner }}/Jellyfin-Seasonals
# path: seasonal-manifest
# token: ${{ secrets.JELLYFIN_PLUGIN_MANIFEST_UPDATER_PAT }}
- name: Update Seasonal Manifest
shell: bash
run: |
cd seasonal-manifest
# - name: Update Seasonal Manifest
# shell: bash
# run: |
# cd seasonal-manifest
REPO_OWNER="${{ github.repository_owner }}"
REPO_NAME="${{ github.event.repository.name }}"
# REPO_OWNER="${{ github.repository_owner }}"
# REPO_NAME="${{ github.event.repository.name }}"
# 1. Get info from previous steps
VERSION="${{ env.VERSION }}"
HASH="${{ env.ZIP_HASH }}"
TIME="${{ env.BUILD_TIME }}"
DOWNLOAD_URL="https://github.com/$REPO_OWNER/$REPO_NAME/releases/download/v$VERSION/Jellyfin.Plugin.MediaBarEnhanced.zip"
# # 1. Get info from previous steps
# VERSION="${{ env.VERSION }}"
# HASH="${{ env.ZIP_HASH }}"
# TIME="${{ env.BUILD_TIME }}"
# DOWNLOAD_URL="https://github.com/$REPO_OWNER/$REPO_NAME/releases/download/v$VERSION/Jellyfin.Plugin.MediaBarEnhanced.zip"
# 2. Get info from env
PLUGIN_GUID="${{ env.PLUGIN_GUID }}"
CHANGELOG="${{ env.CHANGELOG }}"
TARGET_ABI="${{ env.TARGET_ABI }}"
# # 2. Get info from env
# PLUGIN_GUID="${{ env.PLUGIN_GUID }}"
# CHANGELOG="${{ env.CHANGELOG }}"
# TARGET_ABI="${{ env.TARGET_ABI }}"
echo "Updating Seasonal Manifest for Plugin GUID: $PLUGIN_GUID"
# echo "Updating Seasonal Manifest for Plugin GUID: $PLUGIN_GUID"
# 3. Update/Prepend entry in seasonal manifest.json
# Logic:
# - If array is empty or new version != old version: PREPEND new entry
# - If new version == old version: OVERWRITE (update) existing entry (re-release)
# # 3. Update/Prepend entry in seasonal manifest.json
# # Logic:
# # - If array is empty or new version != old version: PREPEND new entry
# # - If new version == old version: OVERWRITE (update) existing entry (re-release)
jq --arg guid "$PLUGIN_GUID" \
--arg hash "$HASH" \
--arg time "$TIME" \
--arg url "$DOWNLOAD_URL" \
--arg ver "$VERSION" \
--arg changelog "$CHANGELOG" \
--arg abi "$TARGET_ABI" \
'map(if .guid == $guid then
if .versions[0].version != $ver then
# New Version -> Prepend
.versions = [{
"version": $ver,
"changelog": $changelog,
"targetAbi": $abi,
"sourceUrl": $url,
"checksum": $hash,
"timestamp": $time
}] + .versions
else
# Same Version -> Update existing (overwrite top)
.versions[0].changelog = $changelog |
.versions[0].targetAbi = $abi |
.versions[0].sourceUrl = $url |
.versions[0].checksum = $hash |
.versions[0].timestamp = $time
end
else . end)' \
manifest.json > manifest.json.tmp && mv manifest.json.tmp manifest.json
# jq --arg guid "$PLUGIN_GUID" \
# --arg hash "$HASH" \
# --arg time "$TIME" \
# --arg url "$DOWNLOAD_URL" \
# --arg ver "$VERSION" \
# --arg changelog "$CHANGELOG" \
# --arg abi "$TARGET_ABI" \
# 'map(if .guid == $guid then
# if .versions[0].version != $ver then
# # New Version -> Prepend
# .versions = [{
# "version": $ver,
# "changelog": $changelog,
# "targetAbi": $abi,
# "sourceUrl": $url,
# "checksum": $hash,
# "timestamp": $time
# }] + .versions
# else
# # Same Version -> Update existing (overwrite top)
# .versions[0].changelog = $changelog |
# .versions[0].targetAbi = $abi |
# .versions[0].sourceUrl = $url |
# .versions[0].checksum = $hash |
# .versions[0].timestamp = $time
# end
# else . end)' \
# manifest.json > manifest.json.tmp && mv manifest.json.tmp manifest.json
- name: Commit and Push Seasonal Manifest
run: |
cd seasonal-manifest
git config user.name "CodeDevMLH"
git config user.email "145071728+CodeDevMLH@users.noreply.github.com"
# - name: Commit and Push Seasonal Manifest
# run: |
# cd seasonal-manifest
# git config user.name "CodeDevMLH"
# git config user.email "145071728+CodeDevMLH@users.noreply.github.com"
# Check if there are changes
if [[ -n $(git status -s) ]]; then
git add manifest.json
git commit -m "Auto-Update MediaBar Enhanced to v${{ env.VERSION }}"
git push
else
echo "No changes to seasonal manifest."
fi
# # Check if there are changes
# if [[ -n $(git status -s) ]]; then
# git add manifest.json
# git commit -m "Auto-Update MediaBar Enhanced to v${{ env.VERSION }}"
# git push
# else
# echo "No changes to seasonal manifest."
# fi

View File

@@ -21,6 +21,7 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Configuration
public bool SlideAnimationEnabled { get; set; } = true;
public bool EnableVideoBackdrop { get; set; } = true;
public bool UseSponsorBlock { get; set; } = true;
public bool PreferLocalTrailers { get; set; } = false;
public bool WaitForTrailerToEnd { get; set; } = true;
public bool StartMuted { get; set; } = true;
public bool FullWidthVideo { get; set; } = true;
@@ -35,5 +36,7 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Configuration
public bool EnableSeasonalContent { get; set; } = false;
public bool IsEnabled { get; set; } = true;
public bool EnableClientSideSettings { get; set; } = false;
public string SortBy { get; set; } = "Random";
public string SortOrder { get; set; } = "Ascending";
}
}

View File

@@ -57,6 +57,14 @@
<div class="fieldDescription">Show trailers as background if available.<br>Adds a
mute/unmute and pause/play button to control the video in the right top corner.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription" id="PreferLocalTrailersContainer">
<label>
<input is="emby-checkbox" type="checkbox" id="PreferLocalTrailers"
name="PreferLocalTrailers" />
<span>Prefer Local Trailers</span>
</label>
<div class="fieldDescription">If enabled, local trailers will be preferred over remote (YouTube) trailers.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="WaitForTrailerToEnd"
@@ -163,7 +171,7 @@
<span>Enable Client-Side Settings</span>
</label>
<div class="fieldDescription">If enabled, users will see a media bar icon in the header to
override settings (like disabling the bar or video backdrops) locally on their device.</div>
override settings (like disabling the bar or trailer backdrops) locally on their device.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
@@ -274,6 +282,33 @@
mobile).</div>
</div>
<h2 class="sectionTitle">Content Sorting</h2>
<div class="selectContainer">
<label class="selectLabel" for="SortBy">Sort By</label>
<select is="emby-select" id="SortBy" name="SortBy" class="emby-select-withcolor emby-select">
<option value="Random">Random</option>
<option value="Original">Original (Custom List Order)</option>
<option value="PremiereDate">Premiere Date</option>
<option value="ProductionYear">Production Year</option>
<option value="CriticRating">Critic Rating</option>
<option value="CommunityRating">Community Rating</option>
<option value="Name">Name</option>
<option value="Runtime">Runtime</option>
</select>
<div class="fieldDescription">Sort items by the selected criteria.</div>
</div>
<div class="selectContainer">
<label class="selectLabel" for="SortOrder">Sort Order</label>
<select is="emby-select" id="SortOrder" name="SortOrder" class="emby-select-withcolor emby-select">
<option value="Ascending">Ascending</option>
<option value="Descending">Descending</option>
</select>
<div class="fieldDescription">Sort items in Ascending or Descending order.</div>
</div>
<div class="fieldDescription" style="margin-bottom: 2em; color: #ffcc00;">
<b>Note:</b> Sorting settings apply to both Server content and Custom IDs. 'Original' preserves Custom List order.
</div>
<h2 class="sectionTitle">Content Limits</h2>
<p>Leave a setting blank to use the default value.</p>
<div class="inputContainer">
@@ -370,7 +405,8 @@
'WaitForTrailerToEnd', 'StartMuted', 'FullWidthVideo', 'EnableMobileVideo',
'ShowTrailerButton', 'AlwaysShowArrows', 'EnableKeyboardControls',
'EnableCustomMediaIds', 'CustomMediaIds', 'EnableLoadingScreen',
'EnableSeasonalContent', 'EnableClientSideSettings'
'EnableSeasonalContent', 'EnableClientSideSettings', 'SortBy', 'SortOrder',
'PreferLocalTrailers'
];
keys.forEach(function (key) {
@@ -404,6 +440,23 @@
updateDesc();
}
// Handle Prefer Local Trailers visibility
var enableVideoBackdropCheckbox = page.querySelector('#EnableVideoBackdrop');
var preferLocalContainer = page.querySelector('#PreferLocalTrailersContainer');
function updatePreferLocalVisibility() {
if (enableVideoBackdropCheckbox && enableVideoBackdropCheckbox.checked) {
if (preferLocalContainer) preferLocalContainer.style.display = 'block';
} else {
if (preferLocalContainer) preferLocalContainer.style.display = 'none';
}
}
if (enableVideoBackdropCheckbox) {
enableVideoBackdropCheckbox.addEventListener('change', updatePreferLocalVisibility);
updatePreferLocalVisibility();
}
Dashboard.hideLoadingMsg();
});
},
@@ -419,7 +472,8 @@
'WaitForTrailerToEnd', 'StartMuted', 'FullWidthVideo', 'EnableMobileVideo',
'ShowTrailerButton', 'AlwaysShowArrows', 'EnableKeyboardControls',
'EnableCustomMediaIds', 'CustomMediaIds', 'EnableLoadingScreen',
'EnableSeasonalContent', 'EnableClientSideSettings'
'EnableSeasonalContent', 'EnableClientSideSettings', 'SortBy', 'SortOrder',
'PreferLocalTrailers'
];
keys.forEach(function (key) {

View File

@@ -12,7 +12,7 @@
<!-- <TreatWarningsAsErrors>false</TreatWarningsAsErrors> -->
<Title>Jellyfin Media Bar Enhanced Plugin</Title>
<Authors>CodeDevMLH</Authors>
<Version>1.4.0.8</Version>
<Version>1.5.0.18</Version>
<RepositoryUrl>https://github.com/CodeDevMLH/jellyfin-plugin-media-bar-enhanced</RepositoryUrl>
</PropertyGroup>

View File

@@ -161,7 +161,7 @@
.homeSectionsContainer {
position: relative;
top: 65vh;
margin-top: 65vh;
z-index: 6;
}

View File

@@ -1,5 +1,5 @@
/*
* Jellyfin Slideshow by M0RPH3US v3.0.9
* Jellyfin Slideshow by M0RPH3US v4.0.1
* Modified by CodeDevMLH v1.1.0.0
*
* New features:
@@ -38,6 +38,7 @@ const CONFIG = {
slideAnimationEnabled: true,
enableVideoBackdrop: true,
useSponsorBlock: true,
preferLocalTrailers: false,
waitForTrailerToEnd: true,
startMuted: true,
fullWidthVideo: true,
@@ -51,6 +52,8 @@ const CONFIG = {
customMediaIds: "",
enableLoadingScreen: true,
enableClientSideSettings: false,
sortBy: "Random",
sortOrder: "Ascending",
};
// State management
@@ -81,6 +84,7 @@ const STATE = {
sponsorBlockInterval: null,
isMuted: CONFIG.startMuted,
customTrailerUrls: {},
ytPromise: null,
},
};
@@ -459,6 +463,66 @@ waitForApiClientAndInitialize();
* Utility functions for slide creation and management
*/
const SlideUtils = {
/**
* Sorts items based on configuration
* @param {Array<Object>} items - Array of item objects
* @param {string} sortBy - Sort criteria
* @param {string} sortOrder - Sort order 'Ascending' or 'Descending'
* @returns {Array<Object>} Sorted array of items
*/
sortItems(items, sortBy, sortOrder) {
if (sortBy === 'Random' || sortBy === 'Original') {
return items;
}
const simpleCompare = (a, b) => {
if (a < b) return -1;
if (a > b) return 1;
return 0;
};
const sorted = [...items].sort((a, b) => {
let valA, valB;
switch (sortBy) {
case 'PremiereDate':
valA = new Date(a.PremiereDate).getTime();
valB = new Date(b.PremiereDate).getTime();
break;
case 'ProductionYear':
valA = a.ProductionYear || 0;
valB = b.ProductionYear || 0;
break;
case 'CriticRating':
valA = a.CriticRating || 0;
valB = b.CriticRating || 0;
break;
case 'CommunityRating':
valA = a.CommunityRating || 0;
valB = b.CommunityRating || 0;
break;
case 'Runtime':
valA = a.RunTimeTicks || 0;
valB = b.RunTimeTicks || 0;
break;
case 'Name':
valA = (a.Name || '').toLowerCase();
valB = (b.Name || '').toLowerCase();
break;
default:
return 0;
}
return simpleCompare(valA, valB);
});
if (sortOrder === 'Descending') {
sorted.reverse();
}
return sorted;
},
/**
* Shuffles array elements randomly
* @param {Array} array - Array to shuffle
@@ -582,23 +646,25 @@ const SlideUtils = {
* @returns {Promise<void>}
*/
loadYouTubeIframeAPI() {
return new Promise((resolve) => {
if (STATE.slideshow.ytPromise) return STATE.slideshow.ytPromise;
STATE.slideshow.ytPromise = new Promise((resolve) => {
if (window.YT && window.YT.Player) {
resolve();
resolve(window.YT);
return;
}
const tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
const previousOnYouTubeIframeAPIReady = window.onYouTubeIframeAPIReady;
window.onYouTubeIframeAPIReady = () => {
if (previousOnYouTubeIframeAPIReady) previousOnYouTubeIframeAPIReady();
resolve();
};
window.onYouTubeIframeAPIReady = () => resolve(window.YT);
if (!document.querySelector('script[src*="youtube.com/iframe_api"]')) {
const tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
}
});
return STATE.slideshow.ytPromise;
},
/**
@@ -957,8 +1023,8 @@ const ApiUtils = {
}
const response = await fetch(
`${STATE.jellyfinData.serverAddress}/Items/${itemId}`,
// `${STATE.jellyfinData.serverAddress}/Users/${STATE.jellyfinData.userId}/Items/${itemId}?Fields=Overview,RemoteTrailers,Genres,CommunityRating,CriticRating,OfficialRating,PremiereDate,RunTimeTicks,ProductionYear,MediaSources`,
// `${STATE.jellyfinData.serverAddress}/Items/${itemId}`,
`${STATE.jellyfinData.serverAddress}/Items/${itemId}?Fields=Overview,RemoteTrailers,Genres,CommunityRating,CriticRating,OfficialRating,PremiereDate,ProductionYear,MediaSources,RunTimeTicks,LocalTrailerCount`,
{
headers: this.getAuthHeaders(),
}
@@ -1030,8 +1096,16 @@ const ApiUtils = {
console.log("Fetching random items from server...");
let sortParams = `sortBy=${CONFIG.sortBy}`;
if (CONFIG.sortBy === 'Random' || CONFIG.sortBy === 'Original') {
sortParams = 'sortBy=Random';
} else {
sortParams += `&sortOrder=${CONFIG.sortOrder}`;
}
const response = await fetch(
`${STATE.jellyfinData.serverAddress}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&sortBy=Random&isPlayed=False&enableUserData=true&Limit=${CONFIG.maxItems}&fields=Id`,
`${STATE.jellyfinData.serverAddress}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&${sortParams}&isPlayed=False&enableUserData=true&Limit=${CONFIG.maxItems}&fields=Id`,
{
headers: this.getAuthHeaders(),
}
@@ -1250,6 +1324,39 @@ const ApiUtils = {
console.error(`Error fetching collection items for ${collectionId}:`, error);
return [];
}
},
/**
* Fetches the first local trailer for an item
* @param {string} itemId - Item ID
* @returns {Promise<string|null>} Stream URL or null
*/
async fetchLocalTrailer(itemId) {
try {
const response = await fetch(
`${STATE.jellyfinData.serverAddress}/Users/${STATE.jellyfinData.userId}/Items/${itemId}/LocalTrailers`,
{
headers: this.getAuthHeaders(),
}
);
if (!response.ok) {
return null;
}
const trailers = await response.json();
if (trailers && trailers.length > 0) {
const trailer = trailers[0];
const mediaSourceId = trailer.MediaSources && trailer.MediaSources[0] ? trailer.MediaSources[0].Id : trailer.Id;
// Construct stream URL
return `${STATE.jellyfinData.serverAddress}/Videos/${trailer.Id}/stream.mp4?Static=true&mediaSourceId=${mediaSourceId}&api_key=${STATE.jellyfinData.accessToken}`;
}
return null;
} catch (error) {
console.error(`Error fetching local trailer for ${itemId}:`, error);
return null;
}
}
};
@@ -1432,12 +1539,21 @@ const SlideCreator = {
let isVideo = false;
let trailerUrl = null;
// 1. Check for Remote Trailers (YouTube)
// Priority: Custom Config URL > Metadata RemoteTrailer
// 1. Check for Remote/Local Trailers
// Priority: Custom Config URL > (PreferLocal -> Local) > Metadata RemoteTrailer
// 1a. Custom URL override
if (STATE.slideshow.customTrailerUrls && STATE.slideshow.customTrailerUrls[itemId]) {
trailerUrl = STATE.slideshow.customTrailerUrls[itemId];
console.log(`Using custom trailer URL for ${itemId}: ${trailerUrl}`);
} else if (item.RemoteTrailers && item.RemoteTrailers.length > 0) {
}
// 1b. Check Local Trailer if preferred
else if (CONFIG.preferLocalTrailers && item.LocalTrailerCount > 0 && item.localTrailerUrl) {
trailerUrl = item.localTrailerUrl;
console.log(`Using local trailer for ${itemId}: ${trailerUrl}`);
}
// 1c. Fallback to Remote Trailer
else if (item.RemoteTrailers && item.RemoteTrailers.length > 0) {
trailerUrl = item.RemoteTrailers[0].Url;
}
@@ -1589,7 +1705,7 @@ const SlideCreator = {
autoplay: false,
preload: "auto",
loop: false,
style: "object-fit: cover; width: 100%; height: 100%; pointer-events: none;"
style: "object-fit: cover; object-position: center center; width: 100%; height: 100%; position: absolute; top: 0; left: 0; pointer-events: none;"
};
if (STATE.slideshow.isMuted) {
@@ -1931,6 +2047,11 @@ const SlideCreator = {
const item = await ApiUtils.fetchItemDetails(itemId);
// Pre-fetch local trailer URL if needed
if (CONFIG.preferLocalTrailers && item.LocalTrailerCount > 0) {
item.localTrailerUrl = await ApiUtils.fetchLocalTrailer(itemId);
}
const slideElement = this.createSlideElement(
item,
item.Type === "Movie" ? "Movie" : "TV Show"
@@ -2530,29 +2651,25 @@ const SlideshowManager = {
document.addEventListener("keydown", (e) => {
const container = document.getElementById("slides-container");
// Allow interaction if container is visible, even if not strictly focused
if (!container || container.style.display === "none") {
return;
}
const focusElement = document.activeElement;
const activeElement = document.activeElement;
const isSlideshowFocused = container.contains(activeElement) || activeElement === container;
if (!isSlideshowFocused) {
return;
}
switch (e.key) {
case "ArrowRight":
if (focusElement && focusElement.classList.contains("detail-button")) {
focusElement.previousElementSibling.focus();
} else {
SlideshowManager.nextSlide();
}
SlideshowManager.nextSlide();
e.preventDefault();
break;
case "ArrowLeft":
if (focusElement && focusElement.classList.contains("play-button")) {
focusElement.nextElementSibling.focus();
} else {
SlideshowManager.prevSlide();
}
SlideshowManager.prevSlide();
e.preventDefault();
break;
@@ -2568,8 +2685,15 @@ const SlideshowManager = {
break;
case "Enter":
if (focusElement) {
focusElement.click();
const currentItemId = STATE.slideshow.itemIds[STATE.slideshow.currentSlideIndex];
if (currentItemId) {
if (window.Emby && window.Emby.Page) {
Emby.Page.show(
`/details?id=${currentItemId}&serverId=${STATE.jellyfinData.serverId}`
);
} else {
window.location.href = `#/details?id=${currentItemId}&serverId=${STATE.jellyfinData.serverId}`;
}
}
e.preventDefault();
break;
@@ -2763,10 +2887,28 @@ const SlideshowManager = {
if (itemIds.length === 0) {
console.log("No custom list found, fetching random items from server...");
itemIds = await ApiUtils.fetchItemIdsFromServer();
if (CONFIG.sortBy === 'Random') {
itemIds = SlideUtils.shuffleArray(itemIds);
}
} else {
// Custom IDs
if (CONFIG.sortBy === 'Random') {
itemIds = SlideUtils.shuffleArray(itemIds);
} else if (CONFIG.sortBy !== 'Original') {
// Client-side sort required...
console.log(`Sorting ${itemIds.length} custom items by ${CONFIG.sortBy} ${CONFIG.sortOrder}`);
const itemsWithDetails = [];
for (const id of itemIds) {
const item = await ApiUtils.fetchItemDetails(id);
if (item) itemsWithDetails.push(item);
}
const sortedItems = SlideUtils.sortItems(itemsWithDetails, CONFIG.sortBy, CONFIG.sortOrder);
itemIds = sortedItems.map(i => i.Id);
}
}
itemIds = SlideUtils.shuffleArray(itemIds);
STATE.slideshow.itemIds = itemIds;
STATE.slideshow.totalItems = itemIds.length;
@@ -3028,10 +3170,10 @@ const MediaBarEnhancedSettingsManager = {
const settings = [
{ key: 'enabled', label: 'Enable Media Bar Enhanced', description: 'Toggle the entire media bar visibility.', default: true },
{ key: 'videoBackdrops', label: 'Enable Trailer Backdrops', description: 'Play trailers as background videos.', default: CONFIG.enableVideoBackdrop },
{ key: 'trailerButton', label: 'Show Trailer Button', description: 'Show button to play trailer in popup (backdrops without trailer)', default: CONFIG.showTrailerButton },
{ key: 'trailerButton', label: 'Show Trailer Button', description: 'Show button to play trailer in popup (only backdrops without trailer)', default: CONFIG.showTrailerButton },
{ key: 'mobileVideo', label: 'Enable Trailer On Mobile', description: 'Allow trailer backdrops on mobile devices.', default: CONFIG.enableMobileVideo },
{ key: 'waitForTrailer', label: 'Wait For Trailer To End', description: 'Wait for the trailer to finish before changing slides.', default: CONFIG.waitForTrailerToEnd },
{ key: 'slideAnimations', label: 'Enable Animations', description: 'Enable zooming-in effect on background images', default: CONFIG.slideAnimationEnabled },
{ key: 'slideAnimations', label: 'Enable Animations', description: 'Enable zooming-in effect (only on background images)', default: CONFIG.slideAnimationEnabled },
];
let html = '<h3 style="margin-top:0; margin-bottom:1em; border-bottom:1px solid #444; padding-bottom:0.5em;">Media Bar Settings</h3>';
@@ -3049,11 +3191,14 @@ const MediaBarEnhancedSettingsManager = {
`;
});
// Reload button
// Buttons Container
html += `
<div style="margin-top:1em; text-align:right;">
<div style="margin-top:1em; display:flex; justify-content:flex-end; align-items:center; gap:1.5em;">
<button is="emby-button" type="button" class="raised button-cancel emby-button" id="mb-settings-reset" title="Reset to Server Defaults">
<span>Load Server Defaults</span>
</button>
<button is="emby-button" type="button" class="raised button-submit emby-button" id="mb-settings-save">
<span>Reload</span>
<span>Save & Reload</span>
</button>
</div>
`;
@@ -3068,10 +3213,23 @@ const MediaBarEnhancedSettingsManager = {
});
});
// Reload Handler
popup.querySelector('#mb-settings-save').addEventListener('click', () => {
location.reload();
});
// Reset Handler
popup.querySelector('#mb-settings-reset').addEventListener('click', () => {
if (confirm("Reset all local Media Bar settings to server defaults?")) {
Object.keys(localStorage).forEach(key => {
if (key.startsWith('mediaBarEnhanced-')) {
localStorage.removeItem(key);
}
});
location.reload();
}
});
const closeHandler = (e) => {
if (!popup.contains(e.target) && e.target !== anchorElement && !anchorElement.contains(e.target)) {
popup.remove();
@@ -3093,6 +3251,84 @@ const MediaBarEnhancedSettingsManager = {
}
};
/**
* Initialize page visibility handling to pause when tab is inactive
*/
const initPageVisibilityHandler = () => {
let wasVideoPlayingBeforeHide = false;
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
console.log("Tab inactive - pausing slideshow and videos");
wasVideoPlayingBeforeHide = STATE.slideshow.isVideoPlaying;
if (STATE.slideshow.slideInterval) {
STATE.slideshow.slideInterval.stop();
}
// Pause active video if playing
const currentItemId = STATE.slideshow.itemIds[STATE.slideshow.currentSlideIndex];
if (currentItemId) {
// YouTube
if (STATE.slideshow.videoPlayers && STATE.slideshow.videoPlayers[currentItemId]) {
const player = STATE.slideshow.videoPlayers[currentItemId];
if (typeof player.pauseVideo === "function") {
try {
player.pauseVideo();
STATE.slideshow.isVideoPlaying = false;
} catch (e) {
console.warn("Error pausing video on tab hide:", e);
}
} else if (player.tagName === 'VIDEO') { // HTML5 Video
player.pause();
STATE.slideshow.isVideoPlaying = false;
}
}
}
} else {
console.log("Tab active - resuming slideshow");
if (!STATE.slideshow.isPaused) {
const currentItemId = STATE.slideshow.itemIds[STATE.slideshow.currentSlideIndex];
if (wasVideoPlayingBeforeHide && currentItemId && STATE.slideshow.videoPlayers && STATE.slideshow.videoPlayers[currentItemId]) {
const player = STATE.slideshow.videoPlayers[currentItemId];
// YouTube
if (typeof player.playVideo === "function") {
try {
player.playVideo();
STATE.slideshow.isVideoPlaying = true;
} catch (e) {
console.warn("Error resuming video on tab show:", e);
if (STATE.slideshow.slideInterval) {
STATE.slideshow.slideInterval.start();
}
}
} else if (player.tagName === 'VIDEO') { // HTML5 Video
try {
player.play().catch(e => console.warn("Error resuming HTML5 video:", e));
STATE.slideshow.isVideoPlaying = true;
} catch(e) { console.warn(e); }
}
} else {
// No video was playing, just restart interval
const activeSlide = document.querySelector('.slide.active');
const hasVideo = activeSlide && activeSlide.querySelector('.video-backdrop');
if (CONFIG.waitForTrailerToEnd && hasVideo) {
// Don't restart interval if waiting for trailer
} else {
if (STATE.slideshow.slideInterval) {
STATE.slideshow.slideInterval.start();
}
}
}
wasVideoPlayingBeforeHide = false;
}
}
});
};
/**
* Initialize the slideshow
*/
@@ -3206,6 +3442,8 @@ const slidesInit = async () => {
SlideshowManager.initKeyboardEvents();
initPageVisibilityHandler();
VisibilityObserver.init();
console.log("✅ Enhanced Jellyfin Slideshow initialized successfully");

View File

@@ -82,11 +82,21 @@ This plugin builds upon the original Media Bar with new capabilities and improve
* **Smarter Playback**:
* Option to wait for the trailer to end before advancing the slide.
* Mute/Unmute controls
* **Override Trailers**: Manually specify a custom trailer URL for any item via the Custom Media IDs list
* **Customization**:
* **Custom Media IDs**: Manually specify which items (Movies, Series, Collections/Boxsets) to display. Easily configurable via the plugin settings
* **Seasonal Content Mode**: Define date-based lists for holidays and seasons (e.g., Halloween, Christmas)
* Pagination dots turn into a counter (e.g., 1/20) if the limit is exceeded
<details>
<summary>Have a look:</summary>
<img width="167" height="142" alt="PagDots_Number" src="https://github.com/user-attachments/assets/6a0a5040-cf13-4d9c-ae96-f50ec249c3f1" />
</details>
* Option to disable the loading screen
* Client Settings: Optionally allow users to set selected media bar settings from their client.
<details>
<summary>Have a look:</summary>
<img width="513" height="575" alt="Client-Settings" src="https://github.com/user-attachments/assets/3e29a84f-f8ea-4b7b-b561-80493cb1535b" />
</details>
### Core Features
* **Immersive Slideshow**: Rotates through your media library
@@ -118,13 +128,14 @@ Because this plugin relies on injecting JavaScript and CSS into the web interfac
| Client Platform | Status | Notes |
| :--- | :---: | :--- |
| **Web Browsers** (Chrome, Firefox, Edge, etc.) | ✅ | Fully supported. |
| **Jellyfin Media Player** (Windows/Linux/macOS) | ✅ | Fully supported. |
| **Android App** | ✅ | Works (Web wrapper). |
| **iOS App** | ✅ | Works (Web wrapper). |
| **Android TV / Fire TV** | ❌ | **Not supported** (Native UI). |
| **Roku** | ❌ | **Not supported** (Native UI). |
| **Swiftfin** | ❌ | **Not supported** (Native UI). |
| **Web Browsers** (Firefox, Chrome etc.) | ✅ | Direct JS injection |
| **Jellyfin Media Player** (Windows/Linux/macOS) | ✅ | Uses jellyfin web |
| **Android App** | ✅ | Uses a web wrapper |
| **iOS App** | ✅ | Uses a web wrapper |
| **Android TV / Fire TV** | ❌ | **Not supported.** Uses a native Java/Kotlin UI. |
| **Roku** | ❌ | **Not supported.** Uses a native UI. |
| **Swiftfin** (iOS/tvOS) | ❌ | **Not supported.** Uses a native Swift UI. |
| **Kodi** (via Jellyfin Addon) | ❌ | **Not supported.** Uses Kodi's native skinning engine. |
## Configuration

View File

@@ -9,12 +9,12 @@
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/raw/branch/main/logo.png",
"versions": [
{
"version": "1.4.0.8",
"changelog": "- feat: Add client-side settings feature for selected media bar settings",
"version": "1.5.0.18",
"changelog": "- fix: keyboard controls in TV mode\n- Add sorting options for content\n- Update mediaBarEnhanced.js and mediaBarEnhanced.css with version 4.0.1 from original repo",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.4.0.8/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "ec343204a7cd2c1af4013e645bdddcd3",
"timestamp": "2026-02-04T17:27:22Z"
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.5.0.18/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "13d18e5d2498f73324d09bf796788e94",
"timestamp": "2026-02-09T16:21:41Z"
},
{
"version": "1.3.0.3",

View File

@@ -0,0 +1,32 @@
(async () => {
const apiClient = window.ApiClient;
if (!apiClient) { console.error("Logged in?"); return; }
try {
// Fetch 1 random item ID
const rnd = await apiClient.getItems(apiClient.getCurrentUserId(), { SortBy: "Random", Limit: 1, Recursive: true, IncludeItemTypes: "Movie,Series" });
if (rnd.Items.length > 0) {
const id = rnd.Items[0].Id;
console.log("Random Item ID:", id);
// Fetch Default Details
const defd = await apiClient.getItem(apiClient.getCurrentUserId(), id);
console.log("Default Fields:", defd);
// Fetch ALL Known Fields manually
const allFields = "Chapters,People,MediaStreams,UserData,RecursiveItemCount,DateCreated,MediaSources,ProductionYear,Studios,Genres,Tags,RemoteTrailers,ProviderIds,Overview,CommunityRating,CriticRating,OfficialRating,PremiereDate,RunTimeTicks";
const full = await res.json();
console.log("Full Details:", full);
// Helper to download JSON
const blob = new Blob([JSON.stringify(full, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `jellyfin-item-${id}.json`;
a.click();
URL.revokeObjectURL(url);
console.log("Downloaded JSON file.");
} else { console.warn("No items."); }
} catch (e) { console.error(e); }
})();

View File

@@ -0,0 +1,32 @@
(async () => {
// 1. Get Auth Data from the active client
const apiClient = window.ApiClient;
if (!apiClient) {
console.error("ApiClient not found. Are you logged in?");
return;
}
try {
console.log("Fetching random item...");
// 2. Fetch 1 random item
// const result = await apiClient.getItems(apiClient.getCurrentUserId(), { SortBy: "Random", Limit: 1, Recursive: true, IncludeItemTypes: "Movie,Series" });
const result = await apiClient.getItems(apiClient.getCurrentUserId(), {
SortBy: "Random",
Limit: 1,
Recursive: true,
IncludeItemTypes: "Movie,Series", // Optional: filter types
Fields: "Overview,RemoteTrailers,Genres,CommunityRating,CriticRating,OfficialRating,PremiereDate,RunTimeTicks,ProductionYear,MediaSources" // Request ALL fields
});
if (result.Items.length > 0) {
const item = result.Items[0];
console.log("Random Item Found:", item.Name);
console.dir(item); // Prints the full interactive object
} else {
console.warn("No items found.");
}
} catch (error) {
console.error("Error fetching item:", error);
}
})();

View File

@@ -0,0 +1,28 @@
(async () => {
const apiClient = window.ApiClient;
if (!apiClient) {
console.error("ApiClient nicht gefunden.");
return;
}
// Die ID des Items, das du abrufen möchtest
const itemId = "DEINE_ITEM_ID_HIER";
const userId = apiClient.getCurrentUserId();
try {
console.log(`Rufe Details für Item ${itemId} ab...`);
// Nutze getItem() statt getItems()
// Parameter: userId, itemId
const item = await apiClient.getItem(userId, itemId);
if (item) {
console.log("Item Details gefunden:", item.Name);
console.dir(item); // Zeigt alle Metadaten (Genres, Pfade, ProviderIds, etc.)
} else {
console.warn("Item konnte nicht gefunden werden.");
}
} catch (error) {
console.error("Fehler beim Abrufen des Items:", error);
}
})();

View File

@@ -0,0 +1,462 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Media Bar Enhanced Configuration</title>
</head>
<body>
<div id="MediaBarEnhancedConfigurationPage" data-role="page" class="page type-interior pluginConfigurationPage"
data-require="emby-input,emby-button,emby-select,emby-checkbox,emby-textarea">
<div data-role="content">
<div class="content-primary">
<div class="sectionTitleContainer">
<h2 class="sectionTitle">Media Bar Enhanced</h2>
<a is="emby-linkbutton" class="raised raised-mini emby-button" style="margin-left: 2em;"
target="_blank" href="https://github.com/CodeDevMLH/jellyfin-plugin-media-bar-enhanced">
<i class="md-icon button-icon button-icon-left secondaryText"></i>
<span>${Help}</span>
</a>
</div>
<hr style="max-width: 800px; margin: 1em 0;">
<div style="margin-bottom: 1.5em;">
<button class="jellyfin-tab-button active" onclick="showTab('basic', this)"
style="background: none; border: none; color: #fff; cursor: pointer; transition: color 0.3s, border-bottom 0.3s; padding: 0.5em 1em; border-bottom: 2px solid #00a4dc;">
<h3>General Settings</h3>
</button>
<button class="jellyfin-tab-button" onclick="showTab('custom', this)"
style="background: none; border: none; color: #ccc; cursor: pointer; transition: color 0.3s, border-bottom 0.3s; padding: 0.5em 1em; border-bottom: 2px solid transparent;">
<h3>Custom Content</h3>
</button>
<button class="jellyfin-tab-button" onclick="showTab('advanced', this)"
style="background: none; border: none; color: #ccc; cursor: pointer; transition: color 0.3s, border-bottom 0.3s; padding: 0.5em 1em; border-bottom: 2px solid transparent;">
<h3>Advanced Settings</h3>
</button>
</div>
<form id="mediaBarEnhancedConfigForm">
<!-- BASIC TAB -->
<div id="basic" class="tab-content">
<h2 class="sectionTitle">Main Plugin Settings</h2>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="IsEnabled" name="IsEnabled" />
<span>Enable Media Bar Enhanced Plugin</span>
</label>
<div class="fieldDescription">Enable or disable the entire plugin functionality.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="EnableVideoBackdrop"
name="EnableVideoBackdrop" />
<span>Enable Trailer Backdrops</span>
</label>
<div class="fieldDescription">Show trailers as background if available.<br>Adds a
mute/unmute and pause/play button to control the video in the right top corner.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="WaitForTrailerToEnd"
name="WaitForTrailerToEnd" />
<span>Wait For Trailer To End</span>
</label>
<div class="fieldDescription">Delay slide transition until trailer finishes.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="EnableMobileVideo"
name="EnableMobileVideo" />
<span>Enable Trailer On Mobile</span>
</label>
<div class="fieldDescription">Allow video playback on mobile devices.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="ShowTrailerButton"
name="ShowTrailerButton" />
<span>Show Trailer Button</span>
</label>
<div class="fieldDescription">Display a button to open trailer in modal. Only visible if
trailer is not set as backdrop or if no trailer is available.</div>
</div>
</div>
<!-- CUSTOM CONTENT TAB -->
<div id="custom" class="tab-content" style="display:none;">
<h2 class="sectionTitle">Custom Media IDs</h2>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="EnableCustomMediaIds"
name="EnableCustomMediaIds" />
<span>Enable Custom Media IDs</span>
</label>
<div class="fieldDescription">If enabled, the slideshow will try to show the items listed
below. If the list is empty, default behavior (random items) is used. Disable this
to temporarily ignore your custom list without deleting it.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="EnableSeasonalContent"
name="EnableSeasonalContent" />
<span>Enable Seasonal Content Mode</span>
</label>
<div class="fieldDescription">Enable this to define time-based lists in the field below.
</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="CustomMediaIds">Media/Collection/Playlist
IDs
(Newline or Comma separated)</label>
<textarea is="emby-textarea" id="CustomMediaIds" name="CustomMediaIds"
style="width: 100%; height: 150px; font-family: monospace;"></textarea>
<div class="fieldDescription" id="customMediaIdsDesc">Enter the IDs of the items you want to show in the slideshow.
You can separate them by new line or comma.
<br><br>
<b>Manual Trailer Override:</b> You can specify a YouTube URL for an item by adding it in
brackets: <br> <code>e.g. ID DESCRIPTION [https://youtu.be/...]</code> or <code>ID [https://youtu.be/...] DESCRIPTION</code>
<br><br>
You can also add a description after the ID using any separator like space, pipe
(|) or dash (-): <br>e.g. <code>ID DESCRIPTION</code> or <code>ID | DESCRIPTION</code>
<br><br>
<b>Note:</b> If using a <b>Collection Name</b> (instead of an ID) combined with a description, you <b>MUST</b> use
the pipe (|) separator.
<br>
<b>Note:</b> The separator <b>MUST NOT</b> be a hex character (0-9, a-f).</div>
<div class="fieldDescription" id="seasonalMediaIdsDesc" style="display: none;">
<b>Seasonal Mode Enabled:</b> Define lines with date ranges (Format: DD.MM-DD.MM |
<i>name</i> | <i>IDs</i>).<br>
Example:<br>
<code>20.10-31.10 | Halloween | ID1, ID2 [https://youtu.be/...]</code><br>
<code>01.12-26.12 | Christmas | ID3, ID4</code><br>
<i>Only lines matching the current date will be used. If no line matches, it will try to
use random items.</i>
</div>
<p>You can find the IDs of your items in the URL of the item page in the web interface.<br>
Example:
<code>https://your-jellyfin-url/web/#/details?id=<b style="color:red;">your-item-id</b>&serverId=your-server-id</code><br><br>
You can also insert a name of a collection or playlist to fetch the IDs of all items in
it (will take the first hit.<br><b>Note:</b> there is currently no feedback if the name
resolution succeeded, you will have to look if the bar displays the correct items.).
</p>
</div>
</div>
<!-- ADVANCED TAB -->
<div id="advanced" class="tab-content" style="display:none;">
<h2 class="sectionTitle">Features</h2>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="SlideAnimationEnabled"
name="SlideAnimationEnabled" />
<span>Enable Slide Animations</span>
</label>
<div class="fieldDescription">Enable the zooming-in effect on background images when a new slide is
shown (does not affect trailer backdrops). Attention: This may cause performance issues on weaker client hardware.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="EnableClientSideSettings"
name="EnableClientSideSettings" />
<span>Enable Client-Side Settings</span>
</label>
<div class="fieldDescription">If enabled, users will see a media bar icon in the header to
override settings (like disabling the bar or trailer backdrops) locally on their device.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="UseSponsorBlock" name="UseSponsorBlock" />
<span>Use SponsorBlock</span>
</label>
<div class="fieldDescription">Skip intro/outro segments in YouTube trailers.</div>
</div>
<div class="selectContainer">
<label class="selectLabel" for="PreferredVideoQuality">Preferred YouTube Quality</label>
<select is="emby-select" id="PreferredVideoQuality" name="PreferredVideoQuality"
class="emby-select-withcolor emby-select">
<option value="Auto">Auto (Smart)</option>
<option value="Maximum">Maximum (4K+)</option>
<option value="1080p">1080p</option>
<option value="720p">720p</option>
</select>
<div class="fieldDescription">"Auto" selects Maximum if screen width > 1920px, otherwise
1080p.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="StartMuted" name="StartMuted" />
<span>Start Muted</span>
</label>
<div class="fieldDescription">Start trailer video playback muted. (Known issue: In the
Android/IOS app, backdrop trailers are always muted.)</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="FullWidthVideo" name="FullWidthVideo" />
<span>Full Width Video</span>
</label>
<div class="fieldDescription">Stretch video to full width. Very nice on desktops, on mobile
devices only the middle of the video is visible.<br>Disable to get the full aspect ratio
on
mobile devices. (looks bad on desktops)</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="EnableLoadingScreen"
name="EnableLoadingScreen" />
<span>Enable Loading Screen</span>
</label>
<div class="fieldDescription">Show a loading screen while the slideshow initializes. (You
may have to reload the page twice)</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="AlwaysShowArrows"
name="AlwaysShowArrows" />
<span>Always Show Arrows</span>
</label>
<div class="fieldDescription">If enabled, navigation arrows will always be visible instead
of only on hover.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="EnableKeyboardControls"
name="EnableKeyboardControls" />
<span>Enable Keyboard Controls</span>
</label>
<div class="fieldDescription">Enable keyboard shortcuts (Arrows left/right (change slide),
Space (pause), M (mute/unmute)) for
the slideshow.</div>
</div>
<h2 class="sectionTitle">Time Settings</h2>
<p>Leave a setting blank to use the default value.</p>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="ShuffleInterval">Shuffle Interval
(ms)</label>
<input is="emby-input" type="number" id="ShuffleInterval" name="ShuffleInterval" />
<div class="fieldDescription">Time in milliseconds between changing slides.</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="RetryInterval">Retry Interval
(ms)</label>
<input is="emby-input" type="number" id="RetryInterval" name="RetryInterval" />
<div class="fieldDescription">Time in milliseconds to wait before retrying failed
operations.</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="LoadingCheckInterval">Loading Check
Interval (ms)</label>
<input is="emby-input" type="number" id="LoadingCheckInterval"
name="LoadingCheckInterval" />
<div class="fieldDescription">Frequency of checking wether the login screen or home screen
has loaded (in milliseconds).</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="FadeTransitionDuration">Fade
Transition Duration (ms)</label>
<input is="emby-input" type="number" id="FadeTransitionDuration"
name="FadeTransitionDuration" />
<div class="fieldDescription">Duration in milliseconds of the transition between slides.
</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="MinSwipeDistance">Min Swipe Distance
(px)</label>
<input is="emby-input" type="number" id="MinSwipeDistance" name="MinSwipeDistance" />
<div class="fieldDescription">Minimum distance in pixels for a swipe to be registered (for
mobile).</div>
</div>
<h2 class="sectionTitle">Content Limits</h2>
<p>Leave a setting blank to use the default value.</p>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="MaxItems">Total Max Items</label>
<input is="emby-input" type="number" id="MaxItems" name="MaxItems" />
<div class="fieldDescription">Maximum total items to fetch (for all content types combined).
</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="MaxMovies">Max Movies</label>
<input is="emby-input" type="number" id="MaxMovies" name="MaxMovies" />
<div class="fieldDescription">Maximum movies to include in slideshow (for random selection).
</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="MaxTvShows">Max TV Shows</label>
<input is="emby-input" type="number" id="MaxTvShows" name="MaxTvShows" />
<div class="fieldDescription">Maximum TV shows to include in slideshow (for random
selection).</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="PreloadCount">Preload Count</label>
<input is="emby-input" type="number" id="PreloadCount" name="PreloadCount" />
<div class="fieldDescription">Number of images to preload.</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="MaxPaginationDots">Max Pagination
Dots</label>
<input is="emby-input" type="number" id="MaxPaginationDots" name="MaxPaginationDots" />
<div class="fieldDescription">Maximum number of dots to show in navigation. If the number
will be exceeded, the dots will "turn" into a counter style (e.g. 1/100). Set to 0 to
enable counter style directly.</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="MaxPlotLength">Max Plot
Length</label>
<input is="emby-input" type="number" id="MaxPlotLength" name="MaxPlotLength" />
<div class="fieldDescription">Maximum characters for the plot summary.</div>
</div>
</div>
<div
style="background-color: rgba(255, 255, 255, 0.05); border-left: 4px solid #00a4dc; border-radius: 4px; padding: 1em 1.5em; margin: 1.5em 0; display: flex; align-items: center; gap: 1em;">
<i class="material-icons" style="color: #00a4dc; font-size: 24px;">info</i>
<div>
All changes require a page refresh (ctrl + r or F5) after saving for changes to take effect.
<br />
If old settings persist, please force clear browser cache.
</div>
</div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block emby-button">
<span>${Save}</span>
</button>
<button is="emby-button" type="button" class="raised button-cancel block btnCancel"
onclick="history.back();">
<span>${ButtonCancel}</span>
</button>
</div>
</form>
</div>
</div>
<script>
function showTab(tabId, btn) {
document.querySelectorAll('.tab-content').forEach(el => el.style.display = 'none');
document.getElementById(tabId).style.display = 'block';
document.querySelectorAll('.jellyfin-tab-button').forEach(b => {
b.classList.remove('active');
b.style.color = '#ccc';
b.style.borderBottom = '2px solid transparent';
});
if (btn) {
btn.classList.add('active');
btn.style.color = '#fff';
btn.style.borderBottom = '2px solid #00a4dc';
}
}
var MediaBarEnhancedConfigurationPage = {
pluginId: 'd7e11d57-819b-4bdd-a88d-53c5f5560225',
loadConfiguration: function (page) {
Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(MediaBarEnhancedConfigurationPage.pluginId).then(function (config) {
var keys = [
'IsEnabled', 'ShuffleInterval', 'RetryInterval', 'MinSwipeDistance',
'LoadingCheckInterval', 'MaxPlotLength', 'MaxMovies', 'MaxTvShows',
'MaxItems', 'PreloadCount', 'FadeTransitionDuration', 'MaxPaginationDots',
'SlideAnimationEnabled', 'EnableVideoBackdrop', 'UseSponsorBlock',
'WaitForTrailerToEnd', 'StartMuted', 'FullWidthVideo', 'EnableMobileVideo',
'ShowTrailerButton', 'AlwaysShowArrows', 'EnableKeyboardControls',
'EnableCustomMediaIds', 'CustomMediaIds', 'EnableLoadingScreen',
'EnableSeasonalContent', 'EnableClientSideSettings'
];
keys.forEach(function (key) {
var el = page.querySelector('#' + key);
if (el) {
if (el.type === 'checkbox') {
el.checked = config[key];
} else {
el.value = config[key];
}
}
});
// Handle Seasonal UI logic
var seasonalCheckbox = page.querySelector('#EnableSeasonalContent');
var normalDesc = page.querySelector('#customMediaIdsDesc');
var seasonalDesc = page.querySelector('#seasonalMediaIdsDesc');
function updateDesc() {
if (seasonalCheckbox && seasonalCheckbox.checked) {
if (normalDesc) normalDesc.style.display = 'none';
if (seasonalDesc) seasonalDesc.style.display = 'block';
} else {
if (normalDesc) normalDesc.style.display = 'block';
if (seasonalDesc) seasonalDesc.style.display = 'none';
}
}
if (seasonalCheckbox) {
seasonalCheckbox.addEventListener('change', updateDesc);
updateDesc();
}
Dashboard.hideLoadingMsg();
});
},
saveConfiguration: function (page) {
Dashboard.showLoadingMsg();
var config = {};
var keys = [
'IsEnabled', 'ShuffleInterval', 'RetryInterval', 'MinSwipeDistance',
'LoadingCheckInterval', 'MaxPlotLength', 'MaxMovies', 'MaxTvShows',
'MaxItems', 'PreloadCount', 'FadeTransitionDuration', 'MaxPaginationDots',
'SlideAnimationEnabled', 'EnableVideoBackdrop', 'UseSponsorBlock',
'WaitForTrailerToEnd', 'StartMuted', 'FullWidthVideo', 'EnableMobileVideo',
'ShowTrailerButton', 'AlwaysShowArrows', 'EnableKeyboardControls',
'EnableCustomMediaIds', 'CustomMediaIds', 'EnableLoadingScreen',
'EnableSeasonalContent', 'EnableClientSideSettings'
];
keys.forEach(function (key) {
var el = page.querySelector('#' + key);
if (el) {
if (el.type === 'checkbox') {
config[key] = el.checked;
} else {
config[key] = (el.type === 'number') ? parseInt(el.value, 10) : el.value;
}
}
});
ApiClient.updatePluginConfiguration(MediaBarEnhancedConfigurationPage.pluginId, config).then(function (result) {
Dashboard.processPluginConfigurationUpdateResult(result);
});
}
};
document.querySelector('#MediaBarEnhancedConfigurationPage').addEventListener('pageshow', function () {
MediaBarEnhancedConfigurationPage.loadConfiguration(this);
});
document.querySelector('#mediaBarEnhancedConfigForm').addEventListener('submit', function (e) {
e.preventDefault();
MediaBarEnhancedConfigurationPage.saveConfiguration(document.querySelector('#MediaBarEnhancedConfigurationPage'));
return false;
});
</script>
<style>
.jellyfin-tab-button.active {
color: #fff !important;
border-bottom: 2px solid #00a4dc !important;
}
</style>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff