diff --git a/app.json b/app.json index ebb0c4c6..3918041c 100644 --- a/app.json +++ b/app.json @@ -8,6 +8,7 @@ "scheme": "streamyfin", "userInterfaceStyle": "dark", "jsEngine": "hermes", + "newArchEnabled": true, "assetBundlePatterns": ["**/*"], "ios": { "requireFullScreen": true, diff --git a/bun.lock b/bun.lock index 85881d64..c1fd22e2 100644 --- a/bun.lock +++ b/bun.lock @@ -4,14 +4,14 @@ "": { "name": "streamyfin", "dependencies": { - "@bottom-tabs/react-navigation": "^0.11.2", + "@bottom-tabs/react-navigation": "^0.12.2", "@expo/metro-runtime": "~6.1.1", "@expo/react-native-action-sheet": "^4.1.1", "@expo/ui": "^0.2.0-beta.4", "@expo/vector-icons": "^15.0.2", "@gorhom/bottom-sheet": "^5.1.0", "@jellyfin/sdk": "^0.11.0", - "@kesha-antonov/react-native-background-downloader": "github:fredrikburmester/react-native-background-downloader#660828358d7560dd0267739410cbcd9de1740bc4", + "@kesha-antonov/react-native-background-downloader": "github:ctriantaf/react-native-background-downloader#aa754725fdaac80ca049ad22e13318609a113ddc", "@react-native-community/netinfo": "^11.4.1", "@react-navigation/material-top-tabs": "^7.2.14", "@react-navigation/native": "^7.0.14", @@ -54,7 +54,7 @@ "react-i18next": "^15.4.0", "react-native": "npm:react-native-tvos@0.81.4-0", "react-native-awesome-slider": "^2.9.0", - "react-native-bottom-tabs": "^0.11.2", + "react-native-bottom-tabs": "^0.12.2", "react-native-circular-progress": "^1.4.1", "react-native-collapsible": "^1.6.2", "react-native-country-flag": "^2.0.2", @@ -63,7 +63,7 @@ "react-native-gesture-handler": "~2.28.0", "react-native-google-cast": "^4.9.0", "react-native-image-colors": "^2.4.0", - "react-native-ios-context-menu": "^3.1.0", + "react-native-ios-context-menu": "^3.2.1", "react-native-ios-utilities": "5.2.0", "react-native-mmkv": "4.0.0-beta.12", "react-native-nitro-modules": "^0.29.1", @@ -300,25 +300,25 @@ "@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], - "@biomejs/biome": ["@biomejs/biome@2.2.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.2.4", "@biomejs/cli-darwin-x64": "2.2.4", "@biomejs/cli-linux-arm64": "2.2.4", "@biomejs/cli-linux-arm64-musl": "2.2.4", "@biomejs/cli-linux-x64": "2.2.4", "@biomejs/cli-linux-x64-musl": "2.2.4", "@biomejs/cli-win32-arm64": "2.2.4", "@biomejs/cli-win32-x64": "2.2.4" }, "bin": { "biome": "bin/biome" } }, "sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg=="], + "@biomejs/biome": ["@biomejs/biome@2.2.5", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.2.5", "@biomejs/cli-darwin-x64": "2.2.5", "@biomejs/cli-linux-arm64": "2.2.5", "@biomejs/cli-linux-arm64-musl": "2.2.5", "@biomejs/cli-linux-x64": "2.2.5", "@biomejs/cli-linux-x64-musl": "2.2.5", "@biomejs/cli-win32-arm64": "2.2.5", "@biomejs/cli-win32-x64": "2.2.5" }, "bin": { "biome": "bin/biome" } }, "sha512-zcIi+163Rc3HtyHbEO7CjeHq8DjQRs40HsGbW6vx2WI0tg8mYQOPouhvHSyEnCBAorfYNnKdR64/IxO7xQ5faw=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.2.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MYT+nZ38wEIWVcL5xLyOhYQQ7nlWD0b/4mgATW2c8dvq7R4OQjt/XGXFkXrmtWmQofaIM14L7V8qIz/M+bx5QQ=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.2.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-FLIEl73fv0R7dI10EnEiZLw+IMz3mWLnF95ASDI0kbx6DDLJjWxE5JxxBfmG+udz1hIDd3fr5wsuP7nwuTRdAg=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.2.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-5DjiiDfHqGgR2MS9D+AZ8kOfrzTGqLKywn8hoXpXXlJXIECGQ32t+gt/uiS2XyGBM2XQhR6ztUvbjZWeccFMoQ=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.2.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Ov2wgAFwqDvQiESnu7b9ufD1faRa+40uwrohgBopeY84El2TnBDoMNXx6iuQdreoFGjwW8vH6k68G21EpNERw=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.2.5", "", { "os": "linux", "cpu": "x64" }, "sha512-fq9meKm1AEXeAWan3uCg6XSP5ObA6F/Ovm89TwaMiy1DNIwdgxPkNwxlXJX8iM6oRbFysYeGnT0OG8diCWb9ew=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.2.5", "", { "os": "linux", "cpu": "x64" }, "sha512-AVqLCDb/6K7aPNIcxHaTQj01sl1m989CJIQFQEaiQkGr2EQwyOpaATJ473h+nXDUuAcREhccfRpe/tu+0wu0eQ=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.2.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-xaOIad4wBambwJa6mdp1FigYSIF9i7PCqRbvBqtIi9y29QtPVQ13sDGtUnsRoe6SjL10auMzQ6YAe+B3RpZXVg=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.2.5", "", { "os": "win32", "cpu": "x64" }, "sha512-F/jhuXCssPFAuciMhHKk00xnCAxJRS/pUzVfXYmOMUp//XW7mO6QeCjsjvnm8L4AO/dG2VOB0O+fJPiJ2uXtIw=="], - "@bottom-tabs/react-navigation": ["@bottom-tabs/react-navigation@0.11.2", "", { "dependencies": { "color": "^5.0.0" }, "peerDependencies": { "@react-navigation/native": ">=7", "react": "*", "react-native": "*", "react-native-bottom-tabs": "*" } }, "sha512-xjRZZe3GZ/bIADBkJSe+qjRC/pQKcTEhZgtoGb4lyINq1NPzhKXhlZHwZLzNJng/Q/+F4RD3M7bQ6oCgSHV2WA=="], + "@bottom-tabs/react-navigation": ["@bottom-tabs/react-navigation@0.12.2", "", { "dependencies": { "color": "^5.0.0" }, "peerDependencies": { "@react-navigation/native": ">=7", "react": "*", "react-native": "*", "react-native-bottom-tabs": "*" } }, "sha512-vQ/7pNcWk2TgveVCBfdQnbLC6zaRbIL2EM4Vsifk/NCCZ49oT1G2K7iXAsCPby9/ofd6ndGOMmsVLFVq5M7fjw=="], "@dominicstop/ts-event-emitter": ["@dominicstop/ts-event-emitter@1.1.0", "", {}, "sha512-CcxmJIvUb1vsFheuGGVSQf4KdPZC44XolpUT34+vlal+LyQoBUOn31pjFET5M9ctOxEpt8xa0M3/2M7uUiAoJw=="], @@ -450,7 +450,7 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@kesha-antonov/react-native-background-downloader": ["@kesha-antonov/react-native-background-downloader@github:fredrikburmester/react-native-background-downloader#6608283", { "peerDependencies": { "react-native": ">=0.57.0" } }, "fredrikburmester-react-native-background-downloader-6608283"], + "@kesha-antonov/react-native-background-downloader": ["@kesha-antonov/react-native-background-downloader@github:ctriantaf/react-native-background-downloader#aa75472", { "peerDependencies": { "react": "*", "react-native": "*" } }, "ctriantaf-react-native-background-downloader-aa75472"], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -610,7 +610,7 @@ "@types/lodash": ["@types/lodash@4.17.20", "", {}, "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA=="], - "@types/node": ["@types/node@24.6.1", "", { "dependencies": { "undici-types": "~7.13.0" } }, "sha512-ljvjjs3DNXummeIaooB4cLBKg2U6SPI6Hjra/9rRIy7CpM0HpLtG9HptkMKAb4HYWy5S7HUvJEuWgr/y0U8SHw=="], + "@types/node": ["@types/node@24.6.2", "", { "dependencies": { "undici-types": "~7.13.0" } }, "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang=="], "@types/react": ["@types/react@19.1.17", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA=="], @@ -1620,13 +1620,13 @@ "react-i18next": ["react-i18next@15.7.4", "", { "dependencies": { "@babel/runtime": "^7.27.6", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { "i18next": ">= 23.4.0", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-nyU8iKNrI5uDJch0z9+Y5XEr34b0wkyYj3Rp+tfbahxtlswxSCjcUL9H0nqXo9IR3/t5Y5PKIA3fx3MfUyR9Xw=="], - "react-is": ["react-is@19.1.1", "", {}, "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA=="], + "react-is": ["react-is@19.2.0", "", {}, "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA=="], "react-native": ["react-native-tvos@0.81.4-0", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native-tvos/virtualized-lists": "0.81.4-0", "@react-native/assets-registry": "0.81.4", "@react-native/codegen": "0.81.4", "@react-native/community-cli-plugin": "0.81.4", "@react-native/gradle-plugin": "0.81.4", "@react-native/js-polyfills": "0.81.4", "@react-native/normalize-colors": "0.81.4", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.29.1", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.83.1", "metro-source-map": "^0.83.1", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.26.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.3", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.1.0", "react": "^19.1.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-QmzKKInWuB0c+TldOqdqIKJPVV94vSzw26V3daqkp0RRA2M7Osi5mS2lpjLvbf7rkSVR/WiYGUs2A1LicXgmAQ=="], "react-native-awesome-slider": ["react-native-awesome-slider@2.9.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-gesture-handler": ">=2.0.0", "react-native-reanimated": ">=3.0.0" } }, "sha512-sc5qgX4YtM6IxjtosjgQLdsal120MvU+YWs0F2MdgQWijps22AXLDCUoBnZZ8vxVhVyJ2WnnIPrmtVBvVJjSuQ=="], - "react-native-bottom-tabs": ["react-native-bottom-tabs@0.11.2", "", { "dependencies": { "react-freeze": "^1.0.0", "sf-symbols-typescript": "^2.0.0", "use-latest-callback": "^0.2.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-2zvR9DgQgqOKhxGeETkphXANDkMyUKN/i0+M+WF52JQd4q4h+uY3ctLnXNQ4pZf1cEDlWQ6aBtYWe3NJKvDIwA=="], + "react-native-bottom-tabs": ["react-native-bottom-tabs@0.12.2", "", { "dependencies": { "react-freeze": "^1.0.0", "sf-symbols-typescript": "^2.0.0", "use-latest-callback": "^0.2.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-OXxYtKbJK8hfV7ZrrS/h3vmoB2WOQQRBuV+cqJ1NJSpKxZNBIwHfbUxGlkgcg/I7x2GlmZ9yDDC3KbQ3ouv69Q=="], "react-native-circular-progress": ["react-native-circular-progress@1.4.1", "", { "dependencies": { "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">=16.0.0", "react-native": ">=0.50.0", "react-native-svg": ">=7.0.0" } }, "sha512-HEzvI0WPuWvsCgWE3Ff2HBTMgAEQB2GvTFw0KHyD/t1STAlDDRiolu0mEGhVvihKR3jJu3v3V4qzvSklY/7XzQ=="], @@ -1938,7 +1938,7 @@ "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], - "use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="], + "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], "utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="], @@ -2122,12 +2122,6 @@ "@react-native-community/cli-tools/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], - "@react-native/community-cli-plugin/metro": ["metro@0.83.3", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.32.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-config": "0.83.3", "metro-core": "0.83.3", "metro-file-map": "0.83.3", "metro-resolver": "0.83.3", "metro-runtime": "0.83.3", "metro-source-map": "0.83.3", "metro-symbolicate": "0.83.3", "metro-transform-plugins": "0.83.3", "metro-transform-worker": "0.83.3", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q=="], - - "@react-native/community-cli-plugin/metro-config": ["metro-config@0.83.3", "", { "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.3", "metro-cache": "0.83.3", "metro-core": "0.83.3", "metro-runtime": "0.83.3", "yaml": "^2.6.1" } }, "sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA=="], - - "@react-native/community-cli-plugin/metro-core": ["metro-core@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.3" } }, "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw=="], - "@react-native/community-cli-plugin/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "@react-navigation/bottom-tabs/color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], @@ -2170,7 +2164,7 @@ "error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], - "expo-build-properties/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + "expo-build-properties/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], @@ -2244,7 +2238,7 @@ "patch-package/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], - "patch-package/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + "patch-package/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -2262,7 +2256,7 @@ "react-native/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], - "react-native/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + "react-native/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "react-native-reanimated/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], @@ -2354,30 +2348,6 @@ "@react-native-community/cli-server-api/open/is-wsl": ["is-wsl@1.1.0", "", {}, "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw=="], - "@react-native/community-cli-plugin/metro/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="], - - "@react-native/community-cli-plugin/metro/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="], - - "@react-native/community-cli-plugin/metro/metro-babel-transformer": ["metro-babel-transformer@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" } }, "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g=="], - - "@react-native/community-cli-plugin/metro/metro-cache": ["metro-cache@0.83.3", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.3" } }, "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q=="], - - "@react-native/community-cli-plugin/metro/metro-cache-key": ["metro-cache-key@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw=="], - - "@react-native/community-cli-plugin/metro/metro-file-map": ["metro-file-map@0.83.3", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA=="], - - "@react-native/community-cli-plugin/metro/metro-resolver": ["metro-resolver@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ=="], - - "@react-native/community-cli-plugin/metro/metro-transform-plugins": ["metro-transform-plugins@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" } }, "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A=="], - - "@react-native/community-cli-plugin/metro/metro-transform-worker": ["metro-transform-worker@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "metro": "0.83.3", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-minify-terser": "0.83.3", "metro-source-map": "0.83.3", "metro-transform-plugins": "0.83.3", "nullthrows": "^1.1.1" } }, "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA=="], - - "@react-native/community-cli-plugin/metro/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], - - "@react-native/community-cli-plugin/metro-config/metro-cache": ["metro-cache@0.83.3", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.3" } }, "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q=="], - - "@react-native/community-cli-plugin/metro-core/metro-resolver": ["metro-resolver@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ=="], - "@react-navigation/bottom-tabs/color/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "@react-navigation/bottom-tabs/color/color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="], @@ -2486,10 +2456,6 @@ "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], - "@react-native/community-cli-plugin/metro/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="], - - "@react-native/community-cli-plugin/metro/metro-transform-worker/metro-minify-terser": ["metro-minify-terser@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ=="], - "@react-navigation/bottom-tabs/color/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], "@react-navigation/bottom-tabs/color/color-string/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], diff --git a/metro.config.js b/metro.config.js index 2e9a0e06..ff7d87fb 100644 --- a/metro.config.js +++ b/metro.config.js @@ -2,7 +2,7 @@ const { getDefaultConfig } = require("expo/metro-config"); /** @type {import('expo/metro-config').MetroConfig} */ -const config = getDefaultConfig(__dirname); // eslint-disable-line no-undef +const config = getDefaultConfig(__dirname); // Add Hermes parser config.transformer.hermesParser = true; diff --git a/package.json b/package.json index 7c45e675..2265a413 100644 --- a/package.json +++ b/package.json @@ -22,14 +22,14 @@ "test": "bun run typecheck && bun run lint && bun run format && bun run doctor" }, "dependencies": { - "@bottom-tabs/react-navigation": "^0.11.2", + "@bottom-tabs/react-navigation": "^0.12.2", "@expo/metro-runtime": "~6.1.1", "@expo/react-native-action-sheet": "^4.1.1", "@expo/ui": "^0.2.0-beta.4", "@expo/vector-icons": "^15.0.2", "@gorhom/bottom-sheet": "^5.1.0", "@jellyfin/sdk": "^0.11.0", - "@kesha-antonov/react-native-background-downloader": "github:fredrikburmester/react-native-background-downloader#660828358d7560dd0267739410cbcd9de1740bc4", + "@kesha-antonov/react-native-background-downloader": "github:ctriantaf/react-native-background-downloader#aa754725fdaac80ca049ad22e13318609a113ddc", "@react-native-community/netinfo": "^11.4.1", "@react-navigation/material-top-tabs": "^7.2.14", "@react-navigation/native": "^7.0.14", @@ -72,7 +72,7 @@ "react-i18next": "^15.4.0", "react-native": "npm:react-native-tvos@0.81.4-0", "react-native-awesome-slider": "^2.9.0", - "react-native-bottom-tabs": "^0.11.2", + "react-native-bottom-tabs": "^0.12.2", "react-native-circular-progress": "^1.4.1", "react-native-collapsible": "^1.6.2", "react-native-country-flag": "^2.0.2", @@ -81,7 +81,7 @@ "react-native-gesture-handler": "~2.28.0", "react-native-google-cast": "^4.9.0", "react-native-image-colors": "^2.4.0", - "react-native-ios-context-menu": "^3.1.0", + "react-native-ios-context-menu": "^3.2.1", "react-native-ios-utilities": "5.2.0", "react-native-mmkv": "4.0.0-beta.12", "react-native-nitro-modules": "^0.29.1", diff --git a/providers/DownloadProvider.tsx b/providers/DownloadProvider.tsx index 034a84ea..74065768 100644 --- a/providers/DownloadProvider.tsx +++ b/providers/DownloadProvider.tsx @@ -7,7 +7,6 @@ import { Directory, File, Paths } from "expo-file-system"; import * as Notifications from "expo-notifications"; import { router } from "expo-router"; import { atom, useAtom } from "jotai"; -import { throttle } from "lodash"; import { createContext, useCallback, @@ -16,7 +15,7 @@ import { useMemo, } from "react"; import { useTranslation } from "react-i18next"; -import { Platform } from "react-native"; +import { DeviceEventEmitter, Platform } from "react-native"; import { toast } from "sonner-native"; import { useHaptic } from "@/hooks/useHaptic"; import useImageStorage from "@/hooks/useImageStorage"; @@ -114,6 +113,20 @@ function useDownloadProvider() { const { settings } = useSettings(); const successHapticFeedback = useHaptic("success"); + // Set up global download complete listener for debugging + useEffect(() => { + const listener = DeviceEventEmitter.addListener( + "downloadComplete", + (data) => { + console.log("🔥 GLOBAL TEST LISTENER received downloadComplete:", data); + }, + ); + + return () => { + listener.remove(); + }; + }, []); + // Generate notification content based on item type const getNotificationContent = useCallback( (item: BaseItemDto, isSuccess: boolean) => { @@ -180,7 +193,7 @@ function useDownloadProvider() { /// Cant use the background downloader callback. As its not triggered if size is unknown. const updateProgress = async () => { const tasks = await BackGroundDownloader.checkForExistingDownloads(); - if (!tasks) { + if (!tasks || tasks.length === 0) { return; } @@ -204,10 +217,41 @@ function useDownloadProvider() { // Find task for this process const task = tasks.find((s: any) => s.id === p.id); + if (!task) { + // ORPHANED DOWNLOAD CHECK: Task disappeared, but was it because it completed? + // This handles the race condition where download finishes between polling intervals + if (p.progress >= 90) { + // Lower threshold to catch more cases + console.log( + `[UPDATE_PROGRESS] Orphaned download detected for ${p.item.Name} at ${p.progress.toFixed(1)}%, checking file...`, + ); + const filename = generateFilename(p.item); + const videoFile = new File(Paths.document, `${filename}.mp4`); + + if (videoFile.exists && videoFile.size > 0) { + console.log( + `[UPDATE_PROGRESS] Orphaned download complete! File size: ${videoFile.size}, marking as complete`, + ); + return { + ...p, + progress: 100, + speed: 0, + bytesDownloaded: videoFile.size, + lastProgressUpdateTime: new Date(), + estimatedTotalSizeBytes: videoFile.size, + lastSessionBytes: videoFile.size, + lastSessionUpdateTime: new Date(), + status: "completed" as const, + }; + } else { + console.warn( + `[UPDATE_PROGRESS] Orphaned download at ${p.progress.toFixed(1)}% but file not found. Keeping current state.`, + ); + } + } return p; // No task found, keep current state } - /* // TODO: Uncomment this block to re-enable iOS zombie task detection // iOS: Extra validation to prevent zombie task interference @@ -340,7 +384,7 @@ function useDownloadProvider() { }); }; - useInterval(updateProgress, 2000); + useInterval(updateProgress, 1000); const getDownloadedItemById = (id: string): DownloadedItem | undefined => { const db = getDownloadsDatabase(); @@ -628,237 +672,12 @@ function useDownloadProvider() { console.log(`[DOWNLOAD] Starting download for ${filename}`); console.log(`[DOWNLOAD] Destination path: ${videoFilePath}`); - const downloadTask = BackGroundDownloader.download({ + BackGroundDownloader.download({ id: process.id, url: process.inputUrl, destination: videoFilePath, metadata: process, }); - - console.log(`[DOWNLOAD] Download task created:`, typeof downloadTask); - - downloadTask - .begin(() => { - console.log(`[DOWNLOAD] Download began for ${process.item.Name}`); - updateProcess(process.id, { - status: "downloading", - progress: process.progress || 0, - bytesDownloaded: process.bytesDownloaded || 0, - lastProgressUpdateTime: new Date(), - lastSessionBytes: process.lastSessionBytes || 0, - lastSessionUpdateTime: new Date(), - }); - }) - .progress( - throttle((data) => { - console.log( - `[DOWNLOAD] Progress: ${data.bytesDownloaded}/${data.bytesTotal} bytes`, - ); - updateProcess(process.id, (currentProcess) => { - // If this is a resumed download, add the paused bytes to current session bytes - const resumedBytes = currentProcess.pausedBytes || 0; - const totalBytes = data.bytesDownloaded + resumedBytes; - - // Calculate progress based on total bytes if we have resumed bytes - let percent: number; - if (resumedBytes > 0 && data.bytesTotal > 0) { - // For resumed downloads, calculate based on estimated total size - const estimatedTotal = - currentProcess.estimatedTotalSizeBytes || - data.bytesTotal + resumedBytes; - percent = (totalBytes / estimatedTotal) * 100; - } else { - // For fresh downloads, use normal calculation - percent = (data.bytesDownloaded / data.bytesTotal) * 100; - } - - return { - speed: calculateSpeed(currentProcess, totalBytes), - status: "downloading", - progress: Math.min(percent, MAX_PROGRESS_BEFORE_COMPLETION), - bytesDownloaded: totalBytes, - lastProgressUpdateTime: new Date(), - // update session-only counters - use current session bytes only for speed calc - lastSessionBytes: data.bytesDownloaded, - lastSessionUpdateTime: new Date(), - }; - }); - }, 500), - ) - .done(async () => { - try { - console.log( - `[DOWNLOAD] .done() callback triggered for ${process.item.Name}`, - ); - console.log( - `[DOWNLOAD] Download completed for ${process.item.Name}`, - ); - console.log(`[DOWNLOAD] Verifying file at: ${videoFilePath}`); - - // Re-create the File object using the same method as when we created it - const filename = generateFilename(process.item); - const videoFile = new File(Paths.document, `${filename}.mp4`); - - console.log(`[DOWNLOAD] File exists: ${videoFile.exists}`); - console.log(`[DOWNLOAD] File URI: ${videoFile.uri}`); - console.log( - `[DOWNLOAD] File path matches: ${videoFile.uri === videoFilePath}`, - ); - - if (!videoFile.exists) { - console.error( - `[DOWNLOAD] File does not exist at ${videoFile.uri}`, - ); - throw new Error("Downloaded file does not exist"); - } - const videoFileSize = videoFile.size; - console.log(`[DOWNLOAD] File size: ${videoFileSize} bytes`); - - const trickPlayData = await downloadTrickplayImages(process.item); - console.log( - `[DOWNLOAD] Trickplay data: ${trickPlayData ? "downloaded" : "not available"}`, - ); - - const db = getDownloadsDatabase(); - const { item, mediaSource } = process; - // Only download external subtitles for non-transcoded streams. - if (!mediaSource.TranscodingUrl) { - await downloadAndLinkSubtitles(mediaSource, item); - } - const { introSegments, creditSegments } = - await fetchAndParseSegments(item.Id!, api!); - const downloadedItem: DownloadedItem = { - item, - mediaSource, - videoFilePath, - videoFileSize, - videoFileName: `${filename}.mp4`, // Store filename separately for easy reconstruction - trickPlayData, - userData: { - audioStreamIndex: 0, - subtitleStreamIndex: 0, - }, - introSegments, - creditSegments, - }; - - console.log(`[DOWNLOAD] Saving to database:`); - console.log(`[DOWNLOAD] - Item: ${item.Name} (${item.Type})`); - console.log(`[DOWNLOAD] - Video path: ${videoFilePath}`); - console.log(`[DOWNLOAD] - Video size: ${videoFileSize} bytes`); - console.log( - `[DOWNLOAD] - Trickplay path: ${trickPlayData?.path || "none"}`, - ); - - if (item.Type === "Movie" && item.Id) { - db.movies[item.Id] = downloadedItem; - console.log(`[DOWNLOAD] Saved movie with ID: ${item.Id}`); - } else if ( - item.Type === "Episode" && - item.SeriesId && - item.ParentIndexNumber !== undefined && - item.ParentIndexNumber !== null && - item.IndexNumber !== undefined && - item.IndexNumber !== null - ) { - if (!db.series[item.SeriesId]) { - const seriesInfo: Partial = { - Id: item.SeriesId, - Name: item.SeriesName, - Type: "Series", - }; - db.series[item.SeriesId] = { - seriesInfo: seriesInfo as BaseItemDto, - seasons: {}, - }; - } - - const seasonNumber = item.ParentIndexNumber; - if (!db.series[item.SeriesId].seasons[seasonNumber]) { - db.series[item.SeriesId].seasons[seasonNumber] = { - episodes: {}, - }; - } - - const episodeNumber = item.IndexNumber; - db.series[item.SeriesId].seasons[seasonNumber].episodes[ - episodeNumber - ] = downloadedItem; - console.log( - `[DOWNLOAD] Saved episode: S${seasonNumber}E${episodeNumber} of series ${item.SeriesId}`, - ); - } - - console.log(`[DOWNLOAD] Database saved successfully`); - await saveDownloadsDatabase(db); - - // Send native notification for successful download - const successNotification = getNotificationContent( - process.item, - true, - ); - await sendDownloadNotification( - successNotification.title, - successNotification.body, - { - itemId: process.item.Id, - itemName: process.item.Name, - type: "download_completed", - }, - ); - - toast.success( - t("home.downloads.toasts.download_completed_for_item", { - item: process.item.Name, - }), - ); - - console.log( - `[DOWNLOAD] Removing process ${process.id} from active downloads`, - ); - removeProcess(process.id); - } catch (error) { - console.error(`[DOWNLOAD] Error in .done() callback:`, error); - throw error; - } - }) - .error(async (error: any) => { - console.error( - `[DOWNLOAD] .error() callback triggered for ${process.item.Name}`, - ); - console.error("[DOWNLOAD] Download error:", error); - console.error( - "[DOWNLOAD] Error details:", - JSON.stringify(error, null, 2), - ); - - // Send native notification for failed download - const failureNotification = getNotificationContent( - process.item, - false, - ); - await sendDownloadNotification( - failureNotification.title, - failureNotification.body, - { - itemId: process.item.Id, - itemName: process.item.Name, - type: "download_failed", - error: error?.message || "Unknown error", - }, - ); - - toast.error( - t("home.downloads.toasts.download_failed_for_item", { - item: process.item.Name, - }), - ); - - console.log( - `[DOWNLOAD] Removing process ${process.id} from active downloads (error)`, - ); - removeProcess(process.id); - }); }, [authHeader, sendDownloadNotification, getNotificationContent], ); @@ -1071,8 +890,14 @@ function useDownloadProvider() { mediaSource: MediaSourceInfo, maxBitrate: Bitrate, ) => { - if (!api || !item.Id || !authHeader) + if (!api || !item.Id || !authHeader) { + console.warn("startBackgroundDownload ~ Missing required params", { + api, + item, + authHeader, + }); throw new Error("startBackgroundDownload ~ Missing required params"); + } try { const deviceId = getOrSetDeviceId(); await saveSeriesPrimaryImage(item);