Merge branch 'main' into feat/discord-rich-presence
Some checks failed
Continuous integration / test (push) Has been cancelled

This commit is contained in:
Timothy Z. 2026-04-22 15:36:31 +03:00 committed by GitHub
commit 71e1fc4dad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 1790 additions and 1471 deletions

View file

@ -11,7 +11,11 @@ jobs:
- name: disable git eol translation
run: git config --global core.autocrlf false
- name: checkout
uses: actions/checkout@v3
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Check version
run: node stremiover.js check
- name: Stable with rustfmt and clippy
uses: actions-rust-lang/setup-rust-toolchain@v1
with:

19
Cargo.lock generated
View file

@ -895,18 +895,18 @@ checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
[[package]]
name = "libmpv2"
version = "4.0.0"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7befa1412ea58aeed5f36da1ad795f15bc6aefd554455f5134f1d4f7b6522e7f"
checksum = "3e0c3802b4bb1a18adbf5659b078ce24cb8e16c79ff695557f4e10af2a44722a"
dependencies = [
"libmpv2-sys",
]
[[package]]
name = "libmpv2-sys"
version = "4.0.0"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42124ba90561beede41d5e6ef64eef63fc1395cf83217e3dd1157294f7dcdb56"
checksum = "edb32cabe7176b7d270b0dca6ff418b75187ae8d3854423e3d06fbff376841e4"
[[package]]
name = "libz-rs-sys"
@ -1624,7 +1624,7 @@ dependencies = [
[[package]]
name = "stremio-shell-ng"
version = "5.0.15"
version = "5.0.20"
dependencies = [
"anyhow",
"bitflags 2.4.2",
@ -1648,6 +1648,7 @@ dependencies = [
"sha2",
"url",
"urlencoding",
"uuid",
"webview2",
"webview2-sys",
"whoami",
@ -1993,11 +1994,13 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "0.8.2"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
dependencies = [
"getrandom 0.2.12",
"getrandom 0.3.3",
"js-sys",
"wasm-bindgen",
]
[[package]]

View file

@ -1,6 +1,6 @@
[package]
name = "stremio-shell-ng"
version = "5.0.15"
version = "5.0.20"
edition = "2018"
[dependencies]
@ -23,8 +23,8 @@ winapi = { version = "0.3.9", features = [
] }
webview2 = "0.1.4"
webview2-sys = "0.1.1"
libmpv2 = "4.0.0"
libmpv2-sys = "4.0.0"
libmpv2 = "4.1.0"
libmpv2-sys = "4.0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
clap = { version = "4", features = ["derive", "unicode"] }
@ -40,7 +40,7 @@ sha2 = "0.10"
reqwest = { version = "0.12", features = ["stream", "json", "blocking"] }
rand = "0.8"
url = { version = "2", features = ["serde"] }
uuid = { version = "1.19", features = ["v4"]}
[build-dependencies]
winres = "0.1"

View file

@ -1,21 +1,2 @@
param (
[String]$pw = $( Read-Host "Password" )
)
$thread = Start-ThreadJob -InputObject ($pw) -ScriptBlock {
$wshell = New-Object -ComObject wscript.shell;
$pw = "$($input)~"
while ($true) {
while ( -not $wshell.AppActivate("Token Logon")) {
Start-Sleep 1
}
Start-Sleep 1
$wshell.SendKeys($pw, $true)
Start-Sleep 1
}
}
cargo build --release --target aarch64-pc-windows-msvc
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" /DSIGN "/Sstremiosign=`$qsigntool.exe`$q sign /fd SHA256 /t http://timestamp.digicert.com /n `$qSmart Code OOD`$q `$f" "setup\Stremio-arm64.iss"
Stop-Job -Job $thread

View file

@ -1,21 +1,2 @@
param (
[String]$pw = $( Read-Host "Password" )
)
$thread = Start-ThreadJob -InputObject ($pw) -ScriptBlock {
$wshell = New-Object -ComObject wscript.shell;
$pw = "$($input)~"
while ($true) {
while ( -not $wshell.AppActivate("Token Logon")) {
Start-Sleep 1
}
Start-Sleep 1
$wshell.SendKeys($pw, $true)
Start-Sleep 1
}
}
cargo build --release --target x86_64-pc-windows-msvc
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" /DSIGN "/Sstremiosign=`$qC:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x86\signtool.exe`$q sign /fd SHA256 /t http://timestamp.digicert.com /n `$qSmart Code OOD`$q `$f" "setup\Stremio.iss"
Stop-Job -Job $thread
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" /DSIGN "/Sstremiosign=`$qsigntool`$q sign /fd SHA256 /t http://timestamp.digicert.com /n `$qSmart Code OOD`$q `$f" "setup\Stremio.iss"

3006
server.js

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,9 @@ use clap::Parser;
use native_windows_gui::{self as nwg, NativeUi};
mod stremio_app;
use crate::stremio_app::{
constants::{DEV_ENDPOINT, IPC_PATH, STA_ENDPOINT, STREMIO_SERVER_DEV_MODE, WEB_ENDPOINT},
constants::{
DEV_ENDPOINT, IPC_PATH, SERVER_IPC_KEY, STA_ENDPOINT, STREMIO_SERVER_DEV_MODE, WEB_ENDPOINT,
},
MainWindow, PipeClient,
};
@ -38,6 +40,12 @@ struct Opt {
force_update: bool,
#[clap(long, help = "Check for RC updates")]
release_candidate: bool,
#[clap(
long,
default_value = "",
help = "Secret key for communication with the server. By default it is randomly generrated on startup"
)]
server_ipc_key: String,
}
fn main() {
@ -54,6 +62,15 @@ fn main() {
let opt = Opt::parse();
std::env::set_var(
SERVER_IPC_KEY,
if opt.server_ipc_key.is_empty() {
uuid::Uuid::new_v4().to_string()
} else {
opt.server_ipc_key.clone()
},
);
let command = match opt.command {
Some(file) => {
if Path::new(&file).exists() {

View file

@ -13,4 +13,5 @@ pub const UPDATE_ENDPOINT: [&str; 3] = [
];
pub const STREMIO_SERVER_DEV_MODE: &str = "STREMIO_SERVER_DEV_MODE";
pub const SRV_BUFFER_SIZE: usize = 1024;
pub const SERVER_IPC_KEY: &str = "SERVER_IPC_KEY";
pub const SRV_LOG_SIZE: usize = 20;

View file

@ -41,9 +41,17 @@ impl PlayerProprChange {
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
struct PlayerEndedError {
message: String,
critical: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct PlayerEnded {
reason: String,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<PlayerEndedError>,
}
impl PlayerEnded {
fn string_from_end_reason(data: EndFileReason) -> String {
@ -53,9 +61,24 @@ impl PlayerEnded {
_ => "other".to_string(),
}
}
pub fn from_end_reason(data: EndFileReason) -> Self {
pub fn from_end_reason(data: EndFileReason, error: &str) -> Self {
Self {
reason: Self::string_from_end_reason(data),
error: if data == mpv_end_file_reason::Error {
if error.is_empty() {
Some(PlayerEndedError {
message: "Unknown error".to_string(),
critical: true,
})
} else {
Some(PlayerEndedError {
message: error.to_string(),
critical: true,
})
}
} else {
None
},
}
}
}
@ -162,6 +185,7 @@ pub enum FpProp {
SubDelay,
SubScale,
CacheBufferingState,
DemuxerCacheTime,
SubPos,
Speed,
}

View file

@ -59,26 +59,42 @@ fn propr_change_tokens() {
#[test]
fn ended_tokens() {
let error_tokens: [Token; 12] = [
Token::Struct {
name: "PlayerEnded",
len: 2,
},
Token::Str("reason"),
Token::Str("error"),
Token::Str("error"),
Token::Some,
Token::Struct {
name: "PlayerEndedError",
len: 2,
},
Token::Str("message"),
Token::Str("Unknown error"),
Token::Str("critical"),
Token::Bool(true),
Token::StructEnd,
Token::StructEnd,
];
let tokens: [Token; 4] = [
Token::Struct {
name: "PlayerEnded",
len: 1,
},
Token::Str("reason"),
Token::None,
Token::Str("quit"),
Token::StructEnd,
];
let mut typed_tokens = tokens.clone();
typed_tokens[2] = Token::Str("error");
assert_tokens(
&PlayerEnded::from_end_reason(mpv_end_file_reason::Error),
&typed_tokens,
&PlayerEnded::from_end_reason(mpv_end_file_reason::Error, ""),
&error_tokens,
);
let mut typed_tokens = tokens.clone();
typed_tokens[2] = Token::Str("quit");
assert_tokens(
&PlayerEnded::from_end_reason(mpv_end_file_reason::Quit),
&typed_tokens,
&PlayerEnded::from_end_reason(mpv_end_file_reason::Quit, ""),
&tokens,
);
}

View file

@ -103,11 +103,20 @@ fn create_event_thread(
}
// -1.0 means to block and wait for an event.
let event = match event_context.wait_event(-1.) {
Some(Ok(event)) => event,
let (event, error) = match event_context.wait_event(-1.) {
Some(Ok(event)) => (event, ""),
Some(Err(error)) => {
eprintln!("Event errored: {error:?}");
continue;
if let libmpv2::Error::Raw(e) = error {
(
Event::EndFile(
libmpv2_sys::mpv_end_file_reason_MPV_END_FILE_REASON_ERROR,
),
libmpv2_sys::mpv_error_str(e),
)
} else {
eprintln!("Unhandled event error: {error:?}");
continue;
}
}
// dummy event received (may be created on a wake up call or on timeout)
None => continue,
@ -124,7 +133,7 @@ fn create_event_thread(
),
Event::EndFile(reason) => PlayerResponse(
"mpv-event-ended",
PlayerEvent::End(PlayerEnded::from_end_reason(reason)),
PlayerEvent::End(PlayerEnded::from_end_reason(reason, error)),
),
Event::Shutdown => {
break;

View file

@ -1,3 +1,4 @@
use crate::stremio_app::constants::SERVER_IPC_KEY;
use crate::stremio_app::ipc;
use native_windows_gui::{self as nwg, PartialUi};
use once_cell::unsync::OnceCell;
@ -153,6 +154,12 @@ impl PartialUi for WebView {
}).expect("Cannot add full screen element changed");
webview.add_content_loading(move |wv, _| {
wv.execute_script(format!(
"window.stremio_server_ipc_key='{}'",
std::env::var(SERVER_IPC_KEY).unwrap_or_default()
).as_str(), |_| Ok(())
).expect("Cannot add SERVER_IPC_KEY to webview");
wv.execute_script(r##"
try{
/* Disable context menus */

69
stremiover.js Normal file
View file

@ -0,0 +1,69 @@
#!/usr/bin/env node
const { readFileSync, writeFileSync } = require('fs');
function getGitVersion() {
const { execSync } = require('child_process');
let ver = execSync('git describe --tags --abbrev=0').toString().match(/^v(?<version>\d+\.\d+.\d+)/);
if (ver === null || typeof ver.groups !== "object" || typeof ver.groups.version !== "string") return null;
return ver.groups.version;
}
function getCargoVersion(str) {
const psection = '[package]\n'
const poffset = str.indexOf(psection)
if (poffset === -1) {
console.error(`No ${psection}`)
return null;
}
const voffset = str.indexOf('\nversion', poffset + psection.length);
if (voffset === -1) {
console.error(`No version`)
return null;
} const vstart = str.indexOf('"', voffset) + 1;
if (vstart === 0) return null;
const vend = str.indexOf('"', vstart);
return { vstart, vend, version: str.slice(vstart, vend) }
}
switch (process.argv[2]) {
case 'check': {
const cver = getCargoVersion(readFileSync('Cargo.toml').toString());
const gver = getGitVersion();
if (gver !== cver.version) {
console.error(`Fatal error: the version in Cargo.toml (v${cver.version}) doesn't match latest tag (v${gver})!`);
process.exit(1);
}
console.log('Cargo versino matches the git tag ' + gver);
break;
}
case 'update': {
let newVer = process.argv[3];
if (!newVer) {
const ghv = getGitVersion().split('.')
const patch = (parseInt(ghv.pop(), 10) + 1).toString(10);
ghv.push(patch);
newVer = ghv.join('.');
console.log(`WRNING: No new version provided. Using GH version + 1`)
}
const toml = readFileSync('Cargo.toml').toString();
const cver = getCargoVersion(toml);
if (cver.version === newVer) {
console.log('Warinig: the new version is the same as the version in Cargo.toml')
}
const newtoml = toml.slice(0, cver.vstart) + newVer + toml.slice(cver.vend)
writeFileSync('Cargo.toml', newtoml);
console.log(`Cargo.toml updated to v${newVer}`);
console.log('Changes can be upstreamed by the following commands:\n');
console.log('git add Cargo.toml');
console.log(`git commit -m "Version updated to v${newVer}"`);
console.log(`git tag -a v${newVer} -m "Release of v${newVer}"`);
console.log('git push && git push --tags');
break;
}
default: {
console.log(`usage: ${process.argv[0]} check|update ver`);
break;
}
}

View file

@ -1,3 +1,8 @@
$tag = $(git describe --abbrev=0)
aws s3 cp --acl public-read ".\StremioSetup-v$((get-item .\StremioSetup*.exe).VersionInfo.ProductVersion.Trim()).exe" s3://stremio-artifacts/stremio-shell-ng/$tag/
foreach ($installer in (get-item .\StremioSetup*.exe)) {
if ($tag.StartsWith("v$($installer.VersionInfo.ProductVersion.Trim())")) {
aws s3 cp --acl public-read "$installer" s3://stremio-artifacts/stremio-shell-ng/$tag/
}
}
node ./generate_descriptor.js --tag=$tag