Commit Graph
22 Commits
Author SHA1 Message Date
Divarion_D 0f773b9758 refactor(streaming): extract map/scodec/source/HLS/FLV helpers with tests
Continue breaking up createChannelItem, startMovie and startStream by pulling
five self-contained blocks into pure, tested helpers:

- resolveOutputMap()          — VOD -map selection (custom > strip-subs > copy-all)
- subtitleCodecForContainer() — mp4->mov_text, mkv->srt, else copy
- resolveChannelSource()      — channel source string -> [serverId, path], rewriting
                                s:<server>:<path> to the remote getFile API URL
- buildHlsMpegtsOutput()      — HLS/mpegts segmenter output args
- buildFlvOutput()            — shared FLV output wrapper (RTMP relay + external
                                push, previously two identical lines)

The startStream extractions pass the conditional $rOptions/$rFLVOptions in as
parameters, so the control flow is unchanged. Behaviour preserved.

StreamProcessMovieOutputTest covers map precedence, container->codec mapping,
every source-resolution branch, HLS output assembly, and FLV wrapping.

Verified: php -l clean, make gates green, phpunit 360/360 green.
2026-08-07 19:31:18 +03:00
Divarion_D 0bb87452ad fix(streaming): import and map all VOD subtitles, not just the first
buildSubtitleImport nested the metadata loop inside the import loop and reused
the outer counter $i. The inner loop advanced $i to count(files), so the outer
import loop exited after its first iteration: only the first subtitle was
imported (-sub_charenc -i), while -map/-metadata was still emitted for every
file — mapping ffmpeg inputs that were never added. Multi-subtitle movies got a
single imported track plus dangling -map references (broken output).

Split the nested loop into two siblings — import every file, then map every
file — and cache the count. All configured subtitles are now imported and
mapped to the correct input index.

Add tests/Unit/StreamProcessSubtitleTest.php covering empty input, a single
local subtitle, remote-server fetch, and the multi-subtitle case that
reproduces the defect (import count was 1, must equal N). Verified red→green
against the pre-fix code.

Verified: php -l clean, make gates green, phpunit 336/336 green.
2026-08-07 19:04:16 +03:00
Divarion_D 4980b00829 refactor(streaming): extract buildSubtitleImport from startMovie
Move the VOD subtitle import + metadata assembly out of startMovie into a
private helper so the method is smaller and the subtitle logic is isolated.

Behaviour is preserved verbatim, INCLUDING a pre-existing bug: the inner
metadata loop reuses the outer import loop's $i counter, so the import loop
effectively runs once (first file) while metadata is mapped for all files.
Left as-is here; it is a real defect to fix in a dedicated change.

Verified: php -l clean, make gates green.
2026-08-07 18:56:00 +03:00
Divarion_D 7adf7a5b27 refactor(streaming): extract shared ffmpeg-command helpers in StreamProcess
createChannelItem, startMovie and startStream each inlined the same blocks
verbatim. Extract them into private helpers — removing the duplication and
shrinking the three oversized methods:

- buildLogoFilterOptions() — logo / -filter_complex overlay block (was inlined 3x)
- resolveGpuInputCodec()   — CUVID hardware-decoder detection (was inlined 2x)
- applyDefaultCopyCodecs() — default -acodec/-vcodec to stream copy (was inlined 3x)

Pure extraction, behaviour preserved. startStream's GPU block is intentionally
left inline: it reuses the already-computed $rFFProbeOutput instead of
re-probing the source, so it is not the same code.

Verified: php -l clean, make gates green (PSR-4 use-imports, LB manifest,
vendor prod-only). net -28 lines.
2026-08-07 18:51:07 +03:00
Divarion_D b98898b25e fix(streaming): reconnect LLOD http input to survive source drops 2026-08-07 16:54:08 +03:00
Divarion_D 22e422bb1c fix(streams): report real transcode speed instead of a stuck "1x"
The Stream Info column always showed a transcode speed of "1x" even though
ffmpeg's real speed fluctuates. Two independent bugs were involved.

