From 55601378fd617025b79d836182af1cd5325bcefe Mon Sep 17 00:00:00 2001 From: 4gray Date: Wed, 27 Nov 2024 18:44:09 +0100 Subject: [PATCH 01/14] chore: bump version to 1.0.0-beta.3 and update dependencies Enables the macOSPrivateApi in the Tauri configuration for enhanced functionality. Updates several dependencies in Cargo.toml to their latest versions, including xmltv, tokio, chrono, reqwest, and flate2, ensuring better performance and security. --- package-lock.json | 54 +---- package.json | 2 +- src-tauri/Cargo.lock | 409 ++++++++++++++++++++++++++++++++++---- src-tauri/Cargo.toml | 10 +- src-tauri/tauri.conf.json | 3 +- 5 files changed, 386 insertions(+), 92 deletions(-) diff --git a/package-lock.json b/package-lock.json index e150a5f22..1f05aa961 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "iptvnator", - "version": "1.0.0-beta.2", + "version": "1.0.0-beta.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "iptvnator", - "version": "1.0.0-beta.2", + "version": "1.0.0-beta.3", "dependencies": { "@angular-builders/jest": "17.0.0", "@angular/animations": "18.2.1", @@ -39,7 +39,6 @@ "iptv-playlist-parser": "github:4gray/iptv-playlist-parser", "lodash": "4.17.21", "moment": "2.30.1", - "nedb-promises": "6.0.3", "ngx-indexed-db": "19.0.0", "ngx-skeleton-loader": "9.0.0", "ngx-whats-new": "0.4.0", @@ -6615,26 +6614,6 @@ } } }, - "node_modules/@seald-io/binary-search-tree": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.3.tgz", - "integrity": "sha512-qv3jnwoakeax2razYaMsGI/luWdliBLHTdC6jU55hQt1hcFqzauH/HsBollQ7IR4ySTtYhT+xyHoijpA16C+tA==" - }, - "node_modules/@seald-io/nedb": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@seald-io/nedb/-/nedb-2.2.2.tgz", - "integrity": "sha512-z91J3pem4ENzHuu9BilOSdlGL2S14OQYePPdvBcPHgHr+s51VIUwQARcOjx21KvtkA27vEpgPqzrVKh7nSlIfw==", - "dependencies": { - "@seald-io/binary-search-tree": "^1.0.2", - "async": "0.2.10", - "localforage": "^1.9.0" - } - }, - "node_modules/@seald-io/nedb/node_modules/async": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", - "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==" - }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -14843,11 +14822,6 @@ "node": ">=0.10.0" } }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" - }, "node_modules/immutable": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", @@ -17897,14 +17871,6 @@ } } }, - "node_modules/lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", - "dependencies": { - "immediate": "~3.0.5" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -18143,14 +18109,6 @@ "node": ">= 12.13.0" } }, - "node_modules/localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "dependencies": { - "lie": "3.1.1" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -19290,14 +19248,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/nedb-promises": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/nedb-promises/-/nedb-promises-6.0.3.tgz", - "integrity": "sha512-pd0N6EsIPhXsfqdLcBJGig1FoTdV8wtuiXB7rBUqycJdUSA6wdI5BlkwEAjDBWIwZc4+xqBHRLtuAB7/bjS+rg==", - "dependencies": { - "@seald-io/nedb": "^2.2.0" - } - }, "node_modules/needle": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", diff --git a/package.json b/package.json index 90bc40c39..8231416dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iptvnator", - "version": "1.0.0-beta.2", + "version": "1.0.0-beta.3", "description": "IPTV player application.", "homepage": "https://github.com/4gray/iptvnator", "author": { diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index c756383f1..7dd691e4d 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4,10 +4,14 @@ version = 3 [[package]] name = "IPTVnator" -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" dependencies = [ + "chrono", "fix-path-env", + "flate2", "log", + "quick-xml 0.31.0", + "reqwest 0.11.27", "serde", "serde_json", "tauri", @@ -21,6 +25,8 @@ dependencies = [ "tauri-plugin-shell", "tauri-plugin-updater", "tauri-plugin-window-state", + "tokio", + "xmltv", ] [[package]] @@ -159,6 +165,19 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-compression" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-recursion" version = "1.1.1" @@ -528,8 +547,10 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-targets 0.52.6", ] @@ -544,7 +565,7 @@ dependencies = [ "cocoa-foundation", "core-foundation 0.10.0", "core-graphics", - "foreign-types", + "foreign-types 0.5.0", "libc", "objc", ] @@ -651,7 +672,7 @@ dependencies = [ "bitflags 2.6.0", "core-foundation 0.10.0", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -954,7 +975,7 @@ dependencies = [ "rustc_version", "toml 0.8.2", "vswhom", - "winreg", + "winreg 0.52.0", ] [[package]] @@ -1137,6 +1158,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1144,7 +1174,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -1158,6 +1188,12 @@ dependencies = [ "syn 2.0.86", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -1565,6 +1601,25 @@ dependencies = [ "syn 2.0.86", ] +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.6.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "h2" version = "0.4.6" @@ -1576,7 +1631,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.1.0", "indexmap 2.6.0", "slab", "tokio", @@ -1646,6 +1701,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.11", +] + [[package]] name = "http" version = "1.1.0" @@ -1657,6 +1723,17 @@ dependencies = [ "itoa 1.0.11", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -1664,7 +1741,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.1.0", ] [[package]] @@ -1675,8 +1752,8 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http", - "http-body", + "http 1.1.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -1686,6 +1763,36 @@ version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa 1.0.11", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.5.0" @@ -1695,9 +1802,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2", - "http", - "http-body", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", "httparse", "itoa 1.0.11", "pin-project-lite", @@ -1713,8 +1820,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", - "http", - "hyper", + "http 1.1.0", + "hyper 1.5.0", "hyper-util", "rustls", "rustls-pki-types", @@ -1724,6 +1831,19 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.31", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "hyper-util" version = "0.1.10" @@ -1733,9 +1853,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", - "http-body", - "hyper", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.5.0", "pin-project-lite", "socket2", "tokio", @@ -2188,6 +2308,23 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.9.0" @@ -2273,7 +2410,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", "syn 2.0.86", @@ -2542,6 +2679,50 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -2947,6 +3128,16 @@ dependencies = [ "psl-types", ] +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quick-xml" version = "0.32.0" @@ -3174,6 +3365,48 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "async-compression", + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.31", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.50.0", +] + [[package]] name = "reqwest" version = "0.12.9" @@ -3187,11 +3420,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.5.0", "hyper-rustls", "hyper-util", "ipnet", @@ -3203,13 +3436,13 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", - "rustls-pemfile", + "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", - "system-configuration", + "sync_wrapper 1.0.1", + "system-configuration 0.6.1", "tokio", "tokio-rustls", "tokio-util", @@ -3354,6 +3587,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -3395,6 +3637,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "schemars" version = "0.8.21" @@ -3440,6 +3691,29 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.22.0" @@ -3705,7 +3979,7 @@ dependencies = [ "bytemuck", "cfg_aliases 0.2.1", "core-graphics", - "foreign-types", + "foreign-types 0.5.0", "js-sys", "log", "objc2", @@ -3854,6 +4128,12 @@ dependencies = [ "syn 2.0.86", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.1" @@ -3872,6 +4152,17 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys 0.5.0", +] + [[package]] name = "system-configuration" version = "0.6.1" @@ -3880,7 +4171,17 @@ checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.6.0", "core-foundation 0.9.4", - "system-configuration-sys", + "system-configuration-sys 0.6.0", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -3995,7 +4296,7 @@ dependencies = [ "glob", "gtk", "heck 0.5.0", - "http", + "http 1.1.0", "jni", "libc", "log", @@ -4007,7 +4308,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest", + "reqwest 0.12.9", "serde", "serde_json", "serde_repr", @@ -4155,9 +4456,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c752aee1b00ec3c4d4f440095995d9bd2c640b478f2067d1fba388900b82eb96" dependencies = [ "data-url", - "http", + "http 1.1.0", "regex", - "reqwest", + "reqwest 0.12.9", "schemars", "serde", "serde_json", @@ -4251,11 +4552,11 @@ dependencies = [ "dirs", "flate2", "futures-util", - "http", + "http 1.1.0", "infer", "minisign-verify", "percent-encoding", - "reqwest", + "reqwest 0.12.9", "semver", "serde", "serde_json", @@ -4294,7 +4595,7 @@ checksum = "a1ef7363e7229ac8d04e8a5d405670dbd43dde8fc4bc3bc56105c35452d03784" dependencies = [ "dpi", "gtk", - "http", + "http 1.1.0", "jni", "raw-window-handle", "serde", @@ -4312,7 +4613,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62fa2068e8498ad007b54d5773d03d57c3ff6dd96f8c8ce58beff44d0d5e0d30" dependencies = [ "gtk", - "http", + "http 1.1.0", "jni", "log", "objc2", @@ -4485,6 +4786,7 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -4504,6 +4806,16 @@ dependencies = [ "syn 2.0.86", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.0" @@ -4817,6 +5129,12 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.0" @@ -5509,6 +5827,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winreg" version = "0.52.0" @@ -5533,7 +5861,7 @@ dependencies = [ "gdkx11", "gtk", "html5ever", - "http", + "http 1.1.0", "javascriptcore-rs", "jni", "kuchikiki", @@ -5611,6 +5939,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "xmltv" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe9c95973cc08e1ebbc63705f61643d63a72614dd42d4c33f0e789da5c96b3d" +dependencies = [ + "serde", +] + [[package]] name = "zbus" version = "5.1.1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a9003ecd0..4d1397eb1 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "IPTVnator" -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" description = "IPTVnator" authors = ["4gray"] license = "MIT" @@ -21,7 +21,7 @@ tauri-build = { version = "2.0.1", features = [] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } log = "0.4" -tauri = { version = "2.0.6", features = [] } +tauri = { version = "2.0.6", features = ["macos-private-api"] } tauri-plugin-log = "2.0.0-rc" tauri-plugin-http = {version = "2", features = ["unsafe-headers"] } tauri-plugin-shell = "2" @@ -30,6 +30,12 @@ tauri-plugin-os = "2" tauri-plugin-fs = "2" tauri-plugin-process = "2" tauri-plugin-dialog = "2" +xmltv = "0.9" +tokio = { version = "1.0", features = ["full"] } +chrono = { version = "0.4", features = ["serde"] } +reqwest = { version = "0.11", features = ["json", "gzip"] } +flate2 = "1.0" +quick-xml = { version = "0.31", features = ["serialize"] } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-updater = "2" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 4e39916e2..5522a2272 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "iptvnator", - "version": "1.0.0-2", + "version": "1.0.0-3", "identifier": "com.4gray.dev", "build": { "frontendDist": "../dist", @@ -28,6 +28,7 @@ "titleBarStyle": "Transparent" } ], + "macOSPrivateApi": true, "security": { "csp": { "default-src": [ From 1d3382af75d492014ffb44fc97aeceed59ea0fb7 Mon Sep 17 00:00:00 2001 From: 4gray Date: Wed, 27 Nov 2024 18:44:37 +0100 Subject: [PATCH 02/14] feat: add fullscreen capability to window settings Adds the "core:window:allow-set-fullscreen" capability to the default configuration. This change enables the application to support fullscreen mode, enhancing user experience and providing more flexibility in window management --- src-tauri/capabilities/default.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index fcd9eeb76..e1246c7be 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -39,6 +39,7 @@ "fs:default", "updater:default", "dialog:default", - "process:default" + "process:default", + "core:window:allow-set-fullscreen" ] } From 3d0dd1de52f5e121916224d1535e1d02f21d530f Mon Sep 17 00:00:00 2001 From: 4gray Date: Wed, 27 Nov 2024 18:51:02 +0100 Subject: [PATCH 03/14] feat: disable spellcheck and autocomplete in search input Add `spellcheck="false"` and `autocomplete="off"` attributes to the search input field in the channel list component. This improves user experience by preventing the browser from suggesting incorrect spellings and previously entered values, ensuring a cleaner and more focused search experience. --- .../channel-list-container.component.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/player/components/channel-list-container/channel-list-container.component.html b/src/app/player/components/channel-list-container/channel-list-container.component.html index 746a1ff2b..563f79ae7 100644 --- a/src/app/player/components/channel-list-container/channel-list-container.component.html +++ b/src/app/player/components/channel-list-container/channel-list-container.component.html @@ -13,6 +13,8 @@ matInput class="full-width" type="search" + spellcheck="false" + autocomplete="off" #search [placeholder]="'CHANNELS.SEARCH_CHANNEL' | translate" [(ngModel)]="searchTerm.name" From 2a2e8318772dc851403515f51f721c4df60c735d Mon Sep 17 00:00:00 2001 From: 4gray Date: Wed, 27 Nov 2024 18:51:26 +0100 Subject: [PATCH 04/14] refactor(playlists): improve error handling and filtering logic Enhance the `getPortalFavorites` and `getPortalLiveStreamFavorites` methods to include null checks for the fetched items. This prevents potential runtime errors when accessing properties of undefined. Additionally, improve code readability by restructuring the filter logic a more explicit format. Add a console log for the parsed playlist in the `addPortalFavorite` method for debugging purposes. --- src/app/services/playlists.service.ts | 35 +++++++++++++++++++-------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/app/services/playlists.service.ts b/src/app/services/playlists.service.ts index 7c2d687c4..227c641e6 100644 --- a/src/app/services/playlists.service.ts +++ b/src/app/services/playlists.service.ts @@ -143,20 +143,34 @@ export class PlaylistsService { getPortalFavorites(portalId: string) { return this.dbService - .getByID<{ favorites: Partial[] }>( - DbStores.Playlists, - portalId - ) - .pipe(map((item) => item.favorites.filter(itm => itm && itm.stream_type && itm.stream_type !== 'live') ?? [])); + .getByID<{ + favorites: Partial[]; + }>(DbStores.Playlists, portalId) + .pipe( + map((item) => { + if (!item || !item.favorites) return []; + return item.favorites.filter( + (itm) => + itm && itm.stream_type && itm.stream_type !== 'live' + ); + }) + ); } getPortalLiveStreamFavorites(portalId: string) { return this.dbService - .getByID<{ favorites: Partial[] }>( - DbStores.Playlists, - portalId - ) - .pipe(map((item) => item.favorites.filter(itm => itm && itm.stream_type && itm.stream_type === 'live') ?? [])); + .getByID<{ + favorites: Partial[]; + }>(DbStores.Playlists, portalId) + .pipe( + map((item) => { + if (!item || !item.favorites) return []; + return item.favorites.filter( + (itm) => + itm && itm.stream_type && itm.stream_type === 'live' + ); + }) + ); } addPortalFavorite(portalId: string, item: any) { @@ -217,6 +231,7 @@ export class PlaylistsService { ) { try { const parsedPlaylist = parse(playlist); + console.log(parsedPlaylist); return createPlaylistObject( title, parsedPlaylist, From 6a8d6a645272d26a0a472e111b564acfb20b26d6 Mon Sep 17 00:00:00 2001 From: 4gray Date: Wed, 27 Nov 2024 18:51:39 +0100 Subject: [PATCH 05/14] feat(i18n): add success message for EPG fetch Adds a new translation key "FETCH_SUCCESS" to English localization, providing a success for when the EPG is fetched successfully. This improves user feedback and enhances the overall user experience. --- src/assets/i18n/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 3a03add75..86dfa3fd3 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -192,7 +192,8 @@ "DESCRIPTION": "Description", "LANGUAGE": "Language", "SHOW_PROGRAM_DETAILS": "Show details about this program" - } + }, + "FETCH_SUCCESS": "EPG was fetched successfully" }, "LANGUAGES": { "ENGLISH": "English", From 7c49995e239a2ab5b84ad1e966c1201cee6f6dc1 Mon Sep 17 00:00:00 2001 From: 4gray Date: Wed, 27 Nov 2024 18:56:08 +0100 Subject: [PATCH 06/14] feat(epg): implement-source EPG data fetching Add support for multiple EPG sources by introducing a new `EpgStore` structure that holds a vector of `EpgSource` instances. Replace the static `EPG_DATA` with a thread-safe `OnceCell` containing a mutex-protected `EpgStore`. Implement the `fetch_and_parse_epg` function to fetch and update EPG data from a given URL, handling both gzipped and regular XML data formats. Enhance error handling and logging for better traceability during data fetching and parsing. --- src-tauri/src/epg.rs | 235 +++++++++++++++++++++++++++++++++++++++++++ src-tauri/src/lib.rs | 27 ++++- 2 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 src-tauri/src/epg.rs diff --git a/src-tauri/src/epg.rs b/src-tauri/src/epg.rs new file mode 100644 index 000000000..4912702c0 --- /dev/null +++ b/src-tauri/src/epg.rs @@ -0,0 +1,235 @@ +use chrono::{DateTime, Utc}; +use flate2::read::GzDecoder; +use log::{info, warn}; +use quick_xml::de::from_str; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::io::Read; +use std::sync::Mutex; +use tokio::sync::OnceCell; +use xmltv::Tv; + +// Modified to store multiple EPG sources +#[derive(Debug)] +struct EpgStore { + sources: Vec, +} + +#[derive(Debug)] +struct EpgSource { + url: String, + programs: HashMap>, +} + +// Replace the static EPG_DATA with the new structure +static EPG_DATA: OnceCell>> = OnceCell::const_new(); + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Program { + pub start: DateTime, + pub stop: DateTime, + pub title: String, + pub desc: Option, + pub channel: String, + pub category: Option, + pub icon: Option, + pub language: Option, +} + +fn parse_xmltv_datetime(datetime: &str) -> Option> { + DateTime::parse_from_str(datetime, "%Y%m%d%H%M%S %z") + .ok() + .map(|dt| dt.with_timezone(&Utc)) +} + +pub async fn fetch_and_parse_epg(url: String) -> Result<(), Box> { + info!("Starting EPG fetch from URL: {}", url); + + let new_source = fetch_single_epg_source(url.clone()).await?; + + // Update EPG store + match EPG_DATA.get() { + Some(epg_data) => { + let mut guard = epg_data.lock().unwrap(); + let store = guard.get_or_insert_with(|| EpgStore { + sources: Vec::new(), + }); + + // Remove existing source with same URL if exists + store.sources.retain(|source| source.url != url); + // Add new source + store.sources.push(new_source); + info!("Updated EPG store for URL: {}", url); + } + None => { + EPG_DATA + .set(Mutex::new(Some(EpgStore { + sources: vec![new_source], + }))) + .unwrap(); + info!("Created new EPG data store"); + } + } + + Ok(()) +} + +// New helper function to fetch and parse a single EPG source +async fn fetch_single_epg_source(url: String) -> Result> { + info!("Starting EPG fetch from URL: {}", url); + + // Fetch content + let client = reqwest::Client::new(); + let content = if url.ends_with(".gz") { + info!("Fetching and decompressing gzipped EPG data..."); + let bytes = client.get(&url).send().await?.bytes().await?; + let mut decoder = GzDecoder::new(&bytes[..]); + let mut string = String::new(); + decoder.read_to_string(&mut string)?; + info!("Successfully decompressed EPG data"); + string + } else { + info!("Fetching EPG data..."); + client.get(&url).send().await?.text().await? + }; + + info!("Parsing EPG XML data..."); + let tv: Tv = from_str(&content)?; + + // Get channel name mapping + let channel_names: HashMap = tv + .channels + .into_iter() + .filter_map(|channel| { + channel + .display_names + .first() + .map(|name| (channel.id, name.name.clone())) + }) + .collect(); + + info!("Converting EPG programs..."); + let programs: Vec = tv + .programmes + .into_iter() + .filter_map(|p| { + let channel_name = match channel_names.get(&p.channel) { + Some(name) => name.clone(), + None => { + warn!("No channel name found for id: {}", p.channel); + return None; + } + }; + + let parse_datetime = |opt_str: &Option| -> Option> { + opt_str.as_deref().and_then(parse_xmltv_datetime) + }; + + let start = match parse_datetime(&Some(p.start)) { + Some(dt) => dt, + None => { + warn!( + "Invalid start time format for program in channel {}", + p.channel + ); + return None; + } + }; + + let stop = match parse_datetime(&p.stop) { + Some(dt) => dt, + None => { + warn!( + "Invalid stop time format for program in channel {}", + p.channel + ); + return None; + } + }; + + let title = match p.titles.first() { + Some(t) => t.value.clone(), + None => { + warn!("Program without title found in channel {}", p.channel); + return None; + } + }; + + Some(Program { + start, + stop, + title, + desc: p.descriptions.first().map(|d| d.value.clone()), + channel: channel_name, + category: p.categories.first().map(|c| c.name.clone()), + icon: p.icons.first().map(|i| i.src.clone()), + language: p.language.map(|lang| lang.value), + }) + }) + .collect(); + + info!("Successfully parsed {} EPG programs", programs.len()); + + // Group by channel + let program_map = programs + .into_iter() + .fold(HashMap::new(), |mut acc, program| { + acc.entry(program.channel.clone()) + .or_insert_with(Vec::new) + .push(program); + acc + }); + + info!("EPG data grouped into {} channels", program_map.len()); + + Ok(EpgSource { + url, + programs: program_map, + }) +} + +pub async fn get_programs_by_channel( + channel_name: String, +) -> Result, Box> { + info!("Retrieving programs for channel name: {}", channel_name); + + let programs = match EPG_DATA.get() { + Some(data) => { + let guard = data.lock().unwrap(); + match &*guard { + Some(store) => { + // Search through all sources and combine matches + let mut all_programs = Vec::new(); + for source in &store.sources { + if let Some(programs) = source.programs.get(&channel_name) { + info!( + "Found {} programs for channel {} in source {}", + programs.len(), + channel_name, + source.url + ); + all_programs.extend(programs.clone()); + } + } + // Sort programs by start time + all_programs.sort_by(|a, b| a.start.cmp(&b.start)); + all_programs + } + None => Vec::new(), + } + } + None => Vec::new(), + }; + + if programs.is_empty() { + warn!("No programs found for channel name: {}", channel_name); + } else { + info!( + "Found {} total programs for channel {}", + programs.len(), + channel_name + ); + } + + Ok(programs) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3151cf083..6a0ccd723 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -78,6 +78,26 @@ async fn open_in_vlc(url: String, path: String) -> Result<(), String> { Ok(()) } +mod epg; +use epg::get_programs_by_channel; + +#[tauri::command] +async fn fetch_epg(url: Vec) -> Result<(), String> { + for single_url in url { + epg::fetch_and_parse_epg(single_url) + .await + .map_err(|e| e.to_string())?; + } + Ok(()) +} + +#[tauri::command] +async fn get_channel_programs(channel_id: String) -> Result, String> { + get_programs_by_channel(channel_id) + .await + .map_err(|e| e.to_string()) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() @@ -89,7 +109,12 @@ pub fn run() { .plugin(tauri_plugin_window_state::Builder::new().build()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_http::init()) - .invoke_handler(tauri::generate_handler![open_in_mpv, open_in_vlc]) + .invoke_handler(tauri::generate_handler![ + fetch_epg, + get_channel_programs, + open_in_mpv, + open_in_vlc + ]) .setup(|app| { if cfg!(debug_assertions) { app.handle().plugin( From 400f97baf4e0b3bc3963cefa21437d4a01119cd6 Mon Sep 17 00:00:00 2001 From: 4gray Date: Wed, 27 Nov 2024 20:11:01 +0100 Subject: [PATCH 07/14] fix: update EPG item description and refactor component Ref the EPG item to directly display the category and description values instead of accessing the first element of the arrays. Update the EpgListComponent to improve the handling of EPG data and selected date filtering. Change references from Electron to Tauri in the component to reflect the current framework usage. --- src/app/app.component.ts | 4 - .../channel-list-container.component.ts | 10 +- .../epg-item-description.component.html | 6 +- .../epg-list-item.component.html | 2 +- .../epg-list/epg-list.component.html | 189 ++++++++---------- .../epg-list/epg-list.component.scss | 1 + .../components/epg-list/epg-list.component.ts | 138 +++++++------ .../html-video-player.component.ts | 79 +++++--- .../info-overlay/info-overlay.component.html | 6 +- .../multi-epg-container.component.html | 14 +- .../multi-epg-container.component.scss | 3 +- .../toolbar/toolbar.component.html | 5 +- .../video-player/video-player.component.html | 2 +- .../video-player/video-player.component.ts | 6 +- src/app/player/models/epg-channel.model.ts | 2 +- src/app/services/epg.service.ts | 139 +++++++++---- src/app/services/tauri.service.ts | 11 +- src/app/settings/settings.component.html | 18 +- src/app/settings/settings.component.ts | 22 +- 19 files changed, 379 insertions(+), 278 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index a2a9c59e8..d9123d77d 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -12,8 +12,6 @@ import * as semver from 'semver'; import { IpcCommand } from '../../shared/ipc-command.class'; import { AUTO_UPDATE_PLAYLISTS, - EPG_ERROR, - EPG_FETCH_DONE, ERROR, OPEN_FILE, SETTINGS_UPDATE, @@ -50,8 +48,6 @@ export class AppComponent { commandsList = [ new IpcCommand(VIEW_ADD_PLAYLIST, () => this.navigateToRoute('/')), new IpcCommand(VIEW_SETTINGS, () => this.navigateToRoute('/settings')), - new IpcCommand(EPG_FETCH_DONE, () => this.epgService.onEpgFetchDone()), - new IpcCommand(EPG_ERROR, () => this.epgService.onEpgError()), new IpcCommand(SHOW_WHATS_NEW, () => this.showWhatsNewDialog()), new IpcCommand(ERROR, (response: { message: string; status: number }) => this.showErrorAsNotification(response) diff --git a/src/app/player/components/channel-list-container/channel-list-container.component.ts b/src/app/player/components/channel-list-container/channel-list-container.component.ts index 7bfba83c2..bd9540891 100644 --- a/src/app/player/components/channel-list-container/channel-list-container.component.ts +++ b/src/app/player/components/channel-list-container/channel-list-container.component.ts @@ -22,6 +22,7 @@ import { TranslateModule, TranslateService } from '@ngx-translate/core'; import * as _ from 'lodash'; import { map, skipWhile } from 'rxjs'; import { Channel } from '../../../../../shared/channel.interface'; +import { EpgService } from '../../../services/epg.service'; import { FilterPipe } from '../../../shared/pipes/filter.pipe'; import * as PlaylistActions from '../../../state/actions'; import { @@ -116,7 +117,8 @@ export class ChannelListContainerComponent { constructor( private readonly store: Store, private snackBar: MatSnackBar, - private translateService: TranslateService + private translateService: TranslateService, + private epgService: EpgService ) {} /** @@ -126,6 +128,12 @@ export class ChannelListContainerComponent { selectChannel(channel: Channel): void { this.selected = channel; this.store.dispatch(PlaylistActions.setActiveChannel({ channel })); + + const epgChannelId = channel?.tvg?.id || channel?.name; + + if (epgChannelId) { + this.epgService.getChannelPrograms(epgChannelId); + } } /** diff --git a/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html b/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html index 297a15d4f..73fbe21b2 100644 --- a/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html +++ b/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html @@ -6,7 +6,7 @@
{{ 'EPG.PROGRAM_DIALOG.TITLE' | translate }}
-

