mirror of
https://github.com/sigmasternchen/MyTube
synced 2025-03-15 21:08:55 +00:00
playback and owner links work
This commit is contained in:
parent
53217b7d37
commit
4541164ad3
14 changed files with 2275 additions and 2 deletions
67
migrations/Version20210106134556.php
Normal file
67
migrations/Version20210106134556.php
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20210106134556 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TABLE video_link (id BLOB NOT NULL, video_id BLOB NOT NULL, created DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||||
|
, mode INTEGER NOT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('CREATE INDEX IDX_313BC42D29C1004E ON video_link (video_id)');
|
||||||
|
$this->addSql('DROP INDEX UNIQ_8D93D6495E237E06');
|
||||||
|
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, password, name, roles FROM user');
|
||||||
|
$this->addSql('DROP TABLE user');
|
||||||
|
$this->addSql('CREATE TABLE user (id BLOB NOT NULL, password VARCHAR(255) NOT NULL COLLATE BINARY, name VARCHAR(180) NOT NULL COLLATE BINARY, roles CLOB NOT NULL COLLATE BINARY --(DC2Type:json)
|
||||||
|
, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('INSERT INTO user (id, password, name, roles) SELECT id, password, name, roles FROM __temp__user');
|
||||||
|
$this->addSql('DROP TABLE __temp__user');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D6495E237E06 ON user (name)');
|
||||||
|
$this->addSql('DROP INDEX IDX_7CC7DA2C16678C77');
|
||||||
|
$this->addSql('CREATE TEMPORARY TABLE __temp__video AS SELECT id, uploader_id, uploaded, name, description, tags, state FROM video');
|
||||||
|
$this->addSql('DROP TABLE video');
|
||||||
|
$this->addSql('CREATE TABLE video (id BLOB NOT NULL, uploader_id BLOB NOT NULL, uploaded DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||||
|
, name VARCHAR(255) NOT NULL COLLATE BINARY, description VARCHAR(1024) NOT NULL COLLATE BINARY, tags CLOB NOT NULL COLLATE BINARY --(DC2Type:array)
|
||||||
|
, state INTEGER NOT NULL, PRIMARY KEY(id), CONSTRAINT FK_7CC7DA2C16678C77 FOREIGN KEY (uploader_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||||
|
$this->addSql('INSERT INTO video (id, uploader_id, uploaded, name, description, tags, state) SELECT id, uploader_id, uploaded, name, description, tags, state FROM __temp__video');
|
||||||
|
$this->addSql('DROP TABLE __temp__video');
|
||||||
|
$this->addSql('CREATE INDEX IDX_7CC7DA2C16678C77 ON video (uploader_id)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('DROP TABLE video_link');
|
||||||
|
$this->addSql('DROP INDEX UNIQ_8D93D6495E237E06');
|
||||||
|
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, name, roles, password FROM user');
|
||||||
|
$this->addSql('DROP TABLE user');
|
||||||
|
$this->addSql('CREATE TABLE user (id BLOB NOT NULL, name VARCHAR(180) NOT NULL, roles CLOB NOT NULL --(DC2Type:json)
|
||||||
|
, password VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('INSERT INTO user (id, name, roles, password) SELECT id, name, roles, password FROM __temp__user');
|
||||||
|
$this->addSql('DROP TABLE __temp__user');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D6495E237E06 ON user (name)');
|
||||||
|
$this->addSql('DROP INDEX IDX_7CC7DA2C16678C77');
|
||||||
|
$this->addSql('CREATE TEMPORARY TABLE __temp__video AS SELECT id, uploader_id, uploaded, name, description, tags, state FROM video');
|
||||||
|
$this->addSql('DROP TABLE video');
|
||||||
|
$this->addSql('CREATE TABLE video (id BLOB NOT NULL, uploaded DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||||
|
, name VARCHAR(255) NOT NULL, description VARCHAR(1024) NOT NULL, tags CLOB NOT NULL --(DC2Type:array)
|
||||||
|
, state INTEGER NOT NULL, uploader_id BLOB NOT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('INSERT INTO video (id, uploader_id, uploaded, name, description, tags, state) SELECT id, uploader_id, uploaded, name, description, tags, state FROM __temp__video');
|
||||||
|
$this->addSql('DROP TABLE __temp__video');
|
||||||
|
$this->addSql('CREATE INDEX IDX_7CC7DA2C16678C77 ON video (uploader_id)');
|
||||||
|
}
|
||||||
|
}
|
1727
public/css/video-js.css
Normal file
1727
public/css/video-js.css
Normal file
File diff suppressed because one or more lines are too long
26
public/js/video.min.js
vendored
Normal file
26
public/js/video.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -6,6 +6,7 @@ namespace App\Controller;
|
||||||
|
|
||||||
use App\Entity\Video;
|
use App\Entity\Video;
|
||||||
use App\Form\VideoType;
|
use App\Form\VideoType;
|
||||||
|
use App\Mapper\CustomUuidMapper;
|
||||||
use App\Service\UserService;
|
use App\Service\UserService;
|
||||||
use App\Service\VideoService;
|
use App\Service\VideoService;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
@ -18,11 +19,13 @@ class HomeController extends AbstractController
|
||||||
{
|
{
|
||||||
private $userService;
|
private $userService;
|
||||||
private $videoService;
|
private $videoService;
|
||||||
|
private $uuidMapper;
|
||||||
|
|
||||||
public function __construct(UserService $userService, VideoService $videoService)
|
public function __construct(UserService $userService, VideoService $videoService, CustomUuidMapper $uuidMapper)
|
||||||
{
|
{
|
||||||
$this->userService = $userService;
|
$this->userService = $userService;
|
||||||
$this->videoService = $videoService;
|
$this->videoService = $videoService;
|
||||||
|
$this->uuidMapper = $uuidMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,6 +41,10 @@ class HomeController extends AbstractController
|
||||||
$user = $this->userService->getLoggedInUser();
|
$user = $this->userService->getLoggedInUser();
|
||||||
$videos = $this->videoService->getVideos($user);
|
$videos = $this->videoService->getVideos($user);
|
||||||
|
|
||||||
|
foreach ($videos as $video) {
|
||||||
|
$video->setCustomId($this->uuidMapper->toString($video->getId()));
|
||||||
|
}
|
||||||
|
|
||||||
return $this->render("home/dashboard.html.twig", [
|
return $this->render("home/dashboard.html.twig", [
|
||||||
"videos" => $videos
|
"videos" => $videos
|
||||||
]);
|
]);
|
||||||
|
|
152
src/Controller/WatchController.php
Normal file
152
src/Controller/WatchController.php
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Entity\Video;
|
||||||
|
use App\Mapper\CustomUuidMapper;
|
||||||
|
use App\Service\UserService;
|
||||||
|
use App\Service\VideoLinkService;
|
||||||
|
use App\Service\VideoService;
|
||||||
|
use Doctrine\DBAL\Types\ConversionException;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
class WatchController extends AbstractController
|
||||||
|
{
|
||||||
|
private const NOT_ALLOWED = 0;
|
||||||
|
private const ALLOWED = 1;
|
||||||
|
private const IS_OWNER = 2;
|
||||||
|
|
||||||
|
public const OWNER_LINK_ID = "owner";
|
||||||
|
public const CONTENT_DIRECTORY = "../content/";
|
||||||
|
|
||||||
|
private const PLAYLIST_MIME_TYPE = "application/x-mpegURL";
|
||||||
|
private const TS_FILE_MIME_TYPE = "video/MP2T";
|
||||||
|
|
||||||
|
private const TS_FILE_FORMAT = "seg-%06d-ts";
|
||||||
|
|
||||||
|
private $userService;
|
||||||
|
private $videoService;
|
||||||
|
private $videoLinkService;
|
||||||
|
private $uuidMapper;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
UserService $userService,
|
||||||
|
VideoService $videoService,
|
||||||
|
VideoLinkService $videoLinkService,
|
||||||
|
CustomUuidMapper $uuidMapper
|
||||||
|
)
|
||||||
|
{
|
||||||
|
$this->userService = $userService;
|
||||||
|
$this->videoService = $videoService;
|
||||||
|
$this->videoLinkService = $videoLinkService;
|
||||||
|
$this->uuidMapper = $uuidMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isAllowed(?Video $video, ?User $user, $linkId): int
|
||||||
|
{
|
||||||
|
if ($video->getUploader() == $user) {
|
||||||
|
return self::IS_OWNER;
|
||||||
|
}
|
||||||
|
|
||||||
|
$link = $this->videoLinkService->get($this->uuidMapper->fromString($linkId));
|
||||||
|
if (!$link) {
|
||||||
|
return self::NOT_ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($link->getVideo() != $video) {
|
||||||
|
return self::NOT_ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check constraints
|
||||||
|
|
||||||
|
return self::ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkRequestData($videoId, $linkId): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$video = $this->videoService->get($this->uuidMapper->fromString($videoId));
|
||||||
|
$user = $this->userService->getLoggedInUser();
|
||||||
|
|
||||||
|
$allowed = $this->isAllowed($video, $user, $linkId);
|
||||||
|
} catch (ConversionException $e) {
|
||||||
|
throw new AccessDeniedHttpException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$allowed) {
|
||||||
|
throw new AccessDeniedHttpException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"video" => $video,
|
||||||
|
"user" => $user,
|
||||||
|
"isOwner" => $allowed == self::IS_OWNER
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/{linkId}/{videoId}/playlist", name="app_watch_global")
|
||||||
|
*/
|
||||||
|
public function globalPlaylist($videoId, $linkId): Response
|
||||||
|
{
|
||||||
|
$data = $this->checkRequestData($videoId, $linkId);
|
||||||
|
|
||||||
|
$file = self::CONTENT_DIRECTORY . $data["video"]->getId() . "/" . "playlist.m3u8";
|
||||||
|
|
||||||
|
$response = new BinaryFileResponse($file);
|
||||||
|
$response->headers->set("Content-Type", self::PLAYLIST_MIME_TYPE);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/{linkId}/{videoId}/{quality}/playlist", name="app_watch_quality", requirements={"quality"="360|480|720|1080"})
|
||||||
|
*/
|
||||||
|
public function qualityPlaylist($videoId, $linkId, int $quality): Response
|
||||||
|
{
|
||||||
|
$data = $this->checkRequestData($videoId, $linkId);
|
||||||
|
|
||||||
|
$file = self::CONTENT_DIRECTORY . $data["video"]->getId() . "/" . $quality . "p/" . "playlist.m3u8";
|
||||||
|
|
||||||
|
$response = new BinaryFileResponse($file);
|
||||||
|
$response->headers->set("Content-Type", self::PLAYLIST_MIME_TYPE);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/{linkId}/{videoId}/{quality}/seg-{tsFileId}-ts", name="app_watch_segment", requirements={"quality"="360|480|720|1080", "tsFileId"="\d+"})
|
||||||
|
*/
|
||||||
|
public function tsFiles($videoId, $linkId, int $quality, int $tsFileId): Response
|
||||||
|
{
|
||||||
|
$data = $this->checkRequestData($videoId, $linkId);
|
||||||
|
|
||||||
|
$file = self::CONTENT_DIRECTORY . $data["video"]->getId() . "/" . $quality . "p/" . sprintf(self::TS_FILE_FORMAT, $tsFileId);
|
||||||
|
|
||||||
|
$response = new BinaryFileResponse($file);
|
||||||
|
$response->headers->set("Content-Type", self::TS_FILE_MIME_TYPE);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/{linkId}/{videoId}/", name="app_watch_page")
|
||||||
|
*/
|
||||||
|
public function watchPage($videoId, $linkId): Response
|
||||||
|
{
|
||||||
|
$data = $this->checkRequestData($videoId, $linkId);
|
||||||
|
|
||||||
|
return $this->render("watch/watch.html.twig", [
|
||||||
|
"thumbnail" => "thumbnail.jpg",
|
||||||
|
"global" => $this->generateUrl("app_watch_global", [
|
||||||
|
"linkId" => $linkId,
|
||||||
|
"videoId" => $videoId
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ namespace App\Entity;
|
||||||
|
|
||||||
use App\Repository\VideoRepository;
|
use App\Repository\VideoRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Ramsey\Uuid\Doctrine\UuidGenerator;
|
use Ramsey\Uuid\Doctrine\UuidGenerator;
|
||||||
use Ramsey\Uuid\UuidInterface;
|
use Ramsey\Uuid\UuidInterface;
|
||||||
|
@ -17,6 +19,7 @@ class Video
|
||||||
public const PROCESSING_THUMBNAIL = 2;
|
public const PROCESSING_THUMBNAIL = 2;
|
||||||
public const PROCESSING_TRANSCODE = 3;
|
public const PROCESSING_TRANSCODE = 3;
|
||||||
public const DONE = 4;
|
public const DONE = 4;
|
||||||
|
public const FAIL = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Id
|
* @ORM\Id
|
||||||
|
@ -25,6 +28,7 @@ class Video
|
||||||
* @ORM\CustomIdGenerator(class=UuidGenerator::class)
|
* @ORM\CustomIdGenerator(class=UuidGenerator::class)
|
||||||
*/
|
*/
|
||||||
private $id;
|
private $id;
|
||||||
|
private $customId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\ManyToOne(targetEntity=User::class, inversedBy="videos")
|
* @ORM\ManyToOne(targetEntity=User::class, inversedBy="videos")
|
||||||
|
@ -57,6 +61,16 @@ class Video
|
||||||
*/
|
*/
|
||||||
private $state = self::WAITING;
|
private $state = self::WAITING;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\OneToMany(targetEntity=VideoLink::class, mappedBy="video")
|
||||||
|
*/
|
||||||
|
private $videoLinks;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->videoLinks = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
public function getId(): ?UuidInterface
|
public function getId(): ?UuidInterface
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
|
@ -131,4 +145,63 @@ class Video
|
||||||
{
|
{
|
||||||
return $this->state;
|
return $this->state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getStateString(): string
|
||||||
|
{
|
||||||
|
switch ($this->state) {
|
||||||
|
case self::WAITING:
|
||||||
|
return "waiting";
|
||||||
|
case self::PROCESSING_THUMBNAIL:
|
||||||
|
return "thumbnail";
|
||||||
|
case self::PROCESSING_TRANSCODE:
|
||||||
|
return "transcoding";
|
||||||
|
case self::DONE:
|
||||||
|
return "done";
|
||||||
|
case self::FAIL:
|
||||||
|
return "fail";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection|VideoLink[]
|
||||||
|
*/
|
||||||
|
public function getVideoLinks(): Collection
|
||||||
|
{
|
||||||
|
return $this->videoLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addVideoLink(VideoLink $videoLink): self
|
||||||
|
{
|
||||||
|
if (!$this->videoLinks->contains($videoLink)) {
|
||||||
|
$this->videoLinks[] = $videoLink;
|
||||||
|
$videoLink->setVideo($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeVideoLink(VideoLink $videoLink): self
|
||||||
|
{
|
||||||
|
if ($this->videoLinks->removeElement($videoLink)) {
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($videoLink->getVideo() === $this) {
|
||||||
|
$videoLink->setVideo(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCustomId(): string
|
||||||
|
{
|
||||||
|
return $this->customId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCustomId($customId): self
|
||||||
|
{
|
||||||
|
$this->customId = $customId;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
80
src/Entity/VideoLink.php
Normal file
80
src/Entity/VideoLink.php
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\VideoLinkRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Ramsey\Uuid\Doctrine\UuidGenerator;
|
||||||
|
use Ramsey\Uuid\UuidInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity(repositoryClass=VideoLinkRepository::class)
|
||||||
|
*/
|
||||||
|
class VideoLink
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\Column(type="uuid", unique=true)
|
||||||
|
* @ORM\GeneratedValue(strategy="CUSTOM")
|
||||||
|
* @ORM\CustomIdGenerator(class=UuidGenerator::class)
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity=Video::class, inversedBy="videoLinks")
|
||||||
|
* @ORM\JoinColumn(nullable=false)
|
||||||
|
*/
|
||||||
|
private $video;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="datetime_immutable")
|
||||||
|
*/
|
||||||
|
private $created;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private $mode = 0;
|
||||||
|
|
||||||
|
public function getId(): ?UuidInterface
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVideo(): ?Video
|
||||||
|
{
|
||||||
|
return $this->video;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setVideo(?Video $video): self
|
||||||
|
{
|
||||||
|
$this->video = $video;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreated(): ?DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreated(DateTimeImmutable $created): self
|
||||||
|
{
|
||||||
|
$this->created = $created;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMode(): ?int
|
||||||
|
{
|
||||||
|
return $this->mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMode(int $mode): self
|
||||||
|
{
|
||||||
|
$this->mode = $mode;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
29
src/Mapper/CustomUuidMapper.php
Normal file
29
src/Mapper/CustomUuidMapper.php
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace App\Mapper;
|
||||||
|
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Types\ConversionException;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
use Ramsey\Uuid\UuidInterface;
|
||||||
|
|
||||||
|
class CustomUuidMapper
|
||||||
|
{
|
||||||
|
private const REPLACE_SLASH = "-";
|
||||||
|
|
||||||
|
public function fromString($str): UuidInterface
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return Uuid::fromBytes(base64_decode(str_replace(self::REPLACE_SLASH, "/", $str) . "=="));
|
||||||
|
} catch (InvalidArgumentException $e) {
|
||||||
|
throw new ConversionException($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString(UuidInterface $uuid): string
|
||||||
|
{
|
||||||
|
return str_replace("/", self::REPLACE_SLASH, str_replace("==", "", base64_encode($uuid->getBytes())));
|
||||||
|
}
|
||||||
|
}
|
50
src/Repository/VideoLinkRepository.php
Normal file
50
src/Repository/VideoLinkRepository.php
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\VideoLink;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method VideoLink|null find($id, $lockMode = null, $lockVersion = null)
|
||||||
|
* @method VideoLink|null findOneBy(array $criteria, array $orderBy = null)
|
||||||
|
* @method VideoLink[] findAll()
|
||||||
|
* @method VideoLink[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
|
*/
|
||||||
|
class VideoLinkRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, VideoLink::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @return VideoLink[] Returns an array of VideoLink objects
|
||||||
|
// */
|
||||||
|
/*
|
||||||
|
public function findByExampleField($value)
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('v')
|
||||||
|
->andWhere('v.exampleField = :val')
|
||||||
|
->setParameter('val', $value)
|
||||||
|
->orderBy('v.id', 'ASC')
|
||||||
|
->setMaxResults(10)
|
||||||
|
->getQuery()
|
||||||
|
->getResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
public function findOneBySomeField($value): ?VideoLink
|
||||||
|
{
|
||||||
|
return $this->createQueryBuilder('v')
|
||||||
|
->andWhere('v.exampleField = :val')
|
||||||
|
->setParameter('val', $value)
|
||||||
|
->getQuery()
|
||||||
|
->getOneOrNullResult()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
24
src/Service/VideoLinkService.php
Normal file
24
src/Service/VideoLinkService.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
|
||||||
|
use App\Entity\VideoLink;
|
||||||
|
use App\Repository\VideoLinkRepository;
|
||||||
|
|
||||||
|
class VideoLinkService
|
||||||
|
{
|
||||||
|
|
||||||
|
private $videoLinkRepository;
|
||||||
|
|
||||||
|
public function __construct(VideoLinkRepository $videoLinkRepository)
|
||||||
|
{
|
||||||
|
$this->videoLinkRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get($linkId): ?VideoLink
|
||||||
|
{
|
||||||
|
return $this->videoLinkRepository->findOneById($linkId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,4 +46,9 @@ class VideoService
|
||||||
$video->setState($state);
|
$video->setState($state);
|
||||||
$this->videoRepository->update();
|
$this->videoRepository->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function get($videoId): ?Video
|
||||||
|
{
|
||||||
|
return $this->videoRepository->findOneById($videoId);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
{% block javascripts %}{% endblock %}
|
|
||||||
<script src="{{ asset("js/mdb.min.js") }}"></script>
|
<script src="{{ asset("js/mdb.min.js") }}"></script>
|
||||||
|
{% block javascripts %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
{% for video in videos %}
|
{% for video in videos %}
|
||||||
<div>
|
<div>
|
||||||
{{ video.name }}
|
{{ video.name }}
|
||||||
|
(<a href="{{ path("app_watch_page", {linkId: constant("App\\Controller\\WatchController::OWNER_LINK_ID"), videoId: video.customId}) }}">Link</a>)
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
|
32
templates/watch/watch.html.twig
Normal file
32
templates/watch/watch.html.twig
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Watch{% endblock %}
|
||||||
|
|
||||||
|
{% block stylesheets %}
|
||||||
|
<link rel="stylesheet" href="{{ asset("css/video-js.css") }}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascripts %}
|
||||||
|
<script src="{{ asset("js/video.min.js") }}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<video
|
||||||
|
id="my-video"
|
||||||
|
class="video-js"
|
||||||
|
controls
|
||||||
|
preload="auto"
|
||||||
|
width="640"
|
||||||
|
height="264"
|
||||||
|
poster="{{ thumbnail }}"
|
||||||
|
data-setup="{}"
|
||||||
|
>
|
||||||
|
<!--<source src="{{ global }}#.m3u8" type="application/vnd.apple.mpegur" />-->
|
||||||
|
<source src="{{ global }}#.m3u8" type="application/x-mpegURL"/>
|
||||||
|
<p class="vjs-no-js">
|
||||||
|
To view this video please enable JavaScript, and consider upgrading to a
|
||||||
|
web browser that
|
||||||
|
<a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>
|
||||||
|
</p>
|
||||||
|
</video>
|
||||||
|
{% endblock %}
|
Loading…
Reference in a new issue