1. Progress was never captured while a stream ran.
   ffmpeg's `-progress` posted to the HTTP /progress endpoint, but ffmpeg
   holds one open-ended chunked POST for the stream's whole life. PHP-FPM's
   php://input can't read a length-less body, and nginx only invokes PHP
   once the request body is complete, so the endpoint fired only at stream
   end. `progress_info` therefore stayed empty and the view fell back to the
   hardcoded "1x".

   Switch ffmpeg to write `-progress` to a local file (the mechanism the
   created-channel path already uses). StreamsCronJob now tails that file,
   keeps the last complete report block, re-encodes it as the JSON the panel
   expects, and truncates it each cycle so it stays tiny on disk.

2. Speeds just above 1 were swallowed by the display.
   `floor($speed * 100) / 100` dropped e.g. 1.01 to "1x" because 1.01 * 100
   is 100.9999… in floating point. Use round() instead, and apply the same
   robust parsing to the radios path (which multiplied the "1.01x" string).

Files:
- Domain/Stream/StreamProcess.php   ffmpeg -progress -> local file
- Cli/CronJobs/StreamsCronJob.php    tail/parse/truncate the progress file
- Public/Controllers/Admin/TableController.php  round() speed display
2026-08-01 23:35:05 +03:00
Divarion-D 3e4fbae04c fix(stream): null-safe activity, stream and server lookups
Null-coalesce absent keys in activity info, cached signal counts, streams_servers
maps, server protocol and PID-running results, and @-suppress the stale .pid
unlink. Avoids offset-on-null/undefined-key warnings under PHP 8 without changing
behaviour on present data.
2026-07-30 19:47:05 +03:00
Divarion-D 603ee3c8b6 fix(streams): repair created-channel build chain and add live encode progress
- QueueCommand: adopt a live build PID instead of unlinking its .create
  marker and relaunching (which killed the running encoder and reset the
  build to 0% on every queue re-add); widen the PID capture window from
  0.3s to 5s to survive console.php bootstrap
- VodCronJob: check build liveness locally via /proc cmdline instead of an
  HTTP self-call that falsely reported the build dead whenever the internal
  API or server_online flag hiccuped
- CreatedCommand: verbose CLI output (per-source status, encode % / speed
  every 10s), local process checks instead of HTTP, cleanup of stale
  _.create on the nothing-to-build early exit
- StreamProcess: pass -progress <id>_<md5>.progress to the channel-item
  ffmpeg command; CreatedCommand parses it and mirrors the percentage into
  streams_servers.progress_info
- TableController/api.php: "% DONE" now includes fractional progress of the
  source currently being encoded
- MonitorCommand: stop type-3 (created) channels from falling through into
  the LLOD branch after startStream — LLOD cannot consume raw
  "s:<server>:<path>" sources and spammed "[LLOD] Invalid source" in a
  restart loop; the on-demand LLOD shortcut now also skips type 3
- PHP 8 warning fixes along the same chain: missing on_demand key
  (ChannelService), undefined $rSplit and missing parent_id column in
  grouped queries (TableController), missing .create file (VodCronJob),
  empty lines in _.list parsing and null cc_info (StreamProcess)
2026-07-10 19:19:25 +03:00
Divarion-D 732a0cd1dc fix(streaming): guard probe URL port, transcode json_decode and playlist md5 races
- StreamProcess: parse_url() output has no 'port'/'path' keys for default-port
  URLs — probe URL built with isset() guards (477 warnings/8h in panel logs)
- StreamProcess: json_decode of transcode_attributes/profile_options can
  return null — normalize to array before array_key_exists/array_merge
  (TypeError at stream start)
- MonitorCommand: the .m3u8 playlist can vanish between the loop's
  file_exists check and md5_file (stream restart race) — guard both call sites
2026-07-08 12:17:54 +03:00
Divarion-D 57e4805769 refactor: remove unused Database imports across multiple services 2026-06-28 20:45:17 +03:00
Divarion-D 241fb685dc fix(StreamProcess): adjust FFMPEG command options for improved performance 2026-06-28 14:26:14 +03:00
Divarion-D a9cf7a5266 refactor(database): centralize domain DB access in DatabaseAware trait
Replace the fragile per-class setDb()/db() pattern (which threw when a
  bootstrap path forgot to wire $db) with a shared trait that lazily resolves
  from DatabaseFactory. Fixes the streaming UserRepository fatal and the same
  latent issue in module crons. 42 classes converted.
