Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05128ebc7e | ||
|
|
3bad812e96 | ||
|
|
1872a744a7 | ||
|
|
83441a3c4f | ||
|
|
49ad41c3f6 | ||
|
|
984a41b180 | ||
|
|
c8421d12b0 | ||
|
|
b5d38629d2 | ||
|
|
8f4adece49 | ||
|
|
cfebccc289 | ||
|
|
2613fd7acf | ||
|
|
bb10b9c15e | ||
|
|
a9ad8d65e4 | ||
|
|
e7232da38b | ||
|
|
53bd1d23c7 | ||
|
|
5643ebfd28 | ||
|
|
bed2fbaa76 | ||
|
|
05f64bc3d7 | ||
|
|
277433581c | ||
|
|
44f0b1cb38 | ||
|
|
d31dcbabdc | ||
|
|
4ed6509505 | ||
|
|
6427b8422a | ||
|
|
ebfbe1d563 | ||
|
|
67e5d8e4d2 | ||
|
|
d68ba7e846 | ||
|
|
0444cf333d | ||
|
|
10279e97d9 | ||
|
|
f6ccd0ea5f | ||
|
|
47827df047 | ||
|
|
7fd781c9d0 | ||
|
|
6faa8f1a4c | ||
|
|
3e05ff1dc9 | ||
|
|
fa06179cd3 | ||
|
|
d8327fc5c9 | ||
|
|
9ffe03f0df | ||
|
|
37e99d7fed | ||
|
|
9b0e3762ac | ||
|
|
48f93e3480 | ||
|
|
2e9c093cdc | ||
|
|
60593dc855 | ||
|
|
0edde43720 | ||
|
|
0f6938a91d | ||
|
|
fe661925e0 | ||
|
|
5fbe60c27a | ||
|
|
89bde9233d | ||
|
|
24ac119e01 | ||
|
|
1ee4aaefb5 | ||
|
|
d737bc9422 | ||
|
|
2a154aaf92 | ||
|
|
561a4254b2 | ||
|
|
b8a0c7f589 | ||
|
|
10e02eeb3c | ||
|
|
f39200544d | ||
|
|
5173f66449 | ||
|
|
965942f63b | ||
|
|
c251cf7e70 | ||
|
|
8699e0b3e2 | ||
|
|
5db2157232 | ||
|
|
822f572006 | ||
|
|
e6637b34f7 | ||
|
|
1acaff6552 | ||
|
|
58188ca094 | ||
|
|
37b30aef1a | ||
|
|
f0370ac57f | ||
|
|
0663b7d9e4 | ||
|
|
e3213f72cc |
@@ -120,11 +120,14 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd central-manifest
|
cd central-manifest
|
||||||
|
|
||||||
|
REPO_OWNER="${{ github.repository_owner }}"
|
||||||
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
|
|
||||||
# 1. Get info from previous steps
|
# 1. Get info from previous steps
|
||||||
VERSION="${{ env.VERSION }}"
|
VERSION="${{ env.VERSION }}"
|
||||||
HASH="${{ env.ZIP_HASH }}"
|
HASH="${{ env.ZIP_HASH }}"
|
||||||
TIME="${{ env.BUILD_TIME }}"
|
TIME="${{ env.BUILD_TIME }}"
|
||||||
DOWNLOAD_URL="https://git.mahom03-spacecloud.de/${{ github.repository }}/releases/download/v$VERSION/Jellyfin.Plugin.MediaBarEnhanced.zip"
|
DOWNLOAD_URL="https://git.mahom03-spacecloud.de/$REPO_OWNER/$REPO_NAME/releases/download/v$VERSION/Jellyfin.Plugin.MediaBarEnhanced.zip"
|
||||||
|
|
||||||
# 2. Get info from env
|
# 2. Get info from env
|
||||||
PLUGIN_GUID="${{ env.PLUGIN_GUID }}"
|
PLUGIN_GUID="${{ env.PLUGIN_GUID }}"
|
||||||
@@ -186,20 +189,23 @@ jobs:
|
|||||||
- name: Checkout Seasonal Manifest Repo
|
- name: Checkout Seasonal Manifest Repo
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
repository: ${{ github.repository_owner }}/jellyfin-plugin-manifest
|
repository: ${{ github.repository_owner }}/Jellyfin-Seasonals-Plugin
|
||||||
path: seasonal-manifest
|
path: seasonal-manifest
|
||||||
token: ${{ secrets.JELLYFIN_PLUGIN_MANIFEST_UPDATER_PAT }}
|
token: ${{ secrets.JELLYFIN_PLUGIN_MANIFEST_UPDATER_PAT }}
|
||||||
|
|
||||||
- name: Update Central Manifest
|
- name: Update Seasonal Manifest
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cd seasonal-manifest
|
cd seasonal-manifest
|
||||||
|
|
||||||
|
REPO_OWNER="${{ github.repository_owner }}"
|
||||||
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
|
|
||||||
# 1. Get info from previous steps
|
# 1. Get info from previous steps
|
||||||
VERSION="${{ env.VERSION }}"
|
VERSION="${{ env.VERSION }}"
|
||||||
HASH="${{ env.ZIP_HASH }}"
|
HASH="${{ env.ZIP_HASH }}"
|
||||||
TIME="${{ env.BUILD_TIME }}"
|
TIME="${{ env.BUILD_TIME }}"
|
||||||
DOWNLOAD_URL="https://git.mahom03-spacecloud.de/${{ github.repository }}/releases/download/v$VERSION/Jellyfin.Plugin.MediaBarEnhanced.zip"
|
DOWNLOAD_URL="https://git.mahom03-spacecloud.de/$REPO_OWNER/$REPO_NAME/releases/download/v$VERSION/Jellyfin.Plugin.MediaBarEnhanced.zip"
|
||||||
|
|
||||||
# 2. Get info from env
|
# 2. Get info from env
|
||||||
PLUGIN_GUID="${{ env.PLUGIN_GUID }}"
|
PLUGIN_GUID="${{ env.PLUGIN_GUID }}"
|
||||||
|
|||||||
50
.github/workflows/release_automation.yml
vendored
50
.github/workflows/release_automation.yml
vendored
@@ -24,6 +24,8 @@ jobs:
|
|||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v5
|
uses: actions/setup-dotnet@v5
|
||||||
@@ -94,6 +96,40 @@ jobs:
|
|||||||
commit_message: "Update manifest.json for release v${{ env.VERSION }} [skip ci]"
|
commit_message: "Update manifest.json for release v${{ env.VERSION }} [skip ci]"
|
||||||
file_pattern: manifest.json
|
file_pattern: manifest.json
|
||||||
|
|
||||||
|
- name: Generate Commit Log
|
||||||
|
if: success()
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "Generating commit log since last tag..."
|
||||||
|
|
||||||
|
# Get the previous tag
|
||||||
|
PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [ -z "$PREV_TAG" ]; then
|
||||||
|
echo "No previous tag found. Getting all commits."
|
||||||
|
start_range=""
|
||||||
|
else
|
||||||
|
echo "Previous tag: $PREV_TAG"
|
||||||
|
start_range="$PREV_TAG.."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "### What's Changed" > commit_log.md
|
||||||
|
echo "" >> commit_log.md
|
||||||
|
git log --pretty=format:"- %s (%h) by @%an" $start_range >> commit_log.md
|
||||||
|
|
||||||
|
# Combine Changelog from manifest (if exists) and commit log
|
||||||
|
if [ -n "${{ env.CHANGELOG }}" ]; then
|
||||||
|
echo "${{ env.CHANGELOG }}" > release_body.txt
|
||||||
|
echo "" >> release_body.txt
|
||||||
|
echo "" >> release_body.txt
|
||||||
|
cat commit_log.md >> release_body.txt
|
||||||
|
else
|
||||||
|
cat commit_log.md > release_body.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Debug output
|
||||||
|
cat release_body.txt
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
@@ -103,7 +139,9 @@ jobs:
|
|||||||
files: ${{ env.ZIP_PATH }}
|
files: ${{ env.ZIP_PATH }}
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
generate_release_notes: true
|
generate_release_notes: false
|
||||||
|
body_path: release_body.txt
|
||||||
|
|
||||||
|
|
||||||
# Update Message in Remote Repository
|
# Update Message in Remote Repository
|
||||||
- name: Checkout Central Manifest Repo
|
- name: Checkout Central Manifest Repo
|
||||||
@@ -118,6 +156,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd central-manifest
|
cd central-manifest
|
||||||
|
|
||||||
|
REPO_OWNER="${{ github.repository_owner }}"
|
||||||
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
|
|
||||||
# 1. Get info from previous steps
|
# 1. Get info from previous steps
|
||||||
VERSION="${{ env.VERSION }}"
|
VERSION="${{ env.VERSION }}"
|
||||||
HASH="${{ env.ZIP_HASH }}"
|
HASH="${{ env.ZIP_HASH }}"
|
||||||
@@ -184,15 +225,18 @@ jobs:
|
|||||||
- name: Checkout Seasonal Manifest Repo
|
- name: Checkout Seasonal Manifest Repo
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
repository: ${{ github.repository_owner }}/jellyfin-plugin-manifest
|
repository: ${{ github.repository_owner }}/Jellyfin-Seasonals
|
||||||
path: seasonal-manifest
|
path: seasonal-manifest
|
||||||
token: ${{ secrets.JELLYFIN_PLUGIN_MANIFEST_UPDATER_PAT }}
|
token: ${{ secrets.JELLYFIN_PLUGIN_MANIFEST_UPDATER_PAT }}
|
||||||
|
|
||||||
- name: Update Central Manifest
|
- name: Update Seasonal Manifest
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cd seasonal-manifest
|
cd seasonal-manifest
|
||||||
|
|
||||||
|
REPO_OWNER="${{ github.repository_owner }}"
|
||||||
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
|
|
||||||
# 1. Get info from previous steps
|
# 1. Get info from previous steps
|
||||||
VERSION="${{ env.VERSION }}"
|
VERSION="${{ env.VERSION }}"
|
||||||
HASH="${{ env.ZIP_HASH }}"
|
HASH="${{ env.ZIP_HASH }}"
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,7 +4,5 @@ obj/
|
|||||||
.idea/
|
.idea/
|
||||||
artifacts
|
artifacts
|
||||||
|
|
||||||
example-plugins/
|
|
||||||
|
|
||||||
*.md
|
*.md
|
||||||
!README.md
|
!README.md
|
||||||
@@ -30,7 +30,8 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Configuration
|
|||||||
public bool EnableKeyboardControls { get; set; } = true;
|
public bool EnableKeyboardControls { get; set; } = true;
|
||||||
public bool AlwaysShowArrows { get; set; } = false;
|
public bool AlwaysShowArrows { get; set; } = false;
|
||||||
public string CustomMediaIds { get; set; } = "";
|
public string CustomMediaIds { get; set; } = "";
|
||||||
public bool EnableCustomMediaIds { get; set; } = false;
|
public bool EnableCustomMediaIds { get; set; } = true;
|
||||||
|
public string PreferredVideoQuality { get; set; } = "Auto";
|
||||||
public bool EnableSeasonalContent { get; set; } = false;
|
public bool EnableSeasonalContent { get; set; } = false;
|
||||||
public bool IsEnabled { get; set; } = true;
|
public bool IsEnabled { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<a is="emby-linkbutton" class="raised raised-mini emby-button" style="margin-left: 2em;"
|
<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">
|
target="_blank" href="https://github.com/CodeDevMLH/jellyfin-plugin-media-bar-enhanced">
|
||||||
<i class="md-icon button-icon button-icon-left secondaryText"></i>
|
<i class="md-icon button-icon button-icon-left secondaryText"></i>
|
||||||
<span>Help</span>
|
<span>${Help}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<hr style="max-width: 800px; margin: 1em 0;">
|
<hr style="max-width: 800px; margin: 1em 0;">
|
||||||
@@ -93,8 +93,9 @@
|
|||||||
name="EnableCustomMediaIds" />
|
name="EnableCustomMediaIds" />
|
||||||
<span>Enable Custom Media IDs</span>
|
<span>Enable Custom Media IDs</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="fieldDescription">If enabled, the slideshow will ONLY show the items listed
|
<div class="fieldDescription">If enabled, the slideshow will try to show the items listed
|
||||||
below.</div>
|
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>
|
||||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||||
<label>
|
<label>
|
||||||
@@ -108,28 +109,35 @@
|
|||||||
<div class="inputContainer">
|
<div class="inputContainer">
|
||||||
<label class="inputLabel inputLabelUnfocused" for="CustomMediaIds">Media/Collection/Playlist
|
<label class="inputLabel inputLabelUnfocused" for="CustomMediaIds">Media/Collection/Playlist
|
||||||
IDs
|
IDs
|
||||||
(Comma or Newline separated)</label>
|
(Newline or Comma separated)</label>
|
||||||
<textarea is="emby-textarea" id="CustomMediaIds" name="CustomMediaIds"
|
<textarea is="emby-textarea" id="CustomMediaIds" name="CustomMediaIds"
|
||||||
style="width: 100%; height: 150px; font-family: monospace;"></textarea>
|
style="width: 100%; height: 150px; font-family: monospace;"></textarea>
|
||||||
<div class="fieldDescription" id="customMediaIdsDesc">Enter the IDs of the items you want to
|
<div class="fieldDescription" id="customMediaIdsDesc">Enter the IDs of the items you want to show in the slideshow.
|
||||||
show in the slideshow.
|
You can separate them by new line or comma.
|
||||||
You can separate them by comma or new line.</div>
|
<br>
|
||||||
|
<b>Manual Trailer Override:</b> You can specify a YouTube URL for an item by adding it in
|
||||||
|
brackets: <code>ID [https://youtu.be/...]</code>
|
||||||
|
<br>
|
||||||
|
You can also add a description after the ID using any separator like space, pipe
|
||||||
|
(|) or dash (-) (e.g. <code>ID | Description</code>).
|
||||||
|
<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.
|
||||||
|
<b>Note:</b> The separator MUST NOT be a hex character (0-9, a-f).</div>
|
||||||
<div class="fieldDescription" id="seasonalMediaIdsDesc" style="display: none;">
|
<div class="fieldDescription" id="seasonalMediaIdsDesc" style="display: none;">
|
||||||
<b>Seasonal Mode Enabled:</b> Define lines with date ranges (Format: DD.MM-DD.MM |
|
<b>Seasonal Mode Enabled:</b> Define lines with date ranges (Format: DD.MM-DD.MM |
|
||||||
<i>name</i> | <i>IDs</i>).<br>
|
<i>name</i> | <i>IDs</i>).<br>
|
||||||
Example:<br>
|
Example:<br>
|
||||||
<code>20.10-31.10 | Halloween | ID1, ID2</code><br>
|
<code>20.10-31.10 | Halloween | ID1, ID2 [https://youtu.be/...]</code><br>
|
||||||
<code>01.12-26.12 | Christmas | ID3, ID4</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
|
<i>Only lines matching the current date will be used. If no line matches, it will try to
|
||||||
fetch the list.txt or use random items.</i>
|
fetch the list.txt or use random items.</i>
|
||||||
</div>
|
</div>
|
||||||
<p>You can find the IDs of your items in the URL of the item page in the web interface.<br>
|
<p>You can find the IDs of your items in the URL of the item page in the web interface.<br>
|
||||||
Example:
|
Example:
|
||||||
<code>https://your-jellyfin-url/web/#/details?id=<b style="color:red;">your-item-id</b>&serverId=your-server-id</code><br>
|
<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
|
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. Note: there is currently no feedback if the name resolution
|
it (will take the first hit.<br>Note: there is currently no feedback if the name
|
||||||
succeeded, you
|
resolution succeeded, you will have to look if the bar displays the correct items.).
|
||||||
will have to look if the bar displays the correct items.).
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -153,6 +161,18 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="fieldDescription">Skip intro/outro segments in YouTube trailers.</div>
|
<div class="fieldDescription">Skip intro/outro segments in YouTube trailers.</div>
|
||||||
</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">
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||||
<label>
|
<label>
|
||||||
<input is="emby-checkbox" type="checkbox" id="StartMuted" name="StartMuted" />
|
<input is="emby-checkbox" type="checkbox" id="StartMuted" name="StartMuted" />
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<!-- <TreatWarningsAsErrors>false</TreatWarningsAsErrors> -->
|
<!-- <TreatWarningsAsErrors>false</TreatWarningsAsErrors> -->
|
||||||
<Title>Jellyfin Media Bar Enhanced Plugin</Title>
|
<Title>Jellyfin Media Bar Enhanced Plugin</Title>
|
||||||
<Authors>CodeDevMLH</Authors>
|
<Authors>CodeDevMLH</Authors>
|
||||||
<Version>1.0.0.3</Version>
|
<Version>1.3.0.1</Version>
|
||||||
<RepositoryUrl>https://github.com/CodeDevMLH/jellyfin-plugin-media-bar-enhanced</RepositoryUrl>
|
<RepositoryUrl>https://github.com/CodeDevMLH/jellyfin-plugin-media-bar-enhanced</RepositoryUrl>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ namespace Jellyfin.Plugin.MediaBarEnhanced
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Injects the script tag into index.html if it's not already present.
|
/// Injects the script tag into index.html if it's not already present.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>True if injection was successful or already present, false otherwise.</returns>
|
|
||||||
public void Inject()
|
public void Inject()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -101,6 +100,11 @@ namespace Jellyfin.Plugin.MediaBarEnhanced
|
|||||||
_logger.LogInformation("MediaBarEnhanced script and CSS already present in index.html. Or could not be injected.");
|
_logger.LogInformation("MediaBarEnhanced script and CSS already present in index.html. Or could not be injected.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Unauthorized access when attempting to inject script into index.html. Automatic injection failed. Attempting fallback now...");
|
||||||
|
RegisterFileTransformation();
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error injecting MediaBarEnhanced resources. Attempting fallback.");
|
_logger.LogError(ex, "Error injecting MediaBarEnhanced resources. Attempting fallback.");
|
||||||
@@ -148,8 +152,15 @@ namespace Jellyfin.Plugin.MediaBarEnhanced
|
|||||||
{
|
{
|
||||||
File.WriteAllText(indexPath, content);
|
File.WriteAllText(indexPath, content);
|
||||||
_logger.LogInformation("MediaBarEnhanced script removed from index.html.");
|
_logger.LogInformation("MediaBarEnhanced script removed from index.html.");
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
_logger.LogInformation("MediaBarEnhanced script not found in index.html. No removal necessary.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (UnauthorizedAccessException uaEx)
|
||||||
|
{
|
||||||
|
_logger.LogError(uaEx, "Unauthorized access when trying to remove MediaBarEnhanced script. Check file permissions.");
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error removing MediaBarEnhanced script.");
|
_logger.LogError(ex, "Error removing MediaBarEnhanced script.");
|
||||||
@@ -170,7 +181,6 @@ namespace Jellyfin.Plugin.MediaBarEnhanced
|
|||||||
|
|
||||||
{
|
{
|
||||||
JObject payload = new JObject();
|
JObject payload = new JObject();
|
||||||
// Random GUID for ID
|
|
||||||
payload.Add("id", "0dfac9d7-d898-4944-900b-1c1837707279");
|
payload.Add("id", "0dfac9d7-d898-4944-900b-1c1837707279");
|
||||||
payload.Add("fileNamePattern", "index.html");
|
payload.Add("fileNamePattern", "index.html");
|
||||||
payload.Add("callbackAssembly", GetType().Assembly.FullName);
|
payload.Add("callbackAssembly", GetType().Assembly.FullName);
|
||||||
@@ -221,7 +231,6 @@ namespace Jellyfin.Plugin.MediaBarEnhanced
|
|||||||
|
|
||||||
if (pluginInterfaceType != null)
|
if (pluginInterfaceType != null)
|
||||||
{
|
{
|
||||||
// The ID must match the one used in RegisterFileTransformation
|
|
||||||
Guid id = Guid.Parse("0dfac9d7-d898-4944-900b-1c1837707279");
|
Guid id = Guid.Parse("0dfac9d7-d898-4944-900b-1c1837707279");
|
||||||
pluginInterfaceType.GetMethod("RemoveTransformation")?.Invoke(null, new object?[] { id });
|
pluginInterfaceType.GetMethod("RemoveTransformation")?.Invoke(null, new object?[] { id });
|
||||||
_logger.LogInformation("File transformation unregistered successfully.");
|
_logger.LogInformation("File transformation unregistered successfully.");
|
||||||
@@ -230,7 +239,6 @@ namespace Jellyfin.Plugin.MediaBarEnhanced
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Log but don't throw, as we want to continue with normal removal
|
|
||||||
_logger.LogWarning(ex, "Error attempting to unregister file transformation. It might not have been registered.");
|
_logger.LogWarning(ex, "Error attempting to unregister file transformation. It might not have been registered.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Jellyfin Slideshow by M0RPH3US v3.0.6
|
* Jellyfin Slideshow by M0RPH3US v3.0.9
|
||||||
* Modified by CodeDevMLH v1.1.0.0
|
* Modified by CodeDevMLH v1.1.0.0
|
||||||
*
|
*
|
||||||
* New features:
|
* New features:
|
||||||
@@ -41,11 +41,11 @@
|
|||||||
|
|
||||||
@keyframes kenBurnsZoomIn {
|
@keyframes kenBurnsZoomIn {
|
||||||
from {
|
from {
|
||||||
transform: scale(1);
|
transform: scale3d(1, 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
transform: scale(1.1);
|
transform: scale3d(1.1, 1.1, 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +128,10 @@
|
|||||||
transition: width 0.2s ease-in-out;
|
transition: width 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layout-mobile .splashLogo {
|
||||||
|
height: 12%;
|
||||||
|
}
|
||||||
|
|
||||||
.backdrop.low-quality {
|
.backdrop.low-quality {
|
||||||
filter: blur(0.5px);
|
filter: blur(0.5px);
|
||||||
transform: scale(1.01);
|
transform: scale(1.01);
|
||||||
@@ -166,6 +170,12 @@
|
|||||||
height: 90%;
|
height: 90%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#slides-container[style*="display: none"],
|
||||||
|
#slides-container[style*="visibility: hidden"] {
|
||||||
|
pointer-events: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
.arrow {
|
||||||
@@ -780,6 +790,41 @@
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
.button-container {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-button,
|
||||||
|
.trailer-button {
|
||||||
|
padding: 8px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.misc-info {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.runTime {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 0.5vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.misc-info .separator-icon:last-of-type {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.genre {
|
||||||
|
top: calc(50% + 16vh);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
top: calc(50% + 27vh);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*Landscape Mode Phones*/
|
/*Landscape Mode Phones*/
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Jellyfin Slideshow by M0RPH3US v3.0.6
|
* Jellyfin Slideshow by M0RPH3US v3.0.9
|
||||||
* Modified by CodeDevMLH v1.1.0.0
|
* Modified by CodeDevMLH v1.1.0.0
|
||||||
*
|
*
|
||||||
* New features:
|
* New features:
|
||||||
@@ -42,9 +42,10 @@ const CONFIG = {
|
|||||||
fullWidthVideo: true,
|
fullWidthVideo: true,
|
||||||
enableMobileVideo: false,
|
enableMobileVideo: false,
|
||||||
showTrailerButton: true,
|
showTrailerButton: true,
|
||||||
|
preferredVideoQuality: "Auto",
|
||||||
enableKeyboardControls: true,
|
enableKeyboardControls: true,
|
||||||
alwaysShowArrows: false,
|
alwaysShowArrows: false,
|
||||||
enableCustomMediaIds: false,
|
enableCustomMediaIds: true,
|
||||||
enableSeasonalContent: false,
|
enableSeasonalContent: false,
|
||||||
customMediaIds: "",
|
customMediaIds: "",
|
||||||
enableLoadingScreen: true,
|
enableLoadingScreen: true,
|
||||||
@@ -77,6 +78,7 @@ const STATE = {
|
|||||||
videoPlayers: {},
|
videoPlayers: {},
|
||||||
sponsorBlockInterval: null,
|
sponsorBlockInterval: null,
|
||||||
isMuted: CONFIG.startMuted,
|
isMuted: CONFIG.startMuted,
|
||||||
|
customTrailerUrls: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -252,31 +254,53 @@ const initLoadingScreen = () => {
|
|||||||
|
|
||||||
const checkInterval = setInterval(() => {
|
const checkInterval = setInterval(() => {
|
||||||
const loginFormLoaded = document.querySelector(".manualLoginForm");
|
const loginFormLoaded = document.querySelector(".manualLoginForm");
|
||||||
const homePageLoaded =
|
const activeTab = document.querySelector(".pageTabContent.is-active");
|
||||||
document.querySelector(".homeSectionsContainer") &&
|
|
||||||
document.querySelector("#slides-container");
|
|
||||||
|
|
||||||
if (loginFormLoaded || homePageLoaded) {
|
if (loginFormLoaded) {
|
||||||
|
finishLoading();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeTab) {
|
||||||
|
const tabIndex = activeTab.getAttribute("data-index");
|
||||||
|
|
||||||
|
if (tabIndex === "0") {
|
||||||
|
const homeSections = document.querySelector(".homeSectionsContainer");
|
||||||
|
const slidesContainer = document.querySelector("#slides-container");
|
||||||
|
|
||||||
|
if (homeSections && slidesContainer) {
|
||||||
|
finishLoading();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
activeTab.children.length > 0 ||
|
||||||
|
activeTab.innerText.trim().length > 0
|
||||||
|
) {
|
||||||
|
finishLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, CONFIG.loadingCheckInterval);
|
||||||
|
|
||||||
|
const finishLoading = () => {
|
||||||
clearInterval(progressInterval);
|
clearInterval(progressInterval);
|
||||||
clearInterval(checkInterval);
|
clearInterval(checkInterval);
|
||||||
|
|
||||||
progressBar.style.transition = "width 300ms ease-in-out";
|
progressBar.style.transition = "width 300ms ease-in-out";
|
||||||
progressBar.style.width = "100%";
|
progressBar.style.width = "100%";
|
||||||
unfilledBar.style.width = "0%";
|
unfilledBar.style.width = "0%";
|
||||||
|
|
||||||
progressBar.addEventListener('transitionend', () => {
|
progressBar.addEventListener("transitionend", () => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const loader = document.querySelector(".bar-loading");
|
const loader = document.querySelector(".bar-loading");
|
||||||
if (loader) {
|
if (loader) {
|
||||||
loader.style.opacity = '0';
|
loader.style.opacity = "0";
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
loader.remove();
|
loader.remove();
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}, CONFIG.loadingCheckInterval);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -321,6 +345,7 @@ const resetSlideshowState = () => {
|
|||||||
STATE.slideshow.itemIds = [];
|
STATE.slideshow.itemIds = [];
|
||||||
STATE.slideshow.loadedItems = {};
|
STATE.slideshow.loadedItems = {};
|
||||||
STATE.slideshow.createdSlides = {};
|
STATE.slideshow.createdSlides = {};
|
||||||
|
STATE.slideshow.customTrailerUrls = {};
|
||||||
STATE.slideshow.totalItems = 0;
|
STATE.slideshow.totalItems = 0;
|
||||||
STATE.slideshow.isLoading = false;
|
STATE.slideshow.isLoading = false;
|
||||||
};
|
};
|
||||||
@@ -820,29 +845,64 @@ const LocalizationUtils = {
|
|||||||
throw new Error(`Failed to fetch translations: ${response.statusText}`);
|
throw new Error(`Failed to fetch translations: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @example
|
||||||
|
* Standard version
|
||||||
|
* ```js
|
||||||
|
* "use strict";
|
||||||
|
* (self.webpackChunk = self.webpackChunk || []).push([[62634], {
|
||||||
|
* 30985: function(e) {
|
||||||
|
* e.exports = JSON.parse('{"Absolute":"..."}')
|
||||||
|
* }
|
||||||
|
* }]);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Minified version
|
||||||
|
* ```js
|
||||||
|
* "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[24072],{60715:function(e){e.exports=JSON.parse('{"Absolute":"..."}')}}]);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
const chunkText = await response.text();
|
const chunkText = await response.text();
|
||||||
|
|
||||||
let jsonMatch = chunkText.match(/JSON\.parse\(['"](.*?)['"]\)/);
|
const replaceEscaped = (text) =>
|
||||||
if (jsonMatch) {
|
text.replace(/\\"/g, '"').replace(/\\n/g, '\n').replace(/\\\\/g, '\\').replace(/\\'/g, "'");
|
||||||
let jsonString = jsonMatch[1]
|
|
||||||
.replace(/\\"/g, '"')
|
// 1. Try to remove start and end wrappers first
|
||||||
.replace(/\\n/g, '\n')
|
|
||||||
.replace(/\\\\/g, '\\')
|
|
||||||
.replace(/\\'/g, "'");
|
|
||||||
try {
|
try {
|
||||||
|
// Matches from start of file to the beginning of JSON.parse('
|
||||||
|
const START = /^(.*)JSON\.parse\(['"]/gms;
|
||||||
|
// Matches from the end of the JSON string to the end of the file
|
||||||
|
const END = /['"]?\)?\s*}?(\r\n|\r|\n)?}?]?\)?;(\r\n|\r|\n)?$/gms;
|
||||||
|
|
||||||
|
const jsonString = replaceEscaped(chunkText.replace(START, '').replace(END, ''));
|
||||||
this.translations[locale] = JSON.parse(jsonString);
|
this.translations[locale] = JSON.parse(jsonString);
|
||||||
return;
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error('Failed to parse JSON from standard extraction.');
|
||||||
|
// Try alternative extraction below
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Try to extract only the JSON string directly
|
||||||
|
let jsonMatch = chunkText.match(/JSON\.parse\(['"](.*?)['"]\)/);
|
||||||
|
if (jsonMatch) {
|
||||||
|
try {
|
||||||
|
const jsonString = replaceEscaped(jsonMatch[1]);
|
||||||
|
this.translations[locale] = JSON.parse(jsonString);
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse JSON from direct extraction.');
|
||||||
// Try direct extraction
|
// Try direct extraction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Fallback: extract everything between the first { and the last }
|
||||||
const jsonStart = chunkText.indexOf('{');
|
const jsonStart = chunkText.indexOf('{');
|
||||||
const jsonEnd = chunkText.lastIndexOf('}') + 1;
|
const jsonEnd = chunkText.lastIndexOf('}') + 1;
|
||||||
if (jsonStart !== -1 && jsonEnd > jsonStart) {
|
if (jsonStart !== -1 && jsonEnd > jsonStart) {
|
||||||
const jsonString = chunkText.substring(jsonStart, jsonEnd);
|
const jsonString = chunkText.substring(jsonStart, jsonEnd);
|
||||||
try {
|
try {
|
||||||
this.translations[locale] = JSON.parse(jsonString);
|
this.translations[locale] = JSON.parse(jsonString);
|
||||||
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to parse JSON from chunk:", e);
|
console.error("Failed to parse JSON from chunk:", e);
|
||||||
}
|
}
|
||||||
@@ -1252,18 +1312,23 @@ const VisibilityObserver = {
|
|||||||
const isVisible =
|
const isVisible =
|
||||||
(window.location.hash === "#/home.html" ||
|
(window.location.hash === "#/home.html" ||
|
||||||
window.location.hash === "#/home") &&
|
window.location.hash === "#/home") &&
|
||||||
|
activeTab &&
|
||||||
activeTab.getAttribute("data-index") === "0";
|
activeTab.getAttribute("data-index") === "0";
|
||||||
|
|
||||||
container.style.display = isVisible ? "block" : "none";
|
container.style.display = isVisible ? "block" : "none";
|
||||||
|
container.style.visibility = isVisible ? "visible" : "hidden";
|
||||||
|
container.style.pointerEvents = isVisible ? "auto" : "none";
|
||||||
|
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
if (STATE.slideshow.slideInterval && !STATE.slideshow.isPaused) {
|
if (STATE.slideshow.slideInterval && !STATE.slideshow.isPaused) {
|
||||||
STATE.slideshow.slideInterval.start();
|
STATE.slideshow.slideInterval.start();
|
||||||
|
SlideshowManager.resumeActivePlayback();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (STATE.slideshow.slideInterval) {
|
if (STATE.slideshow.slideInterval) {
|
||||||
STATE.slideshow.slideInterval.stop();
|
STATE.slideshow.slideInterval.stop();
|
||||||
}
|
}
|
||||||
|
SlideshowManager.stopAllPlayback();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1271,11 +1336,11 @@ const VisibilityObserver = {
|
|||||||
* Initializes visibility observer
|
* Initializes visibility observer
|
||||||
*/
|
*/
|
||||||
init() {
|
init() {
|
||||||
const observer = new MutationObserver(this.updateVisibility);
|
const observer = new MutationObserver(() => this.updateVisibility());
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
|
||||||
document.body.addEventListener("click", this.updateVisibility);
|
document.body.addEventListener("click", () => this.updateVisibility());
|
||||||
window.addEventListener("hashchange", this.updateVisibility);
|
window.addEventListener("hashchange", () => this.updateVisibility());
|
||||||
|
|
||||||
this.updateVisibility();
|
this.updateVisibility();
|
||||||
},
|
},
|
||||||
@@ -1366,7 +1431,11 @@ const SlideCreator = {
|
|||||||
let trailerUrl = null;
|
let trailerUrl = null;
|
||||||
|
|
||||||
// 1. Check for Remote Trailers (YouTube)
|
// 1. Check for Remote Trailers (YouTube)
|
||||||
if (item.RemoteTrailers && item.RemoteTrailers.length > 0) {
|
// Priority: Custom Config URL > Metadata RemoteTrailer
|
||||||
|
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) {
|
||||||
trailerUrl = item.RemoteTrailers[0].Url;
|
trailerUrl = item.RemoteTrailers[0].Url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1415,6 +1484,21 @@ const SlideCreator = {
|
|||||||
loop: 0
|
loop: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Determine video quality
|
||||||
|
let quality = 'hd1080';
|
||||||
|
if (CONFIG.preferredVideoQuality === 'Maximum') {
|
||||||
|
quality = 'highres';
|
||||||
|
} else if (CONFIG.preferredVideoQuality === '720p') {
|
||||||
|
quality = 'hd720';
|
||||||
|
} else if (CONFIG.preferredVideoQuality === '1080p') {
|
||||||
|
quality = 'hd1080';
|
||||||
|
} else { // Auto or fallback
|
||||||
|
// If screen is wider than 1920, prefer highres, otherwise 1080p
|
||||||
|
quality = window.screen.width > 1920 ? 'highres' : 'hd1080';
|
||||||
|
}
|
||||||
|
|
||||||
|
playerVars.suggestedQuality = quality;
|
||||||
|
|
||||||
// Apply SponsorBlock start/end times
|
// Apply SponsorBlock start/end times
|
||||||
if (segments.intro) {
|
if (segments.intro) {
|
||||||
playerVars.start = Math.ceil(segments.intro[1]);
|
playerVars.start = Math.ceil(segments.intro[1]);
|
||||||
@@ -1444,6 +1528,10 @@ const SlideCreator = {
|
|||||||
event.target.setVolume(40);
|
event.target.setVolume(40);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof event.target.setPlaybackQuality === 'function') {
|
||||||
|
event.target.setPlaybackQuality(quality);
|
||||||
|
}
|
||||||
|
|
||||||
// Only play if this is the active slide
|
// Only play if this is the active slide
|
||||||
const slide = document.querySelector(`.slide[data-item-id="${itemId}"]`);
|
const slide = document.querySelector(`.slide[data-item-id="${itemId}"]`);
|
||||||
if (slide && slide.classList.contains('active')) {
|
if (slide && slide.classList.contains('active')) {
|
||||||
@@ -1492,6 +1580,7 @@ const SlideCreator = {
|
|||||||
className: "backdrop video-backdrop",
|
className: "backdrop video-backdrop",
|
||||||
src: trailerUrl,
|
src: trailerUrl,
|
||||||
autoplay: false,
|
autoplay: false,
|
||||||
|
preload: "auto",
|
||||||
loop: false,
|
loop: false,
|
||||||
style: "object-fit: cover; width: 100%; height: 100%; pointer-events: none;"
|
style: "object-fit: cover; width: 100%; height: 100%; pointer-events: none;"
|
||||||
};
|
};
|
||||||
@@ -2317,6 +2406,65 @@ const SlideshowManager = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops all video playback (YouTube and HTML5)
|
||||||
|
* Used when navigating away from the home screen
|
||||||
|
*/
|
||||||
|
stopAllPlayback() {
|
||||||
|
// 1. Pause all YouTube players
|
||||||
|
if (STATE.slideshow.videoPlayers) {
|
||||||
|
Object.values(STATE.slideshow.videoPlayers).forEach(player => {
|
||||||
|
try {
|
||||||
|
if (player && typeof player.pauseVideo === 'function') {
|
||||||
|
player.pauseVideo();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Error pausing YouTube player:", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Pause all HTML5 videos
|
||||||
|
const container = document.getElementById("slides-container");
|
||||||
|
if (container) {
|
||||||
|
container.querySelectorAll('video').forEach(video => {
|
||||||
|
try {
|
||||||
|
video.pause();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Error pausing HTML5 video:", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resumes playback for the active slide if not globally paused
|
||||||
|
*/
|
||||||
|
resumeActivePlayback() {
|
||||||
|
if (STATE.slideshow.isPaused) return;
|
||||||
|
|
||||||
|
const currentItemId = STATE.slideshow.itemIds[STATE.slideshow.currentSlideIndex];
|
||||||
|
if (!currentItemId) return;
|
||||||
|
|
||||||
|
const currentSlide = document.querySelector(`.slide[data-item-id="${currentItemId}"]`);
|
||||||
|
if (!currentSlide) return;
|
||||||
|
|
||||||
|
// 1. Try YouTube Player
|
||||||
|
const ytPlayer = STATE.slideshow.videoPlayers[currentItemId];
|
||||||
|
if (ytPlayer && typeof ytPlayer.playVideo === 'function') {
|
||||||
|
ytPlayer.playVideo();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Try HTML5 Video
|
||||||
|
const html5Video = currentSlide.querySelector('video');
|
||||||
|
if (html5Video) {
|
||||||
|
if (STATE.slideshow.isMuted) {
|
||||||
|
html5Video.muted = true;
|
||||||
|
}
|
||||||
|
html5Video.play().catch(e => console.warn("Error resuming HTML5 video:", e));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes touch events for swiping
|
* Initializes touch events for swiping
|
||||||
*/
|
*/
|
||||||
@@ -2434,9 +2582,24 @@ const SlideshowManager = {
|
|||||||
parseCustomIds() {
|
parseCustomIds() {
|
||||||
if (!CONFIG.enableSeasonalContent) {
|
if (!CONFIG.enableSeasonalContent) {
|
||||||
return CONFIG.customMediaIds
|
return CONFIG.customMediaIds
|
||||||
.split(/[\n,]/) // Split by comma or newline
|
.split(/[\n,]/).map((line) => {
|
||||||
.map((id) => id.trim()) // Remove whitespace
|
const urlMatch = line.match(/\[(.*?)\]/);
|
||||||
.filter((id) => id); // Remove empty strings
|
let id = line;
|
||||||
|
if (urlMatch) {
|
||||||
|
const url = urlMatch[1];
|
||||||
|
id = line.replace(/\[.*?\]/, '').trim();
|
||||||
|
const guidMatch = id.match(/([0-9a-f]{32})/i);
|
||||||
|
if (guidMatch) {
|
||||||
|
id = guidMatch[1];
|
||||||
|
} else {
|
||||||
|
id = id.split('|')[0].trim();
|
||||||
|
}
|
||||||
|
STATE.slideshow.customTrailerUrls[id] = url;
|
||||||
|
}
|
||||||
|
return id.trim();
|
||||||
|
})
|
||||||
|
.map((id) => id.trim())
|
||||||
|
.filter((id) => id);
|
||||||
} else {
|
} else {
|
||||||
return this.parseSeasonalIds();
|
return this.parseSeasonalIds();
|
||||||
}
|
}
|
||||||
@@ -2493,7 +2656,22 @@ const SlideshowManager = {
|
|||||||
|
|
||||||
if (isInRange) {
|
if (isInRange) {
|
||||||
console.log(`Seasonal match found: ${line}`);
|
console.log(`Seasonal match found: ${line}`);
|
||||||
const ids = idsPart.split(/[,]/).map(id => id.trim()).filter(id => id);
|
const ids = idsPart.split(/[,]/).map(line => {
|
||||||
|
const urlMatch = line.match(/\[(.*?)\]/);
|
||||||
|
let id = line;
|
||||||
|
if (urlMatch) {
|
||||||
|
const url = urlMatch[1];
|
||||||
|
id = line.replace(/\[.*?\]/, '').trim();
|
||||||
|
const guidMatch = id.match(/([0-9a-f]{32})/i);
|
||||||
|
if (guidMatch) {
|
||||||
|
id = guidMatch[1];
|
||||||
|
} else {
|
||||||
|
id = id.split('|')[0].trim();
|
||||||
|
}
|
||||||
|
STATE.slideshow.customTrailerUrls[id] = url;
|
||||||
|
}
|
||||||
|
return id.trim();
|
||||||
|
}).filter(id => id);
|
||||||
rawIds.push(...ids);
|
rawIds.push(...ids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2514,8 +2692,13 @@ const SlideshowManager = {
|
|||||||
try {
|
try {
|
||||||
let id = rawId;
|
let id = rawId;
|
||||||
|
|
||||||
// If not a valid GUID, treat as a name and search
|
// If not a valid GUID, check if it starts with one (comments) or treat as a name
|
||||||
if (!guidRegex.test(rawId)) {
|
if (!guidRegex.test(rawId)) {
|
||||||
|
const guidMatch = rawId.match(/^([0-9a-f]{32})(?:[^0-9a-f]|$)/i);
|
||||||
|
|
||||||
|
if (guidMatch) {
|
||||||
|
id = guidMatch[1];
|
||||||
|
} else {
|
||||||
console.log(`Input '${rawId}' is not a GUID, searching for Collection/Playlist by name...`);
|
console.log(`Input '${rawId}' is not a GUID, searching for Collection/Playlist by name...`);
|
||||||
const resolvedId = await ApiUtils.findCollectionOrPlaylistByName(rawId);
|
const resolvedId = await ApiUtils.findCollectionOrPlaylistByName(rawId);
|
||||||
|
|
||||||
@@ -2527,6 +2710,7 @@ const SlideshowManager = {
|
|||||||
continue; // Skip if resolution failed
|
continue; // Skip if resolution failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const item = await ApiUtils.fetchItemDetails(id);
|
const item = await ApiUtils.fetchItemDetails(id);
|
||||||
if (item && (item.Type === 'BoxSet' || item.Type === 'Playlist')) {
|
if (item && (item.Type === 'BoxSet' || item.Type === 'Playlist')) {
|
||||||
@@ -2839,7 +3023,7 @@ const slidesInit = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.slideshowPure = {
|
window.mediaBarEnhanced = {
|
||||||
CONFIG,
|
CONFIG,
|
||||||
STATE,
|
STATE,
|
||||||
SlideUtils,
|
SlideUtils,
|
||||||
|
|||||||
45
README.md
45
README.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Media Bar Enhanced is a plugin for Jellyfin that introduces a customizable and interactive media bar to your dashboard view on Jellyfin web.
|
Media Bar Enhanced is a plugin for Jellyfin that introduces a customizable and interactive media bar to your dashboard view on Jellyfin web.
|
||||||
|
|
||||||
This plugin is a fork and enhancement of the original [Media Bar by MakD](https://github.com/MakD/Jellyfin-Media-Bar) media bar, but can be installed as plugin for easier installation and management/configuration.
|
This plugin is a fork and enhancement of the original [Media Bar by MakD](https://github.com/MakD/Jellyfin-Media-Bar), but can be installed as plugin for easier installation and management/configuration.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ This plugin is a fork and enhancement of the original [Media Bar by MakD](https:
|
|||||||
## Table of Contents
|
## Table of Contents
|
||||||
- [Jellyfin Media Bar Enhanced Plugin](#jellyfin-media-bar-enhanced-plugin)
|
- [Jellyfin Media Bar Enhanced Plugin](#jellyfin-media-bar-enhanced-plugin)
|
||||||
- [Table of Contents](#table-of-contents)
|
- [Table of Contents](#table-of-contents)
|
||||||
|
- [Overview](#overview)
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [New Features \& Enhancements](#new-features--enhancements)
|
- [New Features \& Enhancements](#new-features--enhancements)
|
||||||
- [Core Features](#core-features)
|
- [Core Features](#core-features)
|
||||||
@@ -28,6 +29,45 @@ This plugin is a fork and enhancement of the original [Media Bar by MakD](https:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|

|
||||||
|
|
||||||
|
Expand to get more impressions:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Desktop Layout</summary>
|
||||||
|
|
||||||
|
<img width="1920" height="1080" alt="trailer button" src="https://github.com/user-attachments/assets/5dce8eb1-8f2f-4583-a6d5-16f27ced8608" />
|
||||||
|
Normal mode like the original with additional trailer button
|
||||||
|
<br><br><br>
|
||||||
|
|
||||||
|
<img width="1920" height="993" alt="modal_desktop" src="https://github.com/user-attachments/assets/9087f43d-cd9d-4581-a7e0-404b75bc8e02" />
|
||||||
|
Trailer modal
|
||||||
|
<br><br><br>
|
||||||
|
|
||||||
|
<img width="1920" height="994" alt="config" src="https://github.com/user-attachments/assets/5492c384-a5c4-47ee-9428-3d9de2748e63" />
|
||||||
|
Excerpt from the config: E.g. here you can simply add your items that should be displayed
|
||||||
|
<br><br>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Mobile Layout</summary>
|
||||||
|
|
||||||
|

|
||||||
|
<br>If trailer on mobile is eenabled...
|
||||||
|
<br><br><br>
|
||||||
|
|
||||||
|
<img width="1080" height="2199" alt="mobile" src="https://github.com/user-attachments/assets/f0a0cc0d-f019-45f5-96c8-a5de14bf92ba" />
|
||||||
|
Normal mode like the original with additional trailer button
|
||||||
|
<br><br><br>
|
||||||
|
|
||||||
|
<img width="1080" height="2199" alt="trailer_modal_mobile" src="https://github.com/user-attachments/assets/944f9b82-9c9b-411f-883b-877b65ed933f" />
|
||||||
|
Trailer modal in portrait mode
|
||||||
|
<br><br>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
This plugin builds upon the original Media Bar with new capabilities and improvements:
|
This plugin builds upon the original Media Bar with new capabilities and improvements:
|
||||||
@@ -104,6 +144,9 @@ Configure the plugin via **Dashboard** > **Plugins** > **Media Bar Enhanced**.
|
|||||||
Define exactly what shows up in your bar.
|
Define exactly what shows up in your bar.
|
||||||
|
|
||||||
* **Enable Custom Media IDs**: Restrict the slideshow to a specific list of IDs.
|
* **Enable Custom Media IDs**: Restrict the slideshow to a specific list of IDs.
|
||||||
|
* **Manual Trailer Override**: Add `[YouTube_URL]` after an ID to force a specific trailer.
|
||||||
|
* Example ID: `a1b2c3d4e5... [https://www.youtube.com/watch?v=VIDEO_ID]`
|
||||||
|
* Example Collection Name: `Halloween Collection [https://...] | My Description` (Note: Use `|` to separate description from name if using a name instead of an ID)
|
||||||
* **Enable Seasonal Content Mode**: Advanced date-based scheduling.
|
* **Enable Seasonal Content Mode**: Advanced date-based scheduling.
|
||||||
* Format: `DD.MM-DD.MM | Name | ID1, ID2, ID3`
|
* Format: `DD.MM-DD.MM | Name | ID1, ID2, ID3`
|
||||||
* Example: `20.10-31.10 | Halloween | <ID_OF_HALLOWEEN_COLLECTION>`
|
* Example: `20.10-31.10 | Halloween | <ID_OF_HALLOWEEN_COLLECTION>`
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ Setzt deine Commits neu auf die Spitze eines anderen Branches. Die Commit-IDs we
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
git checkout dev
|
git checkout dev
|
||||||
git rebase main
|
git fetch origin
|
||||||
|
git rebase origin/main
|
||||||
```
|
```
|
||||||
|
|
||||||
| Details | |
|
| Details | |
|
||||||
|
|||||||
@@ -2,35 +2,75 @@
|
|||||||
{
|
{
|
||||||
"guid": "d7e11d57-819b-4bdd-a88d-53c5f5560225",
|
"guid": "d7e11d57-819b-4bdd-a88d-53c5f5560225",
|
||||||
"name": "Media Bar Enhanced",
|
"name": "Media Bar Enhanced",
|
||||||
"description": "A jellyfin plugin to display a media bar (featured content) for jellyfin web.",
|
"description": "A feature-rich fork of the original Media Bar script by MakD that brings your home screen to life.\n\n-> 100% Configurable via Web UI: Manage all features, lists, and settings effortlessly through the plugin configuration page.\n\nKey Highlights:\n- Cinematic Video Backdrops: Supports local & YouTube trailers (incl. SponsorBlock)\n- Custom Content: Curate your slideshow with specific Collections, Playlists, or seasonal events\n\nAdditional Features:\n- Full-width immersive mode\n- Smart resolution handling (up to 4K)\n- Full keyboard navigation & playback control\n- Wait-for-trailer options\n- Customizable pagination & animations\n\nIf you do not have write permissions to the web folder, please also install the file-transformation plugin.",
|
||||||
"overview": "Media Bar for Jellyfin",
|
"overview": "Transforms your Jellyfin home screen with an immersive, fully configurable media slideshow featuring video backdrops.",
|
||||||
"owner": "CodeDevMLH",
|
"owner": "CodeDevMLH",
|
||||||
"category": "General",
|
"category": "General",
|
||||||
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/raw/branch/main/logo.png",
|
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/raw/branch/main/logo.png",
|
||||||
"versions": [
|
"versions": [
|
||||||
{
|
{
|
||||||
"version": "1.0.0.3",
|
"version": "1.3.0.1",
|
||||||
"changelog": "fixes",
|
"changelog": "- feat: Enhance custom media ID functionality with manual trailer override support",
|
||||||
"targetAbi": "10.11.0.0",
|
"targetAbi": "10.11.0.0",
|
||||||
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.0.0.3/Jellyfin.Plugin.MediaBarEnhanced.zip",
|
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.3.0.1/Jellyfin.Plugin.MediaBarEnhanced.zip",
|
||||||
"checksum": "d22b9b355892db2da04a1620bf7ee64d",
|
"checksum": "5a4f555e29c733dabd51169f6ace56eb",
|
||||||
"timestamp": "2026-01-06T21:33:22Z"
|
"timestamp": "2026-02-04T01:14:19Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "1.0.0.2",
|
"version": "1.2.3.7",
|
||||||
"changelog": "fixes",
|
"changelog": "- Fixes the issue where buttons were cut off on smaller screens such as on S24/S25.\n- Update mediaBarEnhanced.js and mediaBarEnhanced.css with version 3.0.9 from original repo",
|
||||||
"targetAbi": "10.11.0.0",
|
"targetAbi": "10.11.0.0",
|
||||||
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.0.0.2/Jellyfin.Plugin.MediaBarEnhanced.zip",
|
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.2.3.7/Jellyfin.Plugin.MediaBarEnhanced.zip",
|
||||||
"checksum": "1041b403ec0193c2172a6fe15501afd3",
|
"checksum": "fa1bf48cff159cc7dbf0aab48511a37c",
|
||||||
"timestamp": "2026-01-06T21:21:37Z"
|
"timestamp": "2026-01-28T22:39:54Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "1.0.0.1",
|
"version": "1.2.2.0",
|
||||||
"changelog": "fixes",
|
"changelog": "- Fixes issues with persistent slides-container visibility",
|
||||||
"targetAbi": "10.11.0.0",
|
"targetAbi": "10.11.0.0",
|
||||||
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.0.0.1/Jellyfin.Plugin.MediaBarEnhanced.zip",
|
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.2.2.0/Jellyfin.Plugin.MediaBarEnhanced.zip",
|
||||||
"checksum": "f4e6194a9cc72fdda7436161c73832de",
|
"checksum": "3362f93815845c4e85b66b31bcd0f52c",
|
||||||
"timestamp": "2026-01-06T21:18:33Z"
|
"timestamp": "2026-01-24T22:53:55Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "1.2.1.0",
|
||||||
|
"changelog": "- Update mediaBarEnhanced.js and mediaBarEnhanced.css with version 3.0.8 from original repo",
|
||||||
|
"targetAbi": "10.11.0.0",
|
||||||
|
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.2.1.0/Jellyfin.Plugin.MediaBarEnhanced.zip",
|
||||||
|
"checksum": "70defc1fb29a17ff4c9362bf7bdc53b5",
|
||||||
|
"timestamp": "2026-01-22T23:50:56Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "1.2.0.0",
|
||||||
|
"changelog": "- Add video quality preference setting (Auto / 1080p / Highres)\n- Set preferred video quality on YouTube player based on setting",
|
||||||
|
"targetAbi": "10.11.0.0",
|
||||||
|
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.2.0.0/Jellyfin.Plugin.MediaBarEnhanced.zip",
|
||||||
|
"checksum": "0b6379f68990026240d97fe8f77fbef1",
|
||||||
|
"timestamp": "2026-01-08T23:30:58Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "1.1.2.0",
|
||||||
|
"changelog": "- Add method to resume video playback when slideshow is active",
|
||||||
|
"targetAbi": "10.11.0.0",
|
||||||
|
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.1.2.0/Jellyfin.Plugin.MediaBarEnhanced.zip",
|
||||||
|
"checksum": "a0e8ff5e59b22a1bdedc916cd5e1c16a",
|
||||||
|
"timestamp": "2026-01-08T15:26:55Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "1.1.1.0",
|
||||||
|
"changelog": "- Add method to pause all video playback when navigating away from home screen",
|
||||||
|
"targetAbi": "10.11.0.0",
|
||||||
|
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.1.1.0/Jellyfin.Plugin.MediaBarEnhanced.zip",
|
||||||
|
"checksum": "09da95fc561b11191d23a5cfa30ea731",
|
||||||
|
"timestamp": "2026-01-08T14:54:57Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "1.1.0.0",
|
||||||
|
"changelog": "- 'custom media IDs' setting is now enabled by default (no input --> random selection)\n- improve GUID handling in slideshow manager to handle seperator and description",
|
||||||
|
"targetAbi": "10.11.0.0",
|
||||||
|
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.1.0.0/Jellyfin.Plugin.MediaBarEnhanced.zip",
|
||||||
|
"checksum": "32305d72b8d704acf8eef0c22277fee9",
|
||||||
|
"timestamp": "2026-01-08T02:15:50Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "1.0.0.0",
|
"version": "1.0.0.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user