Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
php-version: 8.4
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite

- name: Install dependencies
Expand All @@ -30,7 +30,7 @@ jobs:
run: vendor/bin/phpunit

- name: Run PHPStan
run: vendor/bin/phpstan analyse
run: vendor/bin/phpstan analyse --memory-limit=1G

- name: Run Pint
run: vendor/bin/pint --test
Expand Down Expand Up @@ -69,7 +69,7 @@ jobs:
fi

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
with:
name: Release ${{ steps.tag.outputs.version }}
body_path: changelog_extract.md
Expand Down
36 changes: 23 additions & 13 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,21 @@ jobs:
strategy:
fail-fast: false
matrix:
php: ['8.1', '8.2', '8.3']
laravel: ['^10.0', '^11.0']
exclude:
- php: '8.1'
laravel: '^11.0'
# Officially supported PHP versions per Laravel release
include:
- { php: '8.1', laravel: '^10.0' }
- { php: '8.2', laravel: '^10.0' }
- { php: '8.3', laravel: '^10.0' }
- { php: '8.2', laravel: '^11.0' }
- { php: '8.3', laravel: '^11.0' }
- { php: '8.4', laravel: '^11.0' }
- { php: '8.2', laravel: '^12.0' }
- { php: '8.3', laravel: '^12.0' }
- { php: '8.4', laravel: '^12.0' }
- { php: '8.5', laravel: '^12.0' }
- { php: '8.3', laravel: '^13.0' }
- { php: '8.4', laravel: '^13.0' }
- { php: '8.5', laravel: '^13.0' }

steps:
- name: Checkout code
Expand All @@ -43,7 +53,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Cache dependencies
uses: actions/cache@v4
uses: actions/cache@v6
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
Expand All @@ -57,12 +67,12 @@ jobs:
- name: Run PHPUnit tests
run: vendor/bin/phpunit --testdox

- name: Generate coverage report (PHP 8.3, Laravel 11 only)
if: matrix.php == '8.3' && matrix.laravel == '^11.0'
- name: Generate coverage report (PHP 8.4, Laravel 12 only)
if: matrix.php == '8.4' && matrix.laravel == '^12.0'
run: vendor/bin/phpunit --coverage-clover coverage.xml

- name: Upload coverage to Codecov (PHP 8.3, Laravel 11 only)
if: matrix.php == '8.3' && matrix.laravel == '^11.0'
- name: Upload coverage to Codecov (PHP 8.4, Laravel 12 only)
if: matrix.php == '8.4' && matrix.laravel == '^12.0'
uses: codecov/codecov-action@v5
with:
files: ./coverage.xml
Expand All @@ -80,7 +90,7 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
php-version: 8.4
extensions: dom, curl, libxml, mbstring, zip

- name: Install dependencies
Expand All @@ -100,11 +110,11 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
php-version: 8.4
extensions: dom, curl, libxml, mbstring, zip

- name: Install dependencies
run: composer update --prefer-dist --no-interaction --no-progress

- name: Run PHPStan
run: vendor/bin/phpstan analyse --error-format=github
run: vendor/bin/phpstan analyse --memory-limit=1G --error-format=github
32 changes: 30 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.2.0] - 2026-08-02

### Added
- Initial package structure
- Project documentation and planning
- Laravel 12 and Laravel 13 support (`illuminate/*` `^12.0|^13.0`)
- PHP 8.4 and PHP 8.5 support
- `RestreamDto::fromUrl()` to decompose RTMP(S) publishing URLs (e.g. Facebook's
`secure_stream_url`) into `dest_addr`/`dest_port`/`dest_app`/`dest_stream`/`ssl`
- `RestreamService::stats()` for `GET /manage/rtmp/republish/stats`
- `RestreamStatsDto` for republishing connection statistics
- CI test matrix covering Laravel 10–13 on PHP 8.1–8.5 (runs on pull requests to main)