2026-06-28 13:48:35 +03:00
Divarion-D 7fa26585e0 fix(stream): add initial segment time for faster playback start 2026-06-26 19:52:54 +03:00
Divarion-D c2343885dc chore(phpstan): exclude vendored libs + fix safe return.void/deadCode errors
- Exclude bundled third-party code from analysis (Modules/tmdb/lib/*,
    Core/Util/MobileDetect.php) — like the vendored Composer packages, these
    are not our code (php-tmdb client, Mobile-Detect). Removes 67 baselined
    errors incl. the only class.notFound and phpDoc.parseError (both vendored).
  - return.void (10): drop the discarded return value (return true/false/null →
    return;) in void methods — NetworkUtils, LineService, StreamProcess, TMDbService.
  - deadCode.unreachable (2): remove the dead `return;` after AdminHelpers::goHome()
    (a never-returning call) in Plex/WatchController.
  - Regenerate phpstan-baseline.neon: 440 -> 361 errors (291 -> 241 blocks).

  Verified: make phpstan green, PHPUnit 298/298.
2026-06-26 19:26:18 +03:00
Divarion-D 9eec63937e style: import/namespace hygiene across src (PHP-CS-Fixer)
Apply 'make cs-fix' — 493 files. Mechanical, import-block only:
- sort use statements alphabetically (class/function/const grouped);
- drop imports left unused by the PSR-4 migration (e.g. classes referenced by
  leading-backslash FQCN whose redundant 'use' the automated insertion had added);
- one blank line after namespace and after the import block; collapse stray
  blank lines around use.

No logic changes. Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; and a
temporary PHPStan pass over src/Public/Controllers confirms no still-referenced
import was removed (0 unresolved classes). 'use' after inline HTML in view
templates is valid and aliases correctly (verified) — those imports are sorted too.
2026-06-25 20:24:36 +03:00
Divarion-D 62d2d1d942 chore(psr4): phase 3 — namespace Streaming layer
Namespace all 17 Streaming classes into XcVm\Streaming\<Subdir> (Auth, Balancer,
Codec, Delivery, Health, Lifecycle, Protection + root AsyncFileOperations,
StreamingBootstrap, TS).

- Add namespace to every class; add use to referrers across src/ and tests/;
  convert leading-backslash refs from earlier commits (\FfmpegPaths,
  \FFprobeRunner, \ProcessChecker, \StreamingBootstrap) to FQCNs.
- Qualify built-ins/ioncube inside Streaming (\DateTime, \DateTimeZone,
  \Exception, \XC_VM).
- FfmpegPathsTest: new ReflectionClass('FfmpegPaths') string → ::class FQCN.
- phpstan-baseline.neon regenerated (291).

Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; no mangled FQCNs;
leading-backslash re-sweep clean.
2026-06-25 18:55:35 +03:00
Divarion-D 42d9ca8ea0 chore(psr4): phase 3 — namespace Domain layer
Namespace all of Domain into XcVm\Domain\<Subdir> (33 classes across Bouquet,
Device, Epg, Line, Security, Server, Stream, User, Vod).

- Add namespace to every Domain class; add use to referrers across src/ and
  tests/; convert the leading-backslash refs qualified in earlier Core commits
  (\UserRepository, \ServerRepository, \BouquetService, \CategoryService,
  \ConnectionTracker, \BlocklistService, ...) to their FQCNs.
- Qualify still-global / built-in deps inside Domain with leading backslash
  (\RedisManager, \TMDB, \WatchService, \FFprobeRunner, \ProcessChecker,
  \Exception, \DateTime, \PDO, ...) — Infrastructure/Streaming/module classes
  migrate later.
- BruteforceGuard: string guards class_exists('ServerRepository'/'BlocklistService')
  → ::class FQCN; drop the now-always-true method_exists check.
- phpstan-baseline.neon regenerated (292→291).

Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; leading-backslash
re-sweep across Domain classes is clean.
2026-06-25 18:39:28 +03:00
Divarion-D da291e20f8 chore(psr4): phase 3 (Core) — namespace remaining Core subdirs
Namespace the rest of Core, completing the Core layer:
- Backup, Boundary, Cache (CacheInterface/FileCache/RedisCache), Diagnostics,
  GeoIP (GeoIPService/MaxMindUpdater), Init (LegacyInitializer), Localization
  (Translator), Process (Thread/Multithread/ProcessManager), Updates
  (GitHubReleases), Util (NetworkUtils, AdminHelpers, Encryption, GeoIP,
  ImageUtils/ImageResizeService, Mobile_Detect, StreamUtils, SystemInfo,
  TimeUtils), Validation (InputValidator) → XcVm\Core\<Subdir>.
- Rename GithubReleases.php → GitHubReleases.php so the file matches its class
  name (PSR-4 is case-sensitive); fix the WebApiBootstrap require accordingly.
- Qualify built-ins (\Exception, \DateTime, \DateTimeZone, \Redis,
  \RuntimeException, \BadMethodCallException, ...) and still-global deps
  (\ServerRepository, \CacheReader, \DatabaseFactory, \BouquetService,
  \CategoryService, \FfmpegPaths, \StreamSorter, \XC_VM). LegacyInitializer's
  Domain calls stay \-qualified (the known Core→Domain exception).
- Add use to referrers; fix leading-backslash refs from earlier commits; fix
  string class refs class_exists('Translator'/'NetworkUtils'/...) → ::class.
  Remove the duplicate global 'use BoundaryInterface;' in MinistraModule.
- phpstan-baseline.neon regenerated.

Core is now fully namespaced (procedural constant/error/RequestGuard files stay
global by design; M3uParser/PhpM3u8 remain vendored). Verified: php -l clean;
PHPStan no errors; PHPUnit 295/295; leading-backslash re-sweep clean.
2026-06-24 22:35:33 +03:00
Divarion-D 14202afd64 chore(psr4): phase 3 (Core hubs) — namespace Core/Http
Move Core/Http into XcVm\Core\Http (ApiClient, CurlClient, RequestManager,
Request, Response, Router) and Core/Http/Pipeline into XcVm\Core\Http\Pipeline
(StreamContext, StreamMiddlewareInterface, StreamPipeline). RequestGuard.php is
procedural (global functions) and stays global.

- Namespace the 9 classes; qualify still-global deps with leading backslash
  (\ServerRepository in ApiClient; \Authorization, \ServiceContainer,
  \AdminHelpers in Router) and built-in \Throwable; same-namespace siblings
  unqualified.
- Add 'use XcVm\Core\Http\...;' to referencing files — RequestManager 128,
  Router 19, ApiClient 15, Request 11, Response 6, CurlClient 5, plus Pipeline —
  across src/ and tests/.
- Rewrite the modules' 'use Router;' → 'use XcVm\Core\Http\Router;' (plex,
  watch, tmdb).
- Fix pre-existing leading-backslash global refs (\Router etc.) to the FQCN.
- Tests: fully-qualify the Router type hint in generated module fixtures
  (registerRoutes(\XcVm\Core\Http\Router ...)) and update the
  InterfaceContractTest param-type expectation to the FQCN. Also repaired two
  fixtures where the use-inserter mis-placed a 'use' (files declare a method
  literally named namespace(), which the tokenizer reports as T_NAMESPACE).
- phpstan-baseline.neon regenerated (292→292; class names in frozen messages
  gained the namespace, no new/unknown-class errors).

Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; Http classes resolve
via Composer; leading-backslash re-sweep across migrated classes is clean.
2026-06-24 21:32:10 +03:00
Divarion-D 42db4be509 chore(psr4): phase 3 (Core hubs) — namespace Core/Database
Move Core/Database into XcVm\Core\Database (DatabaseHandler, Database,
MigrationRunner, QueryHelper). First of the Core hub sub-layers.

- Namespace the 4 classes; DatabaseHandler extends Database (same namespace);
  built-ins/ioncube qualified (\PDO, \PDOException, \Exception, \Throwable,
  \XC_VM); Database keeps its 'use XcVm\Core\Logging\FileLogger;'.
- Add 'use XcVm\Core\Database\...;' to referencing files (DatabaseHandler 51,
  Database 64, QueryHelper 29, MigrationRunner 3) + 2 test files (PHPStan does not
  analyse tests/, so PHPUnit is the gate there).
- Rewrite pre-existing leading-backslash global refs (\DatabaseHandler etc., e.g.
  in @param docblocks of ResellerApiDispatcher/ResellerTableRenderer) to the full
  FQCN \XcVm\Core\Database\... — a 'use' import does not cover a leading-\
  reference. Done with a lookbehind so FQCN continuations and use-lines are intact.
- phpstan-baseline.neon regenerated (292→292; pre-existing Database/migration_logic
  findings re-anchored after class names in messages gained the namespace).

Verified: php -l clean; PHPStan no errors; PHPUnit 295/295.
2026-06-24 21:15:06 +03:00
Divarion-D 8ac1908554 chore(psr4): phase 3 (Core leaf) — namespace SettingsManager + SettingsRepository
Move the last two Core/Config classes into XcVm\Core\Config. SettingsManager
has the largest fan-out of the whole migration (referenced by ~224 files).

- Namespace SettingsManager (self-contained singleton, no class deps) and
  SettingsRepository (\FileCache:: qualified).
- Add 'use XcVm\Core\Config\SettingsManager;' to 224 referencing files and
  'use ...\SettingsRepository;' to 12 — call sites (SettingsManager::get(), etc.)
  unchanged. Done with a token-based inserter (after namespace/declare/<?php,
  idempotent, same-namespace files skipped).
- ToolsCommand::processRecaptcha: drop dead class_exists('SettingsManager') +
  method_exists guard (always autoloadable now) → call SettingsManager::clearCache()
  directly. This was the only string-literal class reference.

Core/Config is now fully namespaced (ConfigReader, DomainResolver, SettingsManager,
SettingsRepository); the procedural constant files (AppConfig/Binaries/Paths)
remain global by design.

Verified: php -l clean (226 files); PHPStan no errors (baseline unchanged — it is
line-independent so the added use-lines don't disturb it); PHPUnit 295/295; all
Config FQCNs resolve via Composer; sample Public referrers lint-clean.
2026-06-24 20:52:00 +03:00
Divarion-D 1a296d0985 chore(psr4): phase 1 — rename 7 top-level dirs to PascalCase
Atomic case-rename of the seven class-holding source directories to match the
PSR-4 namespace casing, plus every consequent path reference. No code logic
changes — pure structural rename. (config/content/resources/signals/migrations/
ministra/storage/tmp/vendor/www/bin stay lowercase.)

- git mv: core→Core, domain→Domain, infrastructure→Infrastructure,
  streaming→Streaming, modules→Modules, cli→Cli, public→Public (module subdirs
  like Modules/plex stay lowercase; loaded by ModuleLoader, not Composer).
- PHP filesystem paths updated across src/ + tests/: require/include, glob/scandir
  bases, view includes, asset/nginx-alias paths, autoload.php registerDirectories(),
  ModuleLoader/ModuleManager module roots, console.php discovery dirs.
- src/composer.json PSR-4 vendored-lib paths → Core/Parsing/...; vendor/ regenerated.
- nginx.conf: docroot + SCRIPT_FILENAME → Public/. SCRIPT_NAME and the public-facing
  /streaming/*.php compat routes are URLs, left unchanged.
- Build/CI: Makefile LB_DIRS / LB_DIRS_TO_REMOVE / LB_FILES_TO_REMOVE PascalCased
  (closes the LB-archive privileged-leak blocker); phpstan.dist.neon paths/
  scanDirectories/excludePaths; phpunit.xml.dist coverage; phpstan-baseline.neon
  regenerated (294→294 errors, no new resolution failures).
- migrations/deleted_files.txt: old lowercase trees listed for client cleanup
  (assumes case-sensitive FS — the supported Linux target).

Verified: grep-gate 0 live lowercase-dir require/include in src+tests; php -l clean;
PHPUnit 292/292; PHPStan no errors; Composer first / XC_Autoloader last in SPL stack;
global classes resolve from the renamed dirs.
2026-06-24 20:10:34 +03:00