瀏覽代碼

Merge remote-tracking branch 'origin/master' into test

Ian Walton 2 年之前
父節點
當前提交
dbfc283a4b

+ 4 - 2
.github/ISSUE_TEMPLATE/bug_report.md

@@ -7,11 +7,13 @@ assignees: ''
 
 ---
 
-Three considerations:
+Four considerations:
+ - Please do not open bug reports to ask questions. Use the Discussions feature instead.
  - Please make sure the issue only pertains to Jellyfin Media Player. If it also occurs in the web client, send the issue to jellyfin-web instead.
  - Please make sure that your issue is not being caused by errors in custom CSS or note that you are using custom CSS.
      - Notably, there have been instances of custom CSS breaking TV mode.
- - Please provide logs:
+     - You can disable custom CSS under Display in the user settings.
+ - Please provide logs. You can drag the log file into the issue to attach it.
      - Windows: `%LOCALAPPDATA%\JellyfinMediaPlayer\logs`
      - Linux: `~/.local/share/jellyfinmediaplayer/logs/`
      - Linux (Flatpak): `~/.var/app/com.github.iwalton3.jellyfin-media-player/data/jellyfinmediaplayer/logs/`

+ 13 - 9
.github/workflows/main.yml

@@ -4,10 +4,11 @@ on:
   push:
     branches:
       - release
+      - prerelease
       - test
 jobs:
   build-mac:
-    runs-on: macOS-latest
+    runs-on: macos-10.15
     steps:
     - uses: actions/checkout@v2
     - name: Install Qt 5.15.2
@@ -15,6 +16,7 @@ jobs:
       with:
         version: "5.15.2"
         modules: "qtwebengine"  
+        setup-python: 'false'
     - name: Install dependencies
       run: |
         brew update
@@ -49,11 +51,12 @@ jobs:
     - name: Install dependencies
       run: |
         ./download_webclient.sh
-        sed -i 's#Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Redist\\MSVC\\v142\\#'"$(ls -d "/c/Program Files (x86)/Microsoft Visual Studio/2019/"*"/VC/Redist/MSVC/v"* | head -n 1 | sed 's#/c/##g' | tr '/' '\\' | sed 's/\\/\\\\/g')\\\\#g" bundle/win/Bundle.wxs
+        ls -R "/c/Program Files/Microsoft Visual Studio/2022/"*"/VC/Redist/MSVC/"*
+        sed -i 's#Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Redist\\MSVC\\v142\\vcredist_x64.exe#'"$(ls -d "/c/Program Files/Microsoft Visual Studio/2022/"*"/VC/Redist/MSVC/v"* | head -n 1 | sed 's#/c/##g' | tr '/' '\\' | sed 's/\\/\\\\/g')\\\\vc_redist.x64.exe#g" bundle/win/Bundle.wxs
         curl -L https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-win.zip > ninja.zip
         unzip ninja.zip
         mv ninja.exe build/
-        curl -L https://sourceforge.net/projects/mpv-player-windows/files/libmpv/mpv-dev-x86_64-20210404-git-dd86f19.7z/download > mpv.7z
+        curl -L https://sourceforge.net/projects/mpv-player-windows/files/libmpv/mpv-dev-x86_64-20211212-git-0e76372.7z/download > mpv.7z
         7z x mpv.7z
         mv include mpv
         mkdir include
@@ -66,7 +69,7 @@ jobs:
       shell: bash
     - name: Build
       run: |
-        call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
+        call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
         cd build
         set PATH=%PATH%;C:\Program Files (x86)\WiX Toolset v3.11\bin;%CD%
         cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=output -DCMAKE_MAKE_PROGRAM=ninja.exe -DQTROOT=%Qt5_DIR% -DMPV_INCLUDE_DIR=mpv/include -DMPV_LIBRARY=mpv/mpv.dll -DCMAKE_INSTALL_PREFIX=output ..
@@ -93,11 +96,12 @@ jobs:
     - name: Install dependencies
       run: |
         ./download_webclient.sh
-        sed -i 's#Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Redist\\MSVC\\v142\\#'"$(ls -d "/c/Program Files (x86)/Microsoft Visual Studio/2019/"*"/VC/Redist/MSVC/v"* | head -n 1 | sed 's#/c/##g' | tr '/' '\\' | sed 's/\\/\\\\/g')\\\\#g" bundle/win/Bundle.wxs
+        ls -R "/c/Program Files/Microsoft Visual Studio/2022/"*"/VC/Redist/MSVC/"*
+        sed -i 's#Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Redist\\MSVC\\v142\\vcredist_x64.exe#'"$(ls -d "/c/Program Files/Microsoft Visual Studio/2022/"*"/VC/Redist/MSVC/v"* | head -n 1 | sed 's#/c/##g' | tr '/' '\\' | sed 's/\\/\\\\/g')\\\\vc_redist.x86.exe#g" bundle/win/Bundle.wxs
         curl -L https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-win.zip > ninja.zip
         unzip ninja.zip
         mv ninja.exe build/