{{ epgProgram.title[0].value }}

+

{{ epgProgram.title }}

{{ 'EPG.PROGRAM_DIALOG.LANGUAGE' | translate }}
@@ -16,13 +16,13 @@
{{ 'EPG.PROGRAM_DIALOG.CATEGORY' | translate }}
-

{{ epgProgram.category[0].value }}

+

{{ epgProgram.category }}

{{ 'EPG.PROGRAM_DIALOG.DESCRIPTION' | translate }}
-

{{ epgProgram.desc[0].value }}

+

{{ epgProgram.desc }}

diff --git a/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.html b/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.html index 2f30b200e..d06100f57 100644 --- a/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.html +++ b/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.html @@ -17,7 +17,7 @@ > } -@if (item.desc.length > 0) { +@if (item.desc?.length > 0) { -
-
-
- @if (channel?.icon) { - - } -
-
-
- {{ channel?.name[0]?.value }} -
-
- {{ - playingNow - ? playingNow.title[0]?.value - : ('EPG.LIVE_STREAM' | translate) - }} -
-
+@let items = filteredItems$ | async; +@let timeshiftUntil = timeshiftUntil$ | async; + + +
+
+
+ @if (channel?.icon) { + + }
- -
- - - {{ dateToday | momentDate: 'YYYYMMDD' : 'MMMM Do, dddd' }} - +
+
+ {{ channel?.name }} +
+
+ {{ + playingNow + ? playingNow.title + : ('EPG.LIVE_STREAM' | translate) + }} +
- - @let timeshiftUntil = timeshiftUntil$ | async; - @if (timeshiftUntil) { - - + + + {{ dateToday | momentDate: 'YYYY-MM-DD' : 'MMMM Do, dddd' }} + +
+
+ + +@if (timeshiftUntil) { + + + @if (items?.length > 0) { + @for (program of items; track program) { + @if ( + program.start < timeshiftUntil || program.start >= timeNow + ) { + +
+ +
+

{{ program?.title }}

+
+ } @else {
-

{{ item?.title[0]?.value }}

+

- - -
- -
-

-
-
- - - } @else { -

- {{ 'EPG.EPG_NOT_AVAILABLE_DATE' | translate }} -

+ } + } -
- } -} @else { - - - {{ 'EPG.EPG_NOT_AVAILABLE_CHANNEL_TITLE' | translate }}
- {{ 'EPG.EPG_NOT_AVAILABLE_CHANNEL_DESCRIPTION' | translate }} -
-
+ } @else { +

+ {{ 'EPG.EPG_NOT_AVAILABLE_DATE' | translate }} +

+ } + } diff --git a/src/app/player/components/epg-list/epg-list.component.scss b/src/app/player/components/epg-list/epg-list.component.scss index 6a89462c1..23848830d 100644 --- a/src/app/player/components/epg-list/epg-list.component.scss +++ b/src/app/player/components/epg-list/epg-list.component.scss @@ -7,6 +7,7 @@ left: 0; bottom: 0; height: 100%; + overflow-x: hidden; } .active { diff --git a/src/app/player/components/epg-list/epg-list.component.ts b/src/app/player/components/epg-list/epg-list.component.ts index 69111be17..9dbe62359 100644 --- a/src/app/player/components/epg-list/epg-list.component.ts +++ b/src/app/player/components/epg-list/epg-list.component.ts @@ -1,5 +1,5 @@ import { AsyncPipe } from '@angular/common'; -import { Component, NgZone } from '@angular/core'; +import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatDividerModule } from '@angular/material/divider'; @@ -9,10 +9,11 @@ import { MatTooltipModule } from '@angular/material/tooltip'; import { Store } from '@ngrx/store'; import { TranslateModule } from '@ngx-translate/core'; import moment from 'moment'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { EPG_GET_PROGRAM_DONE } from '../../../../../shared/ipc-commands'; import { DataService } from '../../../services/data.service'; +import { EpgService } from '../../../services/epg.service'; import { MomentDatePipe } from '../../../shared/pipes/moment-date.pipe'; import { resetActiveEpgProgram, @@ -29,8 +30,7 @@ export interface EpgData { items: EpgProgram[]; } -const DATE_FORMAT = 'YYYYMMDD'; -const DATE_TIME_FORMAT = 'YYYYMMDDHHmm ZZ'; +const DATE_FORMAT = 'YYYY-MM-DD'; @Component({ standalone: true, @@ -58,7 +58,7 @@ export class EpgListComponent { dateToday: string; /** Array with EPG programs */ - items: EpgProgram[] = []; + items$ = this.epgService.currentEpgPrograms$; /** Object with epg programs for the active channel */ programs: { @@ -77,61 +77,65 @@ export class EpgListComponent { /** Timeshift availability date, based on tvg-rec value from the channel */ timeshiftUntil$: Observable; - /** - * Creates an instance of EpgListComponent - * @param store - * @param electronService - * @param ngZone - */ + private readonly selectedDate$ = new BehaviorSubject( + moment().format(DATE_FORMAT) + ); + + /** Filtered EPG programs based on selected date */ + filteredItems$ = combineLatest([this.items$, this.selectedDate$]).pipe( + map(([items, selectedDate]) => + items + .filter( + (item) => + moment(item.start).format('YYYY-MM-DD') === selectedDate + ) + .sort((a, b) => moment(a.start).diff(moment(b.start))) + ) + ); + constructor( private readonly store: Store, - private electronService: DataService, - private ngZone: NgZone - ) { - this.electronService.listenOn( - EPG_GET_PROGRAM_DONE, - (event, response) => { - this.ngZone.run(() => this.handleEpgData(response)); - } - ); - } + private dataService: DataService, + private readonly epgService: EpgService + ) {} /** * Subscribe for values from the store on component init */ ngOnInit(): void { this.timeshiftUntil$ = this.store.select(selectActive).pipe( - // eslint-disable-next-line @ngrx/avoid-mapping-selectors map((active) => { + console.log(active); + this.channel = { + id: active?.tvg?.id, + name: active?.name, + url: [active?.url], + icon: [active?.tvg?.logo], + }; return ( active?.tvg?.rec || active?.timeshift || active?.catchup?.days ); }), - map((value) => - moment(Date.now()) - .subtract(value, 'days') - .format(DATE_TIME_FORMAT) - ) + map((value) => moment().subtract(value, 'days').toISOString()) ); + + this.items$.subscribe((programs) => this.handleEpgData(programs)); + this.dateToday = moment().format(DATE_FORMAT); + this.selectedDate$.next(this.dateToday); } /** * Handles incoming epg programs for the active channel from the main process * @param programs */ - handleEpgData(programs: { payload: EpgData }): void { - if (programs?.payload?.items?.length > 0) { - this.programs = programs; - this.timeNow = moment(Date.now()).format(DATE_TIME_FORMAT); - this.dateToday = moment(Date.now()).format(DATE_FORMAT); - this.channel = programs.payload?.channel; - this.items = this.selectPrograms(programs); - + handleEpgData(programs: EpgProgram[]): void { + this.timeNow = new Date().toISOString(); + this.dateToday = moment().format(DATE_FORMAT); + if (programs.length > 0) { this.setPlayingNow(); } else { - this.items = []; this.channel = null; this.store.dispatch(setCurrentEpgProgram(undefined)); } @@ -139,23 +143,20 @@ export class EpgListComponent { /** * Selects the program based on the active date - * @param programs object with all available epg programs for the active channel */ selectPrograms(programs: { payload: EpgData }): EpgProgram[] { + const selectedDate = moment(this.dateToday).format('YYYY-MM-DD'); return programs.payload?.items - .filter((item) => item.start.includes(this.dateToday.toString())) + .filter( + (item) => + moment(item.start).format('YYYY-MM-DD') === selectedDate + ) .map((program) => ({ ...program, - start: moment(program.start, DATE_TIME_FORMAT).format( - DATE_TIME_FORMAT - ), - stop: moment(program.stop, DATE_TIME_FORMAT).format( - DATE_TIME_FORMAT - ), + start: program.start, // Keep ISO format + stop: program.stop, // Keep ISO format })) - .sort((a, b) => { - return a.start.localeCompare(b.start); - }); + .sort((a, b) => moment(a.start).diff(moment(b.start))); } /** @@ -163,28 +164,37 @@ export class EpgListComponent { * @param direction direction to switch */ changeDate(direction: 'next' | 'prev'): void { - let dateToSwitch; - if (direction === 'next') { - dateToSwitch = moment(this.dateToday, DATE_FORMAT) - .add(1, 'days') - .format(DATE_FORMAT); - } else if (direction === 'prev') { - dateToSwitch = moment(this.dateToday, DATE_FORMAT) - .subtract(1, 'days') - .format(DATE_FORMAT); - } - this.dateToday = dateToSwitch; - this.items = this.selectPrograms(this.programs); + const newDate = moment(this.selectedDate$.value) + [direction === 'next' ? 'add' : 'subtract'](1, 'days') + .format(DATE_FORMAT); + + this.dateToday = newDate; + this.selectedDate$.next(newDate); } /** * Sets the playing now variable based on the current time */ setPlayingNow(): void { - this.playingNow = this.items.find( - (item) => this.timeNow >= item.start && this.timeNow <= item.stop - ); - this.store.dispatch(setCurrentEpgProgram({ program: this.playingNow })); + this.items$ + .pipe( + map((items) => + items.find((item) => { + const now = new Date().toISOString(); + const start = new Date(item.start).toISOString(); + const stop = new Date(item.stop).toISOString(); + return now >= start && now <= stop; + }) + ) + ) + .subscribe((playingNow) => { + this.playingNow = playingNow; + if (playingNow) { + this.store.dispatch( + setCurrentEpgProgram({ program: playingNow }) + ); + } + }); } /** @@ -211,6 +221,6 @@ export class EpgListComponent { * Removes all ipc renderer listeners after destroy */ ngOnDestroy(): void { - this.electronService.removeAllListeners(EPG_GET_PROGRAM_DONE); + this.dataService.removeAllListeners(EPG_GET_PROGRAM_DONE); } } diff --git a/src/app/player/components/html-video-player/html-video-player.component.ts b/src/app/player/components/html-video-player/html-video-player.component.ts index 9624d94d3..d32dda43f 100644 --- a/src/app/player/components/html-video-player/html-video-player.component.ts +++ b/src/app/player/components/html-video-player/html-video-player.component.ts @@ -75,32 +75,61 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy { if (channel.url) { const url = channel.url + (channel.epgParams ?? ''); const extension = getExtensionFromUrl(channel.url); - this.dataService.sendIpcEvent(CHANNEL_SET_USER_AGENT, { - userAgent: channel.http?.['user-agent'] ?? '', - referer: channel.http?.referrer ?? '', - }); - if ( - extension !== 'mp4' && - extension !== 'mpv' && - Hls && - Hls.isSupported() - ) { - console.log('... switching channel to ', channel.name, url); - this.hls = new Hls(); - this.hls.attachMedia(this.videoPlayer.nativeElement); - this.hls.loadSource(url); - - this.handlePlayOperation(); - } else { - console.error('something wrong with hls.js init...'); - this.addSourceToVideo( - this.videoPlayer.nativeElement, - url, - 'video/mp4' - ); - this.videoPlayer.nativeElement.play(); - } + // Send IPC event and handle the response + this.dataService + .sendIpcEvent(CHANNEL_SET_USER_AGENT, { + userAgent: channel.http?.['user-agent'] ?? '', + referer: channel.http?.referrer ?? '', + }) + .then(() => { + if ( + extension !== 'mp4' && + extension !== 'mpv' && + Hls && + Hls.isSupported() + ) { + console.log( + '... switching channel to ', + channel.name, + url + ); + this.hls = new Hls(); + this.hls.attachMedia(this.videoPlayer.nativeElement); + this.hls.loadSource(url); + this.handlePlayOperation(); + } else { + console.log('Using native video player...'); + this.addSourceToVideo( + this.videoPlayer.nativeElement, + url, + 'video/mp4' + ); + this.videoPlayer.nativeElement.play(); + } + }) + .catch((error) => { + console.error('Error setting user agent:', error); + // Continue playback even if setting user agent fails + if ( + extension !== 'mp4' && + extension !== 'mpv' && + Hls && + Hls.isSupported() + ) { + this.hls = new Hls(); + this.hls.attachMedia(this.videoPlayer.nativeElement); + this.hls.loadSource(url); + this.handlePlayOperation(); + } else { + this.addSourceToVideo( + this.videoPlayer.nativeElement, + url, + 'video/mp4' + ); + this.videoPlayer.nativeElement.play(); + } + }); } } diff --git a/src/app/player/components/info-overlay/info-overlay.component.html b/src/app/player/components/info-overlay/info-overlay.component.html index 224bdc013..9794878ea 100644 --- a/src/app/player/components/info-overlay/info-overlay.component.html +++ b/src/app/player/components/info-overlay/info-overlay.component.html @@ -6,17 +6,17 @@ >
- {{ epgProgram?.title[0]?.value }} | {{ channel.name }} + {{ epgProgram?.title }} | {{ channel.name }}
- {{ epgProgram?.desc[0]?.value }} + {{ epgProgram?.desc }}
- @if (item.icon[0]) { + @if (item.icon) { } - @if (!item.icon[0]) { + @if (!item.icon) {
- {{ item.name[0].value }} + {{ item.name }}
} @@ -99,7 +99,7 @@ @@ -118,7 +118,7 @@ [attr.x]="program.startPosition" >
diff --git a/src/app/player/components/multi-epg/multi-epg-container.component.scss b/src/app/player/components/multi-epg/multi-epg-container.component.scss index 17e51e4a6..e787f143a 100644 --- a/src/app/player/components/multi-epg/multi-epg-container.component.scss +++ b/src/app/player/components/multi-epg/multi-epg-container.component.scss @@ -28,7 +28,7 @@ rect { #epg-container { width: calc(100vw - 100px); - height: calc(100vh - 73px); + height: calc(100vh - 42px); overflow-x: scroll; overflow-y: hidden; } @@ -53,7 +53,6 @@ rect { #epg-navigation { display: flex; background-color: white; - margin-top: 30px; border-bottom: 2px solid #fff; align-items: center; } diff --git a/src/app/player/components/video-player/toolbar/toolbar.component.html b/src/app/player/components/video-player/toolbar/toolbar.component.html index 937ce8b4b..4ba9644bf 100644 --- a/src/app/player/components/video-player/toolbar/toolbar.component.html +++ b/src/app/player/components/video-player/toolbar/toolbar.component.html @@ -23,7 +23,8 @@ {{ activeChannel?.name }}
- + @let isEpgAvailable = isEpgAvailable$ | async; + @if (isEpgAvailable) { - + } diff --git a/src/app/player/components/video-player/video-player.component.html b/src/app/player/components/video-player/video-player.component.html index 327a10371..8bbc053e6 100644 --- a/src/app/player/components/video-player/video-player.component.html +++ b/src/app/player/components/video-player/video-player.component.html @@ -90,6 +90,6 @@ - + diff --git a/src/app/player/components/video-player/video-player.component.ts b/src/app/player/components/video-player/video-player.component.ts index 482869683..19da38575 100644 --- a/src/app/player/components/video-player/video-player.component.ts +++ b/src/app/player/components/video-player/video-player.component.ts @@ -118,7 +118,7 @@ export class VideoPlayerComponent implements OnInit, OnDestroy { listeners = []; - isElectron = this.dataService.isElectron; + isTauri = this.dataService.getAppEnvironment() === 'tauri'; sidebarView: SidebarView = 'CHANNELS'; @@ -210,7 +210,7 @@ export class VideoPlayerComponent implements OnInit, OnDestroy { */ setRendererListeners(): void { this.commandsList.forEach((command) => { - if (this.isElectron) { + if (this.isTauri) { this.dataService.listenOn(command.id, (event, response) => this.ngZone.run(() => command.execute(response)) ); @@ -243,7 +243,7 @@ export class VideoPlayerComponent implements OnInit, OnDestroy { } ngOnDestroy() { - if (this.isElectron) { + if (this.isTauri) { this.dataService.removeAllListeners(PLAYLIST_PARSE_RESPONSE); } else { this.listeners.forEach((listener) => diff --git a/src/app/player/models/epg-channel.model.ts b/src/app/player/models/epg-channel.model.ts index 1f3fbcb28..f0c47e4d1 100644 --- a/src/app/player/models/epg-channel.model.ts +++ b/src/app/player/models/epg-channel.model.ts @@ -1,6 +1,6 @@ export interface EpgChannel { id: string; - name: { lang: string; value: string }[]; + name: string; icon: string[]; url: string[]; } diff --git a/src/app/services/epg.service.ts b/src/app/services/epg.service.ts index a4b0c6d45..1164c2d7e 100644 --- a/src/app/services/epg.service.ts +++ b/src/app/services/epg.service.ts @@ -1,66 +1,125 @@ import { Injectable } from '@angular/core'; -import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar'; +import { MatSnackBar } from '@angular/material/snack-bar'; import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; -import { EPG_FETCH } from '../../../shared/ipc-commands'; -import { setEpgAvailableFlag } from '../state/actions'; -import { DataService } from './data.service'; +import { invoke } from '@tauri-apps/api/core'; +import { BehaviorSubject, from } from 'rxjs'; +import { catchError, map, tap } from 'rxjs/operators'; +import { EpgProgram } from '../player/models/epg-program.model'; +import * as PlaylistActions from '../state/actions'; @Injectable({ providedIn: 'root', }) export class EpgService { - /** Default options for epg snackbar notifications */ - epgSnackBarOptions: MatSnackBarConfig = { - verticalPosition: 'bottom', - horizontalPosition: 'start', - }; + private epgAvailable = new BehaviorSubject(false); + private currentEpgPrograms = new BehaviorSubject([]); + + epgAvailable$ = this.epgAvailable.asObservable(); + currentEpgPrograms$ = this.currentEpgPrograms.asObservable(); constructor( - private store: Store, - private electronService: DataService, private snackBar: MatSnackBar, - private translate: TranslateService + private translate: TranslateService, + private store: Store ) {} /** - * Fetches and updates EPG from the given sources - * @param urls epg source urls + * Fetches EPG from the given URLs */ - fetchEpg(urls: string | string[]) { - const urlsArray = Array.isArray(urls) ? urls : [urls]; - urlsArray.forEach((url) => - this.electronService.sendIpcEvent(EPG_FETCH, { - url, - }) - ); + fetchEpg(urls: string[]): void { this.showFetchSnackbar(); + + // Filter out empty URLs and send all URLs at once + const validUrls = urls.filter((url) => url?.trim()); + if (validUrls.length === 0) return; + + from(invoke('fetch_epg', { url: validUrls })) + .pipe( + tap(() => { + this.epgAvailable.next(true); + this.showSuccessSnackbar(); + }), + catchError((err) => { + console.error('EPG fetch error:', err); + this.epgAvailable.next(false); + this.showErrorSnackbar(); + throw err; + }) + ) + .subscribe(); } - showFetchSnackbar() { + /** + * Gets EPG programs for a specific channel + */ + getChannelPrograms(channelId: string): void { + console.log('Fetching EPG for channel ID:', channelId); + from(invoke('get_channel_programs', { channelId })) + .pipe( + tap((programs) => { + console.log('Received programs:', programs); + }), + map((programs) => + programs.map((program) => ({ + ...program, + start: new Date(program.start).toISOString(), + stop: new Date(program.stop).toISOString(), + })) + ), + catchError((err) => { + console.error('EPG get programs error:', err); + this.showErrorSnackbar(); + this.currentEpgPrograms.next([]); + throw err; + }) + ) + .subscribe((programs) => { + if (programs.length === 0) { + this.store.dispatch( + PlaylistActions.setEpgAvailableFlag({ value: false }) + ); + } else { + this.store.dispatch( + PlaylistActions.setEpgAvailableFlag({ value: true }) + ); + } + this.currentEpgPrograms.next(programs); + console.log('Updated programs:', programs); // Debug log + }); + } + + /** + * Shows fetch in progress snackbar + */ + showFetchSnackbar(): void { + this.snackBar.open(this.translate.instant('EPG.FETCH_EPG'), null, { + duration: 2000, + horizontalPosition: 'start', + }); + } + + /** + * Shows success snackbar + */ + private showSuccessSnackbar(): void { + this.snackBar.open(this.translate.instant('EPG.FETCH_SUCCESS'), null, { + duration: 2000, + horizontalPosition: 'start', + }); + } + + /** + * Shows error snackbar + */ + private showErrorSnackbar(): void { this.snackBar.open( - this.translate.instant('EPG.FETCH_EPG'), + this.translate.instant('EPG.ERROR'), this.translate.instant('CLOSE'), - this.epgSnackBarOptions - ); - } - - onEpgFetchDone() { - this.store.dispatch(setEpgAvailableFlag({ value: true })); - this.snackBar.open( - this.translate.instant('EPG.DOWNLOAD_SUCCESS'), - null, { - ...this.epgSnackBarOptions, duration: 2000, + horizontalPosition: 'start', } ); } - - onEpgError() { - this.snackBar.open(this.translate.instant('EPG.ERROR'), null, { - ...this.epgSnackBarOptions, - duration: 2000, - }); - } } diff --git a/src/app/services/tauri.service.ts b/src/app/services/tauri.service.ts index 8727e1734..e83f28725 100644 --- a/src/app/services/tauri.service.ts +++ b/src/app/services/tauri.service.ts @@ -3,6 +3,7 @@ import { invoke } from '@tauri-apps/api/core'; import { fetch } from '@tauri-apps/plugin-http'; import { parse } from 'iptv-playlist-parser'; import { + EPG_GET_PROGRAM_DONE, ERROR, PLAYLIST_PARSE_BY_URL, PLAYLIST_PARSE_RESPONSE, @@ -66,6 +67,11 @@ export class TauriService extends DataService { }); throw error; }); + } else if (type === 'EPG_FETCH_DONE') { + window.postMessage({ + type: EPG_GET_PROGRAM_DONE, + payload, + }); } else { console.log('Unknown type', type); } @@ -184,7 +190,10 @@ export class TauriService extends DataService { } removeAllListeners(type: string): void { - throw new Error('Method not implemented.'); + console.error( + 'Method not implemented. Following type was provided:', + type + ); } listenOn(command: string, callback: (...args: any[]) => void): void { diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index 79c3a0ab5..842b07155 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -9,7 +9,7 @@
- +
{{ 'SETTINGS.EPG_URL_LABEL' | translate }} @@ -205,7 +205,7 @@ >
- @if (isPwa || isElectron) { + @if (isPwa || isTauri) {
@@ -311,12 +311,14 @@
-
- {{ 'SETTINGS.EPG_NOTE' | translate }} -  {{ - 'SETTINGS.EPG_NOTE_URL_TEXT' | translate - }} -
+ @if (isPwa) { +
+ {{ 'SETTINGS.EPG_NOTE' | translate }} +  {{ + 'SETTINGS.EPG_NOTE_URL_TEXT' | translate + }} +
+ }
diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index 8e31459dd..fe8bb3fe9 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -22,7 +22,6 @@ import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { take } from 'rxjs'; import * as semver from 'semver'; import { - EPG_FORCE_FETCH, SET_MPV_PLAYER_PATH, SET_VLC_PLAYER_PATH, SETTINGS_UPDATE, @@ -65,7 +64,7 @@ export class SettingsComponent implements OnInit { languageEnum = Language; /** Flag that indicates whether the app runs in electron environment */ - isElectron = this.electronService.isElectron; + isTauri = this.electronService.getAppEnvironment() === 'tauri'; isPwa = this.electronService.getAppEnvironment() === 'pwa'; @@ -91,7 +90,7 @@ export class SettingsComponent implements OnInit { label: 'VideoJs Player', }, ...this.electronPlayers, - /* ...(this.isElectron ? this.electronPlayers : []), */ + /* ...(this.isTauri ? this.electronPlayers : []), */ ]; /** Current version of the app */ @@ -109,7 +108,7 @@ export class SettingsComponent implements OnInit { /** Settings form object */ settingsForm = this.formBuilder.group({ player: [VideoPlayer.VideoJs], - ...(this.isElectron ? { epgUrl: new FormArray([]) } : {}), + ...(this.isTauri ? { epgUrl: new FormArray([]) } : {}), language: Language.ENGLISH, showCaptions: false, theme: Theme.LightTheme, @@ -161,7 +160,7 @@ export class SettingsComponent implements OnInit { player: settings.player ? settings.player : VideoPlayer.VideoJs, - ...(this.isElectron ? { epgUrl: [] } : {}), + ...(this.isTauri ? { epgUrl: [] } : {}), language: settings.language ?? Language.ENGLISH, showCaptions: settings.showCaptions ?? false, theme: settings.theme ?? Theme.LightTheme, @@ -175,7 +174,7 @@ export class SettingsComponent implements OnInit { throw new Error(error); } - if (this.isElectron) { + if (this.isTauri) { this.setEpgUrls(settings.epgUrl); } } @@ -280,15 +279,17 @@ export class SettingsComponent implements OnInit { */ applyChangedSettings(): void { this.settingsForm.markAsPristine(); - // check whether the epg url was changed or not - if (this.isElectron) { + if (this.isTauri) { let epgUrls = this.settingsForm.value.epgUrl; if (epgUrls) { if (!Array.isArray(epgUrls)) { epgUrls = [epgUrls]; } epgUrls = epgUrls.filter((url) => url !== ''); - this.epgService.fetchEpg(epgUrls); + if (epgUrls.length > 0) { + // Fetch all EPG URLs at once + this.epgService.fetchEpg(epgUrls); + } } } this.translate.use(this.settingsForm.value.language); @@ -315,8 +316,7 @@ export class SettingsComponent implements OnInit { * @param url epg source url */ refreshEpg(url: string): void { - this.electronService.sendIpcEvent(EPG_FORCE_FETCH, url); - this.epgService.showFetchSnackbar(); + this.epgService.fetchEpg([url]); } /** From 75b08985fd1155faa0dc0e4984fff8ba3f6553d9 Mon Sep 17 00:00:00 2001 From: 4gray Date: Wed, 27 Nov 2024 20:11:23 +0100 Subject: [PATCH 08/14] style: adjust video player and layout heights Set video player heights to 50vh for better responsiveness. Update EPG content height to 50vh minus padding for improved layout. Modify grid template rows to 50% for better space distribution. Ensure fallback titles and descriptions in dialog for better user experience. --- src/app/portals/epg-view/epg-view.component.ts | 9 ++++----- .../live-stream-layout/live-stream-layout.component.scss | 6 +++--- .../web-player-view/web-player-view.component.scss | 6 +++++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/app/portals/epg-view/epg-view.component.ts b/src/app/portals/epg-view/epg-view.component.ts index 1ea036d4b..0f92ae0ca 100644 --- a/src/app/portals/epg-view/epg-view.component.ts +++ b/src/app/portals/epg-view/epg-view.component.ts @@ -15,7 +15,6 @@ import { EpgItem } from '../../xtream/epg-item.interface'; templateUrl: './epg-view.component.html', imports: [ DatePipe, - EpgItemDescriptionComponent, MatButtonModule, MatDividerModule, MatIconModule, @@ -24,8 +23,8 @@ import { EpgItem } from '../../xtream/epg-item.interface'; ], styles: ` mat-list { - overflow-y: scroll; - height: calc(100vh - 459px); + /* overflow-y: scroll; */ + height: 100vh; } `, }) @@ -37,8 +36,8 @@ export class EpgViewComponent { showDetails(item: EpgItem) { this.dialog.open(EpgItemDescriptionComponent, { data: { - title: [{ value: item.title, lang: item.lang }], - desc: [{ value: item.description }], + title: item.title ?? 'No title', + desc: item.description ?? 'No description', }, }); } diff --git a/src/app/portals/live-stream-layout/live-stream-layout.component.scss b/src/app/portals/live-stream-layout/live-stream-layout.component.scss index 5598e6bbd..8fe24537d 100644 --- a/src/app/portals/live-stream-layout/live-stream-layout.component.scss +++ b/src/app/portals/live-stream-layout/live-stream-layout.component.scss @@ -7,12 +7,11 @@ .itv-container { display: grid; grid-template-columns: 330px 1fr 1fr; - grid-template-rows: 1fr 1fr 1fr; + grid-template-rows: 50% 50%; gap: 0 0; grid-auto-flow: row; grid-template-areas: 'sidebar video-player video-player' - 'sidebar epg epg' 'sidebar epg epg'; height: 100%; } @@ -49,9 +48,10 @@ } .epg-content { - height: 100%; + height: calc(50vh - 20px); overflow-y: auto; border-right: #111 1px solid; + padding-bottom: 20px; } .sidebar-content { diff --git a/src/app/portals/web-player-view/web-player-view.component.scss b/src/app/portals/web-player-view/web-player-view.component.scss index 3e829143d..4f278607c 100644 --- a/src/app/portals/web-player-view/web-player-view.component.scss +++ b/src/app/portals/web-player-view/web-player-view.component.scss @@ -1,3 +1,7 @@ app-vjs-player > .video-js { - height: 100%; + height: 50vh !important; +} + +app-html-video-player > #video-player { + height: 50vh !important; } From b82fa5cc4a0634e54675294ae65ca379d49a9ebf Mon Sep 17 00:00:00 2001 From: 4gray Date: Wed, 27 Nov 2024 21:25:30 +0100 Subject: [PATCH 09/14] refactor: fix drag&drop in favorites list, simplify template Refactor the channel list component to improve readability and maintainability. Remove unnecessary and streamline the template structure by using Angular's structural directives. This enhances the clarity of the code and reduces complexity in the HTML markup. --- .../channel-list-container.component.html | 181 +++++++++--------- .../channel-list-container.component.ts | 17 +- 2 files changed, 104 insertions(+), 94 deletions(-) diff --git a/src/app/player/components/channel-list-container/channel-list-container.component.html b/src/app/player/components/channel-list-container/channel-list-container.component.html index 563f79ae7..ac4539969 100644 --- a/src/app/player/components/channel-list-container/channel-list-container.component.html +++ b/src/app/player/components/channel-list-container/channel-list-container.component.html @@ -57,100 +57,105 @@ - - - - {{ - groups.key || - ('CHANNELS.UNGROUPED' | translate) - | titlecase - }} - ({{ groups.value.length }}) - - - - - - - - + > + + + + + } + } - - - star -
- {{ 'CHANNELS.FAVORITES' | translate }} -
-
- - - - - - {{ - 'CHANNELS.NO_FAVORITES' | translate - }} - - {{ - 'CHANNELS.USE_STAR_TO_FAVORITE' | translate - }} + @let showFavorites = (playlistId$ | async) !== 'GLOBAL_FAVORITES'; + @if (showFavorites) { + + + star +
+ {{ 'CHANNELS.FAVORITES' | translate }} +
-
-
+ @let favorites = favorites$ | async; + @if (favorites) { + + @if (favorites.length > 0) { + + } @else { + {{ + 'CHANNELS.NO_FAVORITES' | translate + }} + + {{ + 'CHANNELS.USE_STAR_TO_FAVORITE' | translate + }} + } + + } + + } } @else { No channels there diff --git a/src/app/player/components/channel-list-container/channel-list-container.component.ts b/src/app/player/components/channel-list-container/channel-list-container.component.ts index bd9540891..12d959cf0 100644 --- a/src/app/player/components/channel-list-container/channel-list-container.component.ts +++ b/src/app/player/components/channel-list-container/channel-list-container.component.ts @@ -1,4 +1,8 @@ -import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; +import { + CdkDragDrop, + DragDropModule, + moveItemInArray, +} from '@angular/cdk/drag-drop'; import { ScrollingModule } from '@angular/cdk/scrolling'; import { CommonModule, TitleCasePipe } from '@angular/common'; import { @@ -37,8 +41,11 @@ import { ChannelListItemComponent } from './channel-list-item/channel-list-item. templateUrl: './channel-list-container.component.html', styleUrls: ['./channel-list-container.component.scss'], imports: [ - CommonModule, ChannelListItemComponent, + CommonModule, + DragDropModule, + FilterPipe, + FormsModule, MatDividerModule, MatExpansionModule, MatFormFieldModule, @@ -46,11 +53,9 @@ import { ChannelListItemComponent } from './channel-list-item/channel-list-item. MatInputModule, MatListModule, MatTabsModule, - TranslateModule, - TitleCasePipe, - FilterPipe, - FormsModule, ScrollingModule, + TitleCasePipe, + TranslateModule, ], }) export class ChannelListContainerComponent { From 03493cc6d4762d7bd95bcacfa94384fefd4fc09a Mon Sep 17 00:00:00 2001 From: 4gray Date: Thu, 28 Nov 2024 23:31:06 +0100 Subject: [PATCH 10/14] chore: update @ngrx/signals to version 18.1.1 Adds @ngrx/signals version 18.1.1 to package.json and package-lock.json to incorporate new features and improvements. This update ensures compatibility with Angular 18 and enhances the overall performance of the application. --- package-lock.json | 18 ++++++++++++++++++ package.json | 1 + 2 files changed, 19 insertions(+) diff --git a/package-lock.json b/package-lock.json index 1f05aa961..8479bd036 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@ngrx/effects": "18.0.2", "@ngrx/entity": "18.0.2", "@ngrx/router-store": "18.0.2", + "@ngrx/signals": "18.1.1", "@ngrx/store": "18.0.2", "@ngrx/store-devtools": "18.0.2", "@ngx-pwa/local-storage": "18.0.0", @@ -5655,6 +5656,23 @@ "rxjs": "^6.5.3 || ^7.5.0" } }, + "node_modules/@ngrx/signals": { + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@ngrx/signals/-/signals-18.1.1.tgz", + "integrity": "sha512-7AaFp4VrzYhnDHlF15VZAMvp067zIC5arRRRMV+i+oi95sQVDXezpj74KTIguwRbZ3FU+GKGzFCNrwgYvginSg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": "^18.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + }, + "peerDependenciesMeta": { + "rxjs": { + "optional": true + } + } + }, "node_modules/@ngrx/store": { "version": "18.0.2", "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-18.0.2.tgz", diff --git a/package.json b/package.json index 8231416dc..577e51455 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@ngrx/effects": "18.0.2", "@ngrx/entity": "18.0.2", "@ngrx/router-store": "18.0.2", + "@ngrx/signals": "18.1.1", "@ngrx/store": "18.0.2", "@ngrx/store-devtools": "18.0.2", "@ngx-pwa/local-storage": "18.0.0", From 05f2ca1eb4ddb8e814647a18eba87dda6a2b9f80 Mon Sep 17 00:00:00 2001 From: 4gray Date: Thu, 28 Nov 2024 23:31:25 +0100 Subject: [PATCH 11/14] feat(toolbar): add settings and update toolbar buttons Inject MatDialog into ToolbarComponent and implement openSettings method to display the SettingsComponent. Update the toolbar HTML to include a settings button and conditionally render the favorite button based on the playlist ID. Enhance VideoPlayerComponent to react to settings changes by integrating SettingsStore. --- .../toolbar/toolbar.component.html | 34 ++++++++++++------- .../video-player/toolbar/toolbar.component.ts | 14 +++++++- .../video-player/video-player.component.ts | 13 ++++++- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/app/player/components/video-player/toolbar/toolbar.component.html b/src/app/player/components/video-player/toolbar/toolbar.component.html index 4ba9644bf..a2e1933e1 100644 --- a/src/app/player/components/video-player/toolbar/toolbar.component.html +++ b/src/app/player/components/video-player/toolbar/toolbar.component.html @@ -7,22 +7,30 @@ > menu - + + {{ + (favorites$ | async).includes(activeChannel?.id) + ? 'star' + : 'star_outline' + }} + + } {{ activeChannel?.name }}
+ @let isEpgAvailable = isEpgAvailable$ | async; @if (isEpgAvailable) { + +
@@ -59,11 +64,7 @@ [matTooltip]="'TOP_MENU.TOGGLE_FAVORITE_FLAG' | translate" > - {{ - isFavoriteStream - ? 'star' - : 'star_outline' - }} + {{ isFavoriteStream ? 'star' : 'star_outline' }}
@@ -91,9 +92,7 @@
-
+