Browse Source

Added rtmp stats for users, fixes #5

master
Shyim 6 years ago
parent
commit
8d78961074
  1. 6
      config/services.yaml
  2. 2
      public/static/js/app.js
  3. 25
      public/theme/src/components/ReCast/Streams/SetupStream.vue
  4. 9
      src/Command/CronRunnerCommand.php
  5. 8
      src/Command/GenerateConfigCommand.php
  6. 34
      src/Component/Nginx/ConfigGenerator.php
  7. 45
      src/Component/Nginx/Stats.php
  8. 37
      src/Controller/Streams.php

6
config/services.yaml

@ -36,11 +36,11 @@ services:
class: App\Component\ServiceManager class: App\Component\ServiceManager
arguments: [!tagged recast.service] arguments: [!tagged recast.service]
App\Component\NginxConfigGenerator: App\Component\Nginx\ConfigGenerator:
class: App\Component\NginxConfigGenerator class: App\Component\Nginx\ConfigGenerator
autowire: true autowire: true
bind: bind:
$nginxFolder: '%nginxConfigFolder%' $nginxConfigFolder: '%nginxConfigFolder%'
$appHost: '%appHost%' $appHost: '%appHost%'
# add more service definitions when explicit configuration is needed # add more service definitions when explicit configuration is needed

2
public/static/js/app.js

File diff suppressed because one or more lines are too long

25
public/theme/src/components/ReCast/Streams/SetupStream.vue

@ -8,6 +8,19 @@
<fg-input label="URL" v-model="stream.streamUrl" disabled="true"></fg-input> <fg-input label="URL" v-model="stream.streamUrl" disabled="true"></fg-input>
<fg-input label="Stream Key" v-model="stream.streamKey" disabled="true"></fg-input> <fg-input label="Stream Key" v-model="stream.streamKey" disabled="true"></fg-input>
<div v-if="stats.active">
<h3>Stream Statistics</h3>
<p>
<strong>Bytes received</strong> {{ stats.bytes_in / 1048576 | toNumber }} MiB<br>
<strong>Bytes sent</strong> {{ stats.bytes_out / 1048576 | toNumber }} MiB<br>
<strong>Current bandwidth in</strong> {{ stats.bw_in / 1000000 | toNumber }} Mb/s<br>
<strong>Current bandwidth out</strong> {{ stats.bw_out / 1000000 | toNumber }} Mb/s<br>
<strong>Res: </strong> {{ stats.meta.video.width }}x{{ stats.meta.video.height }}<br>
<strong>FPS: </strong> {{ stats.meta.video.frame_rate }}
</p>
</div>
<h4>OBS</h4> <h4>OBS</h4>
<ul> <ul>
@ -26,13 +39,23 @@
export default { export default {
data() { data() {
return { return {
stream: {} stream: {},
stats: {}
} }
}, },
mounted() { mounted() {
this.axios.get('/streams/one?id=' + this.$route.params.id).then(response => { this.axios.get('/streams/one?id=' + this.$route.params.id).then(response => {
this.stream = response.data; this.stream = response.data;
}); });
this.axios.get(`/streams/${this.$route.params.id}/stats`).then(response => {
this.stats = response.data;
})
},
filters: {
toNumber: function (value) {
return value.toFixed(2);
}
} }
} }

9
src/Command/CronRunnerCommand.php