### Changed
- **Breaking**: `RestreamService` now targets Nimble's real RTMP republishing API
(`/manage/rtmp/republish`); the previous `/manage/restream/*` endpoints do not
exist in Nimble's native API
- **Breaking**: `RestreamDto` now carries the real republish rule fields
(`src_app`, `src_stream`, `dest_addr`, `dest_port`, `dest_app`, `dest_stream`,
plus optional `ssl`, `auth_schema`, `dest_login`, `dest_password`,
`keep_src_stream_params`, `dest_app_params`, `dest_stream_params`) instead of
`streamId`/`targetUrl`/`protocol`
- **Breaking**: `RestreamService::add()` was replaced by `create(RestreamDto)`,
which returns the created rule with its assigned id
- **Breaking**: `RestreamService::update()` was removed — Nimble has no update
endpoint; delete the rule and create a new one instead
- Tests migrated from `/** @test */` annotations to `#[Test]` attributes
(doc-comment metadata is removed in PHPUnit 12+)
- GitHub Actions updated to Node 24 runtimes (`actions/cache@v6`,
`softprops/action-gh-release@v3`)

### Fixed
- `nimble:status` no longer crashes on byte values above the TB range

## [0.1.0] - 2025-10-10

Expand Down
79 changes: 46 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ A comprehensive Laravel package for seamless integration with Nimble Streamer AP

## Requirements

- PHP 8.1 or higher
- Laravel 10 or 11
- PHP 8.1 – 8.5
- Laravel 10, 11, 12 or 13
- Nimble Streamer with API enabled
- Composer

Expand Down Expand Up @@ -282,46 +282,59 @@ if (Nimble::dvr()->configure([
}
```

### Restream Management
### Restream Management (RTMP Republishing)

Restreaming uses Nimble's native RTMP republishing API (`/manage/rtmp/republish`).

```php
use AlexHackney\LaraNimble\DTOs\RestreamDto;
use AlexHackney\LaraNimble\Facades\Nimble;

// List all restream targets
$restreams = Nimble::restream()->list();
foreach ($restreams as $restream) {
echo "Target: {$restream->targetUrl}\n";
echo "Protocol: {$restream->protocol}\n";
echo "Status: {$restream->status}\n";
echo "Enabled: " . ($restream->enabled ? 'Yes' : 'No') . "\n";
// List republishing rules created through this API
$rules = Nimble::restream()->list();
foreach ($rules as $rule) {
echo "Rule {$rule->id}: {$rule->srcApp}/{$rule->srcStream}";
echo " -> {$rule->destAddr}:{$rule->destPort}/{$rule->destApp}/{$rule->destStream}\n";
}

// Get a specific restream target
$restream = Nimble::restream()->get('restream-123');
echo "Target URL: {$restream->targetUrl}";

// Add a new restream target
if (Nimble::restream()->add('stream-123', [
'target_url' => 'rtmp://live.youtube.com/stream/key123',
'protocol' => 'rtmp',
'enabled' => true,
])) {
echo "Restream target added successfully!";
// Get a specific rule (null when it does not exist)
$rule = Nimble::restream()->get(42);

// Create a rule from explicit fields
$created = Nimble::restream()->create(new RestreamDto(
srcApp: 'live',
srcStream: 'stream1',
destAddr: 'a.rtmp.youtube.com',
destPort: 1935,
destApp: 'live2',
destStream: 'your-stream-key',
));
echo "Created rule {$created->id}";

// Or decompose an RTMP(S) publishing URL, e.g. Facebook's secure_stream_url
$created = Nimble::restream()->create(RestreamDto::fromUrl(
'live',
'stream1',
'rtmps://live-api-s.facebook.com:443/rtmp/your-stream-key'
));

// Delete a rule
if (Nimble::restream()->delete(42)) {
echo "Rule deleted!";
}

// Update a restream target
if (Nimble::restream()->update('restream-123', [
'enabled' => false,
])) {
echo "Restream target updated!";
}

// Delete a restream target
if (Nimble::restream()->delete('restream-123')) {
echo "Restream target deleted!";
// Connection statistics for all rules
foreach (Nimble::restream()->stats() as $stat) {
echo "Rule {$stat->id}: {$stat->state}, {$stat->bandwidth} bandwidth\n";
}
```

Notes from the Nimble API docs:

- `src_stream` is optional in the raw API, but omitting it republishes **every** stream in the source application, so `RestreamDto` refuses to build a create payload without it.
- `list()` only returns rules created through the native API — rules defined in WMSPanel do not appear.
- Rules created through the native API are not persisted across a Nimble config reload or restart; recreate them as needed.

### Stream Pulling

```php
Expand Down Expand Up @@ -652,8 +665,8 @@ The MIT License (MIT). See [LICENSE](LICENSE) for details.
**Developed by:** Alex Hackney

**Built with:**
- Laravel 10 & 11
- PHP 8.1+
- Laravel 10 – 13
- PHP 8.1 – 8.5
- Nimble Streamer API

## Artisan Commands
Expand Down
16 changes: 8 additions & 8 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,18 @@
"docs": "https://github.com/alexhackney/lara-nimble#readme"
},
"require": {
"php": "^8.1|^8.2|^8.3",
"illuminate/support": "^10.0|^11.0",
"illuminate/http": "^10.0|^11.0",
"php": "^8.1|^8.2|^8.3|^8.4|^8.5",
"illuminate/support": "^10.0|^11.0|^12.0|^13.0",
"illuminate/http": "^10.0|^11.0|^12.0|^13.0",
"guzzlehttp/guzzle": "^7.5"
},
"require-dev": {
"orchestra/testbench": "^8.0|^9.0",
"phpunit/phpunit": "^10.5|^11.0",
"orchestra/testbench": "^8.0|^9.0|^10.0|^11.0",
"phpunit/phpunit": "^10.5|^11.0|^12.0|^13.0",
"mockery/mockery": "^1.6",
"laravel/pint": "^1.13",
"phpstan/phpstan": "^1.10",
"larastan/larastan": "^2.9"
"phpstan/phpstan": "^1.10|^2.0",
"larastan/larastan": "^2.9|^3.0"
},
"autoload": {
"psr-4": {
Expand All @@ -71,7 +71,7 @@
"test": "vendor/bin/phpunit",
"test-coverage": "vendor/bin/phpunit --coverage-html coverage",
"format": "vendor/bin/pint",
"analyse": "vendor/bin/phpstan analyse"
"analyse": "vendor/bin/phpstan analyse --memory-limit=1G"
},
"config": {
"sort-packages": true,
Expand Down
3 changes: 1 addition & 2 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@ parameters:
- src
level: 8
ignoreErrors:
excludePaths:
checkMissingIterableValueType: false
- identifier: missingType.iterableValue
11 changes: 1 addition & 10 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
cacheDirectory=".phpunit.cache"
Expand All @@ -14,19 +14,10 @@
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<coverage>
<report>
<html outputDirectory="coverage"/>
<text outputFile="php://stdout" showUncoveredFiles="true"/>
</report>
</coverage>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
<exclude>
<directory>src/Console</directory>
</exclude>
</source>
<php>
<env name="APP_ENV" value="testing"/>
Expand Down
2 changes: 1 addition & 1 deletion src/Client/NimbleClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ private function validateConfig(array $config): void
throw new InvalidConfigurationException('Protocol must be either http or https');
}

if (isset($config['token']) && ! is_string($config['token']) && $config['token'] !== null) {
if (isset($config['token']) && ! is_string($config['token'])) {
throw new InvalidConfigurationException('Token must be a string or null');
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Commands/NimbleStatusCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ private function formatUptime(int $seconds): string
private function formatBytes(int $bytes): string
{
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$factor = floor((strlen((string) $bytes) - 1) / 3);
$factor = min((int) floor((strlen((string) $bytes) - 1) / 3), count($units) - 1);

return sprintf('%.2f %s', $bytes / (1024 ** $factor), $units[$factor]);
}
Expand Down
Loading
Loading