-        curl -L https://sourceforge.net/projects/mpv-player-windows/files/libmpv/mpv-dev-i686-20210404-git-dd86f19.7z/download > mpv.7z
+        curl -L https://sourceforge.net/projects/mpv-player-windows/files/libmpv/mpv-dev-i686-20211212-git-0e76372.7z/download > mpv.7z
         7z x mpv.7z
         mv include mpv
         mkdir include
@@ -110,7 +114,7 @@ jobs:
       shell: bash
     - name: Build
       run: |
-        call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars32.bat"
+        call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars32.bat"
         cd build
         set PATH=%PATH%;C:\Program Files (x86)\WiX Toolset v3.11\bin;%CD%
         cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=output -DCMAKE_MAKE_PROGRAM=ninja.exe -DQTROOT=%Qt5_DIR% -DMPV_INCLUDE_DIR=mpv/include -DMPV_LIBRARY=mpv/mpv.dll -DCMAKE_INSTALL_PREFIX=output ..
@@ -127,7 +131,7 @@ jobs:
   build-ubuntu:
     strategy:
       matrix:
-        tag: [focal, groovy]
+        tag: [focal, hirsute, impish, jammy]
     runs-on: "ubuntu-latest"
     steps:
     - uses: actions/checkout@v2
@@ -144,7 +148,7 @@ jobs:
   build-debian:
     strategy:
       matrix:
-        tag: [buster, bullseye]
+        tag: [bullseye, bookworm]
     runs-on: "ubuntu-latest"
     steps:
     - uses: actions/checkout@v2

+ 5 - 5
CMakeModules/VersionConfiguration.cmake

@@ -21,13 +21,13 @@ else()
 endif()
 
 set(VERSION_MAJOR 1)
-set(VERSION_MINOR 6)
-set(VERSION_NANO 0)
+set(VERSION_MINOR 7)
+set(VERSION_NANO 1)
 
 option(UPGRADE_DEBUG "" OFF)
 
-set(VERSION_STRING "1.6.0")
-set(VERSION_STRING_SHORT "1.6.0")
-set(CANONICAL_VERSION_STRING "1.6.0")
+set(VERSION_STRING "1.7.1")
+set(VERSION_STRING_SHORT "1.7.1")
+set(CANONICAL_VERSION_STRING "1.7.1")
 
 configure_file(src/core/Version.cpp.in src/core/Version.cpp)

+ 3 - 3
README.md