@ -2,7 +2,7 @@
namespace App\Command; namespace App\Command;
use App\Component\NginxConfigGenerator; use App\Component\Nginx\ConfigGenerator;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
@ -23,8 +23,9 @@ class CronRunnerCommand extends Command implements ContainerAwareInterface
* @var Connection * @var Connection
*/ */
private $connection; private $connection;
/** /**
* @var NginxConfigGenerator * @var ConfigGenerator
*/ */
private $configGenerator; private $configGenerator;
@ -32,10 +33,10 @@ class CronRunnerCommand extends Command implements ContainerAwareInterface
* CronRunnerCommand constructor. * CronRunnerCommand constructor.
* @param null|string $name * @param null|string $name
* @param Connection $connection * @param Connection $connection
* @param NginxConfigGenerator $configGenerator * @param ConfigGenerator $configGenerator
* @author Soner Sayakci <shyim@posteo.de> * @author Soner Sayakci <shyim@posteo.de>
*/ */
public function __construct(?string $name = null, Connection $connection, NginxConfigGenerator $configGenerator) public function __construct(?string $name = null, Connection $connection, ConfigGenerator $configGenerator)
{ {
parent::__construct($name); parent::__construct($name);
$this->connection = $connection; $this->connection = $connection;

8
src/Command/GenerateConfigCommand.php

@ -4,7 +4,7 @@
namespace App\Command; namespace App\Command;
use App\Component\NginxConfigGenerator; use App\Component\Nginx\ConfigGenerator;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@ -17,17 +17,17 @@ use Symfony\Component\Console\Style\SymfonyStyle;
class GenerateConfigCommand extends Command class GenerateConfigCommand extends Command
{ {
/** /**
* @var NginxConfigGenerator * @var ConfigGenerator
*/ */
private $configGenerator; private $configGenerator;
/** /**
* GenerateConfigCommand constructor. * GenerateConfigCommand constructor.
* @param null|string $name * @param null|string $name
* @param NginxConfigGenerator $configGenerator * @param ConfigGenerator $configGenerator
* @author Soner Sayakci <shyim@posteo.de> * @author Soner Sayakci <shyim@posteo.de>
*/ */
public function __construct(?string $name = null, NginxConfigGenerator $configGenerator) public function __construct(?string $name = null, ConfigGenerator $configGenerator)
{ {
parent::__construct($name); parent::__construct($name);
$this->configGenerator = $configGenerator; $this->configGenerator = $configGenerator;

34
src/Component/NginxConfigGenerator.php → src/Component/Nginx/ConfigGenerator.php

@ -1,16 +1,17 @@
<?php <?php
namespace App\Component; namespace App\Component\Nginx;
use App\Component\ServiceManager;
use App\Entity\Endpoint; use App\Entity\Endpoint;
use App\Repository\StreamsRepository; use App\Repository\StreamsRepository;
/** /**
* Class NginxConfigGenerator * Class ConfigGenerator
* @author Soner Sayakci <shyim@posteo.de> * @author Soner Sayakci <shyim@posteo.de>
*/ */
class NginxConfigGenerator class ConfigGenerator
{ {
private const VHOST = "\t\tapplication %s { private const VHOST = "\t\tapplication %s {
\t\t\tlive on; \t\t\tlive on;
@ -31,6 +32,15 @@ rtmp {
%s %s
\t} \t}
} }
http {
\tserver {
\t\tlisten 127.0.0.1:26765;
\t\tlocation /stat {
\t\t\trtmp_stat all;
\t\t}
\t}
}
"; ";
/** /**
@ -45,7 +55,7 @@ rtmp {
/** /**
* @var string * @var string
*/ */
private $nginxFolder; private $nginxConfigFolder;
/** /**
* @var string * @var string
@ -53,18 +63,18 @@ rtmp {
private $appHost; private $appHost;
/** /**
* NginxConfigGenerator constructor. * ConfigGenerator constructor.
* @param StreamsRepository $repository * @param StreamsRepository $repository
* @param ServiceManager $manager * @param ServiceManager $manager
* @param string $nginxFolder * @param string $nginxConfigFolder
* @param string $appHost * @param string $appHost
* @author Soner Sayakci <shyim@posteo.de> * @author Soner Sayakci <shyim@posteo.de>
*/ */
public function __construct(StreamsRepository $repository, ServiceManager $manager, string $nginxFolder, string $appHost) public function __construct(StreamsRepository $repository, ServiceManager $manager, string $nginxConfigFolder, string $appHost)
{ {
$this->repository = $repository; $this->repository = $repository;
$this->manager = $manager; $this->manager = $manager;
$this->nginxFolder = $nginxFolder; $this->nginxConfigFolder = $nginxConfigFolder;
$this->appHost = $appHost; $this->appHost = $appHost;
} }
@ -74,9 +84,9 @@ rtmp {
*/ */
public function generate(): void public function generate(): void
{ {
if (!file_exists($this->nginxFolder)) { if (!file_exists($this->nginxConfigFolder)) {
if (!mkdir($this->nginxFolder, 7777, true) && !is_dir($this->nginxFolder)) { if (!mkdir($this->nginxConfigFolder, 7777, true) && !is_dir($this->nginxConfigFolder)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $this->nginxFolder)); throw new \RuntimeException(sprintf('Directory "%s" was not created', $this->nginxConfigFolder));
} }
} }
@ -100,7 +110,7 @@ rtmp {
} }
} }
file_put_contents($this->nginxFolder . '/nginx.conf', sprintf(self::NGINX_CONF, $vhost)); file_put_contents($this->nginxConfigFolder . '/nginx.conf', sprintf(self::NGINX_CONF, $vhost));
} }
/** /**

45
src/Component/Nginx/Stats.php

@ -0,0 +1,45 @@
<?php
namespace App\Component\Nginx;
use App\Entity\Streams;
/**
* Class Stats
* @author Soner Sayakci <shyim@posteo.de>
*/
class Stats
{
/**
* @var array
*/
private $data;
/**
* Stats constructor.
* @author Soner Sayakci <shyim@posteo.de>
*/
public function __construct()
{
$stats = file_get_contents('http://127.0.0.1:26765/stat');
$xml = simplexml_load_string($stats, 'SimpleXMLElement', LIBXML_NOCDATA);
$this->data = json_decode(json_encode($xml), true);
}
/**
* @param Streams $stream
* @author Soner Sayakci <shyim@posteo.de>
* @return array
*/
public function getStatsForStream(Streams $stream): array
{
$appKey = $stream->getUser()->getUsername() . '/' . $stream->getName();
foreach ($this->data['server']['application'] as $application) {
if ($application['name'] === $appKey && isset($application['live']['stream'])) {
return $application['live']['stream'];
}
}
return [];
}
}

37
src/Controller/Streams.php

@ -3,6 +3,7 @@
namespace App\Controller; namespace App\Controller;
use App\Component\Nginx\Stats;
use App\Component\ServiceManager; use App\Component\ServiceManager;
use App\Entity\Endpoint; use App\Entity\Endpoint;
use App\Entity\User; use App\Entity\User;
@ -48,7 +49,7 @@ class Streams extends Controller
* @Route(path="/list") * @Route(path="/list")
* @author Soner Sayakci <shyim@posteo.de> * @author Soner Sayakci <shyim@posteo.de>
*/ */
public function streams() : Response public function streams(): Response
{ {
/** @var User $user */ /** @var User $user */
$user = $this->getUser(); $user = $this->getUser();
@ -62,7 +63,7 @@ class Streams extends Controller
* @return \Symfony\Component\HttpFoundation\RedirectResponse|Response * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
* @author Soner Sayakci <shyim@posteo.de> * @author Soner Sayakci <shyim@posteo.de>
*/ */
public function one(Request $request) : Response public function one(Request $request): Response
{ {
$stream = $this->repository->find($request->query->get('id')); $stream = $this->repository->find($request->query->get('id'));
@ -85,7 +86,7 @@ class Streams extends Controller
* @throws \Doctrine\ORM\OptimisticLockException * @throws \Doctrine\ORM\OptimisticLockException
* @author Soner Sayakci <shyim@posteo.de> * @author Soner Sayakci <shyim@posteo.de>
*/ */
public function update(Request $request) : Response public function update(Request $request): Response
{ {
$requestBody = $request->request->all(); $requestBody = $request->request->all();
@ -105,7 +106,7 @@ class Streams extends Controller
$manager = $this->get('doctrine.orm.entity_manager'); $manager = $this->get('doctrine.orm.entity_manager');
try { try {
$manager->persist($stream); $manager->persist($stream);
$manager->flush(); $manager->flush();
} catch (UniqueConstraintViolationException $e) { } catch (UniqueConstraintViolationException $e) {
@ -124,7 +125,7 @@ class Streams extends Controller
* @throws \Doctrine\ORM\OptimisticLockException * @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\TransactionRequiredException * @throws \Doctrine\ORM\TransactionRequiredException
*/ */
public function regenerate(Request $request) : Response public function regenerate(Request $request): Response
{ {
$manager = $this->get('doctrine.orm.entity_manager'); $manager = $this->get('doctrine.orm.entity_manager');
$streams = $manager->find(\App\Entity\Streams::class, $request->request->get('id')); $streams = $manager->find(\App\Entity\Streams::class, $request->request->get('id'));
@ -147,7 +148,7 @@ class Streams extends Controller
* @throws \Doctrine\ORM\OptimisticLockException * @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\TransactionRequiredException * @throws \Doctrine\ORM\TransactionRequiredException
*/ */
public function delete(Request $request) : Response public function delete(Request $request): Response
{ {
$manager = $this->get('doctrine.orm.entity_manager'); $manager = $this->get('doctrine.orm.entity_manager');
$streams = $manager->find(\App\Entity\Streams::class, $request->request->get('id')); $streams = $manager->find(\App\Entity\Streams::class, $request->request->get('id'));
@ -171,6 +172,24 @@ class Streams extends Controller
return new JsonResponse($manager->getTemplateData()); return new JsonResponse($manager->getTemplateData());
} }
/**
* @Route(path="/{id}/stats")
* @param int $id
* @param Stats $nginxStats
* @return JsonResponse
* @author Soner Sayakci <shyim@posteo.de>
*/
public function stats(Stats $nginxStats, int $id): JsonResponse
{
$stream = $this->repository->find($id);
if ($stream === null || $stream->getUserId() !== $this->getUser()->getId()) {
return new JsonResponse([]);
}
return new JsonResponse($nginxStats->getStatsForStream($stream));
}
/** /**
* @Route(path="/{id}/endpoints/") * @Route(path="/{id}/endpoints/")
* @author Soner Sayakci <shyim@posteo.de> * @author Soner Sayakci <shyim@posteo.de>
@ -188,7 +207,7 @@ class Streams extends Controller
* @param int $id * @param int $id
* @return JsonResponse * @return JsonResponse
*/ */
public function endpoint(int $id) public function endpoint(int $id): JsonResponse
{ {
return new JsonResponse($this->endpointRepository->find($id)); return new JsonResponse($this->endpointRepository->find($id));
} }
@ -249,7 +268,7 @@ class Streams extends Controller
* @throws \Doctrine\ORM\OptimisticLockException * @throws \Doctrine\ORM\OptimisticLockException
* @author Soner Sayakci <shyim@posteo.de> * @author Soner Sayakci <shyim@posteo.de>
*/ */
public function toggleEndpoint(Request $request) : JsonResponse public function toggleEndpoint(Request $request): JsonResponse
{ {
$id = $request->request->get('id'); $id = $request->request->get('id');
$endpoint = $this->endpointRepository->find($id); $endpoint = $this->endpointRepository->find($id);
@ -277,7 +296,7 @@ class Streams extends Controller
* @throws \Doctrine\ORM\OptimisticLockException * @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\TransactionRequiredException * @throws \Doctrine\ORM\TransactionRequiredException
*/ */
public function deleteEndpoint(Request $request) : Response public function deleteEndpoint(Request $request): Response
{ {
$manager = $this->get('doctrine.orm.entity_manager'); $manager = $this->get('doctrine.orm.entity_manager');
$endpoint = $manager->find(Endpoint::class, $request->request->get('id')); $endpoint = $manager->find(Endpoint::class, $request->request->get('id'));

Loading…
Cancel
Save