@@ -18,8 +18,8 @@ Related Documents:
 ## Building at a glance (Linux)
 
 ```bash
-sudo apt install autoconf automake libtool libharfbuzz-dev libfreetype6-dev libfontconfig1-dev libx11-dev libxrandr-dev libvdpau-dev libva-dev mesa-common-dev libegl1-mesa-dev yasm libasound2-dev libpulse-dev libuchardet-dev zlib1g-dev libfribidi-dev git libgnutls28-dev libgl1-mesa-dev libsdl2-dev cmake wget python g++ qtwebengine5-dev qtquickcontrols2-5-dev libqt5x11extras5-dev libcec-dev qml-module-qtquick-controls qml-module-qtwebengine qml-module-qtwebchannel qtbase5-private-dev
-mkdir jmp; cd jmp
+sudo apt install build-essential autoconf automake libtool libharfbuzz-dev libfreetype6-dev libfontconfig1-dev libx11-dev libxrandr-dev libvdpau-dev libva-dev mesa-common-dev libegl1-mesa-dev yasm libasound2-dev libpulse-dev libuchardet-dev zlib1g-dev libfribidi-dev git libgnutls28-dev libgl1-mesa-dev libsdl2-dev cmake wget python g++ qtwebengine5-dev qtquickcontrols2-5-dev libqt5x11extras5-dev libcec-dev qml-module-qtquick-controls qml-module-qtwebengine qml-module-qtwebchannel qtbase5-private-dev curl unzip
+mkdir ~/jmp; cd ~/jmp
 git clone https://github.com/mpv-player/mpv-build.git
 cd mpv-build
 echo --enable-libmpv-shared > mpv_options
@@ -28,7 +28,7 @@ echo --disable-cplayer >> mpv_options
 sudo ./install
 sudo ldconfig
 cd ~/jmp/
-git clone git://github.com/iwalton3/jellyfin-media-player
+git clone https://github.com/jellyfin/jellyfin-media-player.git
 cd jellyfin-media-player
 ./download_webclient.sh
 cd build

+ 3 - 0
bundle/win/qt.conf

@@ -5,3 +5,6 @@ Binaries = .
 Imports = .
 Qml2Imports = .
 LibraryExecutables = .
+
+[Platforms]
+WindowsArguments = darkmode=2

+ 62 - 0
debian/changelog

@@ -1,3 +1,65 @@
+jellyfin-media-player (1.7.1-1) unstable; urgency=medium
+
+  * Fix audio and subtitle selection for 10.8.0. (#271)
+  * Stop forcing fullscreen on Windows when visibility changes. (#94)
+  * Update jellyfin-web client to 10.8.1.
+
+ -- Ian Walton <ian@iwalton.com>  Sun, 26 Jun 2022 13:40:05 -0400
+
+jellyfin-media-player (1.7.0-1) unstable; urgency=medium
+
+  * Update web client to 10.8.0.
+  * Add --platform option. (#159)
+  * Added Xbox input mapping. (#197)
+  * Disable youtube-dl attempts on media errors.
+  * Allow retry with transcode instead of usng a hard failure. (#127)
+  * Add exit app function (#198)
+  * Set the MaxStaticBitrate to avoid server hard-coded 8 mbps limit.
+  * Add build for Ubuntu 22.04 (#256)
+  * Enable windows dark mode support. (#247)
+
+ -- Ian Walton <ian@iwalton.com>  Sat, 11 Jun 2022 09:45:44 -0400
+
+jellyfin-media-player (1.7.0-pre4) unstable; urgency=medium
+
+  * Update web client to 10.8.0 Beta 1.
+
+ -- Ian Walton <ian@iwalton.com>  Mon, 28 Mar 2022 21:21:58 -0400
+
+jellyfin-media-player (1.7.0-pre3) unstable; urgency=medium
+
+  * Update web client to 10.8.0 Alpha 5.
+  * Add --platform option. (#159)
+  * Added Xbox input mapping. (#197)
+  * Disable youtube-dl attempts on media errors.
+  * Allow retry with transcode instead of usng a hard failure. (#127)
+  * Add exit app function (not used yet) (#198)
+
+ -- Ian Walton <ian@iwalton.com>  Sun, 16 Jan 2022 13:20:56 -0500
+
+jellyfin-media-player (1.7.0-pre2) unstable; urgency=medium
+
+  * Set the MaxStaticBitrate to avoid server hard-coded 8 mbps limit.
+  * Upgrade to MPV version 20211219 fd63bf3.
+
+ -- Ian Walton <ian@iwalton.com>  Wed, 22 Dec 2021 17:34:43 -0500
+
+jellyfin-media-player (1.7.0-pre1) unstable; urgency=medium
+
+  * This is a prerelease build to test for jellyfin-web 10.8.0.
+
+ -- Ian Walton <ian@iwalton.com>  Sat, 18 Dec 2021 08:15:55 -0500
+
+jellyfin-media-player (1.6.1-1) unstable; urgency=medium
+
+  * Update jellyfin-web to 10.7.6.
+  * Add dependency to libqt5xml5. (#103)
+  * Fix hang after playback error. (#88)
+  * Fix alt+tab switching on GNOME. (#84)
+  * Fix media key pause/play on Windows. (#83) 
+
+ -- Ian Walton <ian@iwalton.com>  Sun, 01 Aug 2021 13:54:03 -0400
+
 jellyfin-media-player (1.6.0-1) unstable; urgency=medium
 
   * Always set volume on playback. (#78)

+ 3 - 2
debian/control

@@ -27,7 +27,7 @@ Build-Depends: debhelper (>= 9),
                libsdl2-dev,
                cmake,
                wget,
-               python,
+               python3,
                g++,
                qtwebengine5-dev,
                qtquickcontrols2-5-dev,
@@ -50,6 +50,7 @@ Depends: libmpv1,
          qml-module-qtwebengine,
          qml-module-qtwebchannel,
          qml-module-qtquick-controls,
-         libqt5x11extras5
+         libqt5x11extras5,
+         libqt5xml5
 Description: Jellyfin is the Free Software Media System.
  This package provides the Jellyfin desktop media player.

+ 1 - 0
native/jmpUpdatePlugin.js

@@ -19,6 +19,7 @@ class jmpUpdatePlugin {
                 const version = urlSegments[urlSegments.length - 1].substring(1);
                 const currentVersion = navigator.userAgent.split(" ")[1];
 
+                if (currentVersion.includes('pre')) return; // Do not notify for prereleases
                 if (version == currentVersion) return;
                 if (!/^[0-9.-]+$/.test(version)) return;
 

+ 3 - 2
native/mpvAudioPlayer.js

@@ -30,7 +30,7 @@ function cancelFadeTimeout() {
 }
 
 class mpvAudioPlayer {
-    constructor({ events, appHost, appSettings }) {
+    constructor({ events, appHost, appSettings, toast }) {
         const self = this;
 
         self.events = events;
@@ -178,7 +178,8 @@ class mpvAudioPlayer {
         }
 
         function onError(error) {
-            console.error(`media element error: ${error}`);
+            console.error(`media error: ${error}`);
+            toast(`media error: ${error}`);
 
             self.events.trigger(self, 'error', [
                 {

+ 119 - 25
native/mpvVideoPlayer.js

@@ -7,7 +7,7 @@
     }
 
     class mpvVideoPlayer {
-        constructor({ events, loading, appRouter, globalize, appHost, appSettings }) {
+        constructor({ events, loading, appRouter, globalize, appHost, appSettings, confirm }) {
             this.events = events;
             this.loading = loading;
             this.appRouter = appRouter;
@@ -189,14 +189,33 @@
              * @private
              * @param e {Event} The event received from the `<video>` element
              */
-            this.onError = (error) => {
-                console.error(`media element error: ${error}`);
+            this.onError = async (error) => {
+                this.removeMediaDialog();
+                console.error(`media error: ${error}`);
+
+                const errorData = {
+                    type: 'mediadecodeerror'
+                };
+
+                try {
+                    await confirm({
+                        title: "Playback Failed",
+                        text: `Playback failed with error "${error}". Retry with transcode? (Note this may hang the player.)`,
+                        cancelText: "Cancel",
+                        confirmText: "Retry"
+                    });
+                } catch (ex) {
+                    // User declined retry
+                    errorData.streamInfo = {
+                        // Prevent jellyfin-web retrying with transcode
+                        // which crashes the player
+                        mediaSource: {
+                            SupportsTranscoding: false
+                        }
+                    };
+                }
 
-                this.events.trigger(this, 'error', [
-                    {
-                        type: 'mediadecodeerror'
-                    }
-                ]);
+                this.events.trigger(this, 'error', [errorData]);
             };
 
             this.onDuration = (duration) => {
@@ -224,6 +243,39 @@
             return this.appSettings.get('volume') || 1;
         }
 
+        /**
+         * @private
+         */
+        getRelativeIndexByType(mediaStreams, jellyIndex, streamType) {
+            let relIndex = 1;
+            for (const source of mediaStreams) {
+                if (source.Type != streamType || source.IsExternal) {
+                    continue;
+                }
+
+                if (source.Index == jellyIndex) {
+                    return relIndex;
+                }
+
+                relIndex += 1;
+            }
+
+            return null;
+        }
+
+        /**
+         * @private
+         */
+        getStreamByIndex(mediaStreams, jellyIndex) {
+            for (const source of mediaStreams) {
+                if (source.Index == jellyIndex) {
+                    return source;
+                }
+            }
+
+            return null;
+        }
+
         /**
          * @private
          */
@@ -231,7 +283,7 @@
             const options = this._currentPlayOptions;
 
             if (this._subtitleTrackIndexToSetOnPlaying != null && this._subtitleTrackIndexToSetOnPlaying >= 0) {
-                const initialSubtitleStream = options.mediaSource.MediaStreams[this._subtitleTrackIndexToSetOnPlaying];
+                const initialSubtitleStream = this.getStreamByIndex(options.mediaSource.MediaStreams, this._subtitleTrackIndexToSetOnPlaying);
                 if (!initialSubtitleStream || initialSubtitleStream.DeliveryMethod === 'Encode') {
                     this._subtitleTrackIndexToSetOnPlaying = -1;
                 } else if (initialSubtitleStream.DeliveryMethod === 'External') {
@@ -243,7 +295,43 @@
                 return '';
             }
 
-            return '#' + this._subtitleTrackIndexToSetOnPlaying;
+            const subtitleRelIndex = this.getRelativeIndexByType(
+                options.mediaSource.MediaStreams,
+                this._subtitleTrackIndexToSetOnPlaying,
+                'Subtitle'
+            );
+
+            return subtitleRelIndex != null
+                ? '#' + subtitleRelIndex
+                : '';
+        }
+
+        /**
+         * @private
+         */
+        getAudioParam() {
+            const options = this._currentPlayOptions;
+
+            if (this._audioTrackIndexToSetOnPlaying != null && this._audioTrackIndexToSetOnPlaying >= 0) {
+                const initialAudioStream = this.getStreamByIndex(options.mediaSource.MediaStreams, this._audioTrackIndexToSetOnPlaying);
+                if (!initialAudioStream) {
+                    return '#1';
+                }
+            }
+
+            if (this._audioTrackIndexToSetOnPlaying == -1 || this._audioTrackIndexToSetOnPlaying == null) {
+                return '#1';
+            }
+
+            const audioRelIndex = this.getRelativeIndexByType(
+                options.mediaSource.MediaStreams,
+                this._audioTrackIndexToSetOnPlaying,
+                'Audio'
+            );
+
+            return audioRelIndex != null
+                ? '#' + audioRelIndex
+                : '#1';
         }
 
         tryGetFramerate(options) {
@@ -281,8 +369,7 @@
                 player.load(val,
                     { startMilliseconds: ms, autoplay: true },
                     streamdata,
-                    (this._audioTrackIndexToSetOnPlaying != null)
-                     ? '#' + this._audioTrackIndexToSetOnPlaying : '#1',
+                    this.getAudioParam(),
                     this.getSubtitleParam(),
                     resolve);
             });
@@ -348,7 +435,7 @@
                 return;
             }
 
-            window.api.player.setAudioStream(index != -1 ? '#' + index : '');
+            window.api.player.setAudioStream(this.getAudioParam());
         }
 
         onEndedInternal() {
@@ -375,23 +462,14 @@
             return Promise.resolve();
         }
 
-        destroy() {
+        removeMediaDialog() {
+            this.loading.hide();
             window.api.player.stop();
             window.api.power.setScreensaverEnabled(true);
 
             this.appRouter.setTransparency('none');
             document.body.classList.remove('hide-scroll');
 
-            const player = window.api.player;
-            this._hasConnection = false;
-            player.playing.disconnect(this.onPlaying);
-            player.positionUpdate.disconnect(this.onTimeUpdate);
-            player.finished.disconnect(this.onEnded);
-            this._duration = undefined;
-            player.updateDuration.disconnect(this.onDuration);
-            player.error.disconnect(this.onError);
-            player.paused.disconnect(this.onPause);
-
             const dlg = this._videoDialog;
             if (dlg) {
                 this._videoDialog = null;
@@ -404,6 +482,20 @@
             }
         }
 
+        destroy() {
+            this.removeMediaDialog();
+
+            const player = window.api.player;
+            this._hasConnection = false;
+            player.playing.disconnect(this.onPlaying);
+            player.positionUpdate.disconnect(this.onTimeUpdate);
+            player.finished.disconnect(this.onEnded);
+            this._duration = undefined;
+            player.updateDuration.disconnect(this.onDuration);
+            player.error.disconnect(this.onError);
+            player.paused.disconnect(this.onPause);
+        }
+
         /**
          * @private
          */
@@ -556,6 +648,7 @@
 
     pause() {
         window.api.player.pause();
+        window.api.power.setScreensaverEnabled(true);
     }
 
     // This is a retry after error
@@ -566,6 +659,7 @@
 
     unpause() {
         window.api.player.play();
+        window.api.power.setScreensaverEnabled(false);
     }
 
     paused() {
@@ -726,4 +820,4 @@
     }
 /* eslint-enable indent */
 
-window._mpvVideoPlayer = mpvVideoPlayer;
+window._mpvVideoPlayer = mpvVideoPlayer;

+ 41 - 45
native/nativeshell.js

@@ -8,6 +8,7 @@ const features = [
     "externallinks",
     "clientsettings",
     "multiserver",
+    "exitmenu",
     "remotecontrol",
     "fullscreenchange",
     "filedownload",
@@ -63,6 +64,7 @@ window.NativeShell = {
 function getDeviceProfile() {
     return {
         'Name': 'Jellyfin Media Player',
+        'MaxStaticBitrate': 1000000000,
         'MusicStreamingTranscodingBitrate': 1280000,
         'TimelineOffsetSeconds': 5,
         'TranscodingProfiles': [
@@ -94,6 +96,7 @@ function getDeviceProfile() {
             {'Format': 'smi', 'Method': 'External'},
             {'Format': 'pgssub', 'Method': 'Embed'},
             {'Format': 'dvdsub', 'Method': 'Embed'},
+            {'Format': 'dvbsub', 'Method': 'Embed'},
             {'Format': 'pgs', 'Method': 'Embed'}
         ]
     };
@@ -131,6 +134,9 @@ window.NativeShell.AppHost = {
     },
     deviceName() {
         return viewdata.deviceName;
+    },
+    exit() {
+        window.api.system.exit();
     }
 };
 
@@ -140,19 +146,8 @@ async function showSettingsModal() {
     });
 
     const modalContainer = document.createElement("div");
-    Object.assign(modalContainer.style, {
-        position: "fixed",
-        top: 10,
-        bottom: 10,
-        left: 10,
-        right: 10,
-        zIndex: 2000,
-        width: "100%",
-        height: "100%",
-        backgroundColor: "#000000C0",
-        display: "flex",
-        justifyContent: "center"
-    });
+    modalContainer.className = "dialogContainer";
+    modalContainer.style.backgroundColor = "rgba(0,0,0,0.5)";
     modalContainer.addEventListener("click", e => {
         if (e.target == modalContainer) {
             modalContainer.remove();
@@ -161,34 +156,30 @@ async function showSettingsModal() {
     document.body.appendChild(modalContainer);
 
     const modalContainer2 = document.createElement("div");
-    Object.assign(modalContainer2.style, {
-        width: "100%",
-        maxWidth: "500px",
-        overflowY: "auto",
-        margin: "20px",
-        height: "auto"
-    });
+    modalContainer2.className = "focuscontainer dialog dialog-fixedSize dialog-small formDialog opened";
     modalContainer.appendChild(modalContainer2);
 
-    const modal = document.createElement("div");
-    modal.className = "jmp-settings-modal";
-    Object.assign(modal.style, {
-        width: "100%",
-        padding: "20px",
-        boxSizing: "border-box",
-        backgroundColor: "#202020",
-        height: "min-content",
-        color: "#fff"
-    });
-    modalContainer2.appendChild(modal);
+    const modalHeader = document.createElement("div");
+    modalHeader.className = "formDialogHeader";
+    modalContainer2.appendChild(modalHeader);
 
-    const title = document.createElement("h1");
+    const title = document.createElement("h3");
+    title.className = "formDialogHeaderTitle";
     title.textContent = "Jellyfin Media Player Settings";
-    modal.appendChild(title);
-
+    modalHeader.appendChild(title);
+    
+    const modalContents = document.createElement("div");
+    modalContents.className = "formDialogContent smoothScrollY";
+    modalContents.style.paddingTop = "2em";
+    modalContents.style.marginBottom = "6.2em";
+    modalContainer2.appendChild(modalContents);
+    
     for (let section of settings) {
         const group = document.createElement("fieldset");
-        modal.appendChild(group);
+        group.className = "editItemMetadataForm editMetadataForm dialog-content-centered";
+        group.style.border = 0;
+        group.style.outline = 0;
+        modalContents.appendChild(group);
 
         const createSection = async (clear) => {
             if (clear) {
@@ -200,16 +191,22 @@ async function showSettingsModal() {
             });
 
             const legend = document.createElement("legend");
-            legend.textContent = section.key;
+            const legendHeader = document.createElement("h2");
+            legendHeader.textContent = section.key;
+            legendHeader.style.textTransform = "capitalize";
+            legend.appendChild(legendHeader);
             group.appendChild(legend);
 
             for (const setting of section.settings) {
                 const label = document.createElement("label");
+                label.className = "inputContainer";
+                label.style.marginBottom = "1.8em";
                 label.style.display = "block";
+                label.style.textTransform = "capitalize";
                 if (setting.options) {
                     const safeValues = {};
                     const control = document.createElement("select");
-                    control.style.maxWidth = "350px";
+                    control.className = "emby-select-withcolor emby-select";
                     for (const option of setting.options) {
                         safeValues[String(option.value)] = option.value;
                         const opt = document.createElement("option");
@@ -238,7 +235,10 @@ async function showSettingsModal() {
                             createSection(true);
                         }
                     });
-                    label.appendChild(document.createTextNode(setting.key + " "));
+                    const labelText = document.createElement('label');
+                    labelText.className = "inputLabel";
+                    labelText.textContent = setting.key + ": ";
+                    label.appendChild(labelText);
                     label.appendChild(control);
                 } else {
                     const control = document.createElement("input");
@@ -257,15 +257,11 @@ async function showSettingsModal() {
     }
 
     const closeContainer = document.createElement("div");
-    Object.assign(closeContainer.style, {
-        width: "100%",
-        display: "flex",
-        justifyContent: "flex-end",
-        paddingTop: "10px"
-    });
-    modal.appendChild(closeContainer);
+    closeContainer.className = "formDialogFooter";
+    modalContents.appendChild(closeContainer);
 
     const close = document.createElement("button");
+    close.className = "raised button-cancel block btnCancel formDialogFooterItem emby-button";
     close.textContent = "Close"
     close.addEventListener("click", () => {
         modalContainer.remove();

+ 77 - 0
resources/inputmaps/xbox-controller-linux.json

@@ -0,0 +1,77 @@
+{
+  "name": "Xbox Controller",
+  "idmatcher": "Xbox One",
+  "mapping": 
+  {
+    // A
+    "KEY_BUTTON_0": "enter",
+
+    // B
+    "KEY_BUTTON_1": {
+      "short": "back",
+      "long": "home"
+    },
+
+    // X
+    "KEY_BUTTON_2": "cycle_audio",
+
+    // Y
+    "KEY_BUTTON_3": "cycle_subtitle",
+    "KEY_BUTTON_3": "search",
+
+    // LB
+    "KEY_BUTTON_4": "seek_backward",
+
+    // RB
+    "KEY_BUTTON_5": "seek_forward",
+
+    // left thumbstick press
+    "KEY_BUTTON_6": "host:toggleDebug",
+
+    // right thumbstick press
+    "KEY_BUTTON_7": "host:fullscreen",
+
+    // start
+    "KEY_BUTTON_8": "",
+
+    // back
+    "KEY_BUTTON_9": "",
+
+    // Windows button
+    "KEY_BUTTON_10": {
+      "short": "home",
+      "long": "exit"
+    },
+
+    // D-PAD
+    "KEY_BUTTON_11": "up",
+    "KEY_BUTTON_12": "down",
+    "KEY_BUTTON_13": "left",
+    "KEY_BUTTON_14": "right",
+
+    // left thumbstick axis
+    "KEY_AXIS_0_UP": "left",
+    "KEY_AXIS_0_DOWN": "right",
+    "KEY_AXIS_1_UP": "up",
+    "KEY_AXIS_1_DOWN": "down",
+
+    // right thumbstick axis
+    "KEY_AXIS_4_UP": "increase_volume",
+    "KEY_AXIS_4_DOWN": "decrease_volume",
+    "KEY_AXIS_3_UP": "decrease_audio_delay",
+    "KEY_AXIS_3_DOWN": "increase_audio_delay",
+
+    // left trigger
+    "KEY_AXIS_2_UP": "",
+
+    // right trigger
+    "KEY_AXIS_5_UP": "",
+    
+    // D-Pad with JoyHat events
+    "KEY_HAT_DOWN": "down",
+    "KEY_HAT_UP": "up",
+    "KEY_HAT_RIGHT": "right",
+    "KEY_HAT_LEFT": "left"
+
+  }
+}

+ 41 - 9
resources/meta/com.github.iwalton3.jellyfin-media-player.appdata.xml

@@ -39,7 +39,39 @@
     <category>TV</category>
   </categories>
   <releases>
-      <release version="1.6.0" date="2020-05-04">
+      <release version="1.7.1" date="2022-06-26">
+        <p>Changes:</p>
+        <ul>
+          <li>Fix audio and subtitle selection for 10.8.0. (#271)</li>
+          <li>Stop forcing fullscreen on Windows when visibility changes. (#94)</li>
+          <li>Update jellyfin-web client to 10.8.1.</li>
+        </ul>
+      </release>
+      <release version="1.7.0" date="2022-06-11">
+        <p>Changes:</p>
+        <ul>
+          <li>Update web client to 10.8.0.</li>
+          <li>Add --platform option. (#159)</li>
+          <li>Added Xbox input mapping. (#197)</li>
+          <li>Disable youtube-dl attempts on media errors.</li>
+          <li>Allow retry with transcode instead of usng a hard failure. (#127)</li>
+          <li>Add exit app function (#198)</li>
+          <li>Set the MaxStaticBitrate to avoid server hard-coded 8 mbps limit.</li>
+          <li>Add build for Ubuntu 22.04 (#256)</li>
+          <li>Enable windows dark mode support. (#247)</li>
+        </ul>
+      </release>
+      <release version="1.6.1" date="2021-08-01">
+        <p>Changes:</p>
+        <ul>
+          <li>Update jellyfin-web to 10.7.6.</li>
+          <li>Add dependency to libqt5xml5. (#103)</li>
+          <li>Fix hang after playback error. (#88)</li>
+          <li>Fix alt+tab switching on GNOME. (#84)</li>
+          <li>Fix media key pause/play on Windows. (#83)</li>
+        </ul>
+      </release>
+      <release version="1.6.0" date="2021-05-04">
         <p>Changes:</p>
         <ul>
           <li>Always set volume on playback. (#78)</li>
@@ -54,7 +86,7 @@
           <li>Add more error handling to AutoSet feature.</li>
         </ul>
       </release>
-      <release version="1.5.0" date="2020-04-24">
+      <release version="1.5.0" date="2021-04-24">
         <p>Changes:</p>
         <ul>
           <li>Remember intended subtitle and audio selection between episodes.</li>
@@ -65,7 +97,7 @@
           <li>Fix volume OSD not showing on mute toggle. (#63)</li>
         </ul>
       </release>
-      <release version="1.4.1" date="2020-04-19">
+      <release version="1.4.1" date="2021-04-19">
         <p>Changes:</p>
         <ul>
           <li>Add update notifier.</li>
@@ -74,7 +106,7 @@
           <li>Fix excessive width of options drop-downs in some cases.</li>
         </ul>
       </release>
-      <release version="1.4.0" date="2020-04-18">
+      <release version="1.4.0" date="2021-04-18">
         <p>Changes:</p>
         <ul>
           <li>Backport fix for images not loading.</li>
@@ -87,7 +119,7 @@
           <li>Fix more warnings in the codebase. (#32)</li>
         </ul>
       </release>
-      <release version="1.3.1" date="2020-04-13">
+      <release version="1.3.1" date="2021-04-13">
         <p>Changes:</p>
         <ul>
           <li>Add builds for win32, debian, and ubuntu.</li>
@@ -100,7 +132,7 @@
           <li>Upgrade jellyfin-web to 10.7.2.</li>
         </ul>
       </release>
-      <release version="1.3.0" date="2020-04-11">
+      <release version="1.3.0" date="2021-04-11">
         <p>Changes:</p>
         <ul>
           <li>Add settings menu for built-in player settings.</li>
@@ -114,7 +146,7 @@
           <li>Fix F11 key for fullscreen.</li>
         </ul>
       </release>
-      <release version="1.2.1" date="2020-04-06">
+      <release version="1.2.1" date="2021-04-06">
         <p>Changes:</p>
         <ul>
           <li>Fix external subtitle support.</li>
@@ -122,7 +154,7 @@
           <li>Make easier to compile on Linux.</li>
         </ul>
       </release>
-      <release version="1.2.0" date="2020-04-05">
+      <release version="1.2.0" date="2021-04-05">
         <p>Changes:</p>
         <ul>
           <li>Add native music playback.</li>
@@ -130,7 +162,7 @@
           <li>Remove breakpad.</li>
         </ul>
       </release>
-      <release version="1.1.0" date="2020-04-05">
+      <release version="1.1.0" date="2021-04-05">
         <p>Changes:</p>
         <ul>
           <li>Added support for setting playback rate.</li>

+ 2 - 1
resources/meta/com.github.iwalton3.jellyfin-media-player.desktop

@@ -6,6 +6,7 @@ Exec=jellyfinmediaplayer
 Icon=com.github.iwalton3.jellyfin-media-player
 Terminal=false
 Type=Application
+StartupWMClass=jellyfinmediaplayer
 Categories=AudioVideo;Video;Player;TV;
 
 Actions=DesktopF;DesktopW;TVF;TVW
@@ -24,4 +25,4 @@ Exec=jellyfinmediaplayer --fullscreen --tv
 
 [Desktop Action TVW]
 Name=TV [Windowed]
-Exec=jellyfinmediaplayer --windowed --tv
+Exec=jellyfinmediaplayer --windowed --tv

+ 15 - 1
src/main.cpp

@@ -81,7 +81,8 @@ void ShowLicenseInfo()
 
 /////////////////////////////////////////////////////////////////////////////////////////
 QStringList g_qtFlags = {
-  "--disable-web-security"
+  "--disable-web-security",
+  "--enable-gpu-rasterization"
 };
 
 /////////////////////////////////////////////////////////////////////////////////////////
@@ -106,10 +107,15 @@ int main(int argc, char *argv[])
     scaleOption.setValueName("scale");
     scaleOption.setDefaultValue("auto");
     
+    auto platformOption = QCommandLineOption("platform", "Equivalant to QT_QPA_PLATFORM.");
+    platformOption.setValueName("platform");
+    platformOption.setDefaultValue("default");
+
     auto devOption = QCommandLineOption("remote-debugging-port", "Port number for devtools.");
     devOption.setValueName("port");
     parser.addOption(scaleOption);
     parser.addOption(devOption);
+    parser.addOption(platformOption);
 
     char **newArgv = appendCommandLineArguments(argc, argv, g_qtFlags);
     int newArgc = argc + g_qtFlags.size();
@@ -154,7 +160,15 @@ int main(int argc, char *argv[])
     else if (scale != "none")
       qputenv("QT_SCALE_FACTOR", scale.toUtf8());
 
+    auto platform = parser.value("platform");
+    if (!(platform.isEmpty() || platform == "default"))
+    {
+      qputenv("QT_QPA_PLATFORM", platform.toUtf8());
+    }
+
     QApplication app(newArgc, newArgv);
+    app.setApplicationName("Jellyfin Media Player");
+
 #if defined(Q_OS_WIN) 
     // Setting window icon on OSX will break user ability to change it
     app.setWindowIcon(QIcon(":/images/icon.png"));

+ 16 - 12
src/player/PlayerComponent.cpp

@@ -128,6 +128,9 @@ bool PlayerComponent::componentInitialize()
   // See: https://github.com/plexinc/plex-media-player/issues/736
   mpv::qt::set_property(m_mpv, "cache-seek-min", 5000);
 
+  // Disable ytdl
+  mpv::qt::set_property(m_mpv, "ytdl", false);
+
   if (SettingsComponent::Get().ignoreSSLErrors()) {
     mpv::qt::set_property(m_mpv, "tls-ca-file", "");
     mpv::qt::set_property(m_mpv, "tls-verify", "no");
@@ -873,20 +876,21 @@ void PlayerComponent::reselectStream(const QString &streamSelection, MediaType t
 
   QString selection = "no";
 
-  for (auto stream : findStreamsForURL(streamName))
+  if (!streamID.isEmpty())
   {
-    auto map = stream.toMap();
-
-    if (map["type"].toString() != mpvStreamTypeName)
-      continue;
-
-    if (!streamID.isEmpty() && map["ff-index"].toString() == streamID)
+    selection = streamID;
+  } else {
+    for (auto stream : findStreamsForURL(streamName))
     {
-      selection = map["id"].toString();
-      break;
-    } else if (streamID.isEmpty() && map["external-filename"].toString() == streamName) {
-      selection = map["id"].toString();
-      break;
+      auto map = stream.toMap();
+
+      if (map["type"].toString() != mpvStreamTypeName)
+      {
+        continue;
+      } else if (map["external-filename"].toString() == streamName) {
+        selection = map["id"].toString();
+        break;
+      }
     }
   }
 

+ 2 - 2
src/shared/Paths.cpp

@@ -101,9 +101,9 @@ QString Paths::socketName(const QString& serverName)
     userName = "unknown";
 
 #ifdef Q_OS_UNIX
-  return QString("/tmp/pmp_%1_%2.sock").arg(serverName).arg(userName);
+  return QString("/tmp/jmp_%1_%2.sock").arg(serverName).arg(userName);
 #else
-  return QString("pmp_%1_%2.sock").arg(serverName).arg(userName);
+  return QString("jmp_%1_%2.sock").arg(serverName).arg(userName);
 #endif
 }
 

+ 0 - 14
src/ui/KonvergoWindow.cpp

@@ -478,20 +478,6 @@ void KonvergoWindow::onVisibilityChanged(QWindow::Visibility visibility)
 {
   QLOG_DEBUG() << "QWindow visibility set to" << visibility;
 
-#ifdef Q_OS_WIN32
-  if (visibility == QWindow::Windowed)
-  {
-    QScreen* realScreen = findCurrentScreen();
-    if (realScreen && realScreen->geometry() == geometry())
-    {
-      QLOG_WARN() << "winging it!";
-      setScreen(realScreen);
-      setVisibility(QWindow::FullScreen);
-      return;
-    }
-  }
-#endif
-
   if (visibility == QWindow::Windowed && SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "forceAlwaysFS").toBool())
   {
     QLOG_WARN() << "Forcing re-entering fullscreen because of forceAlwaysFS setting!";