Created
September 6, 2022 04:38
-
-
Save sujit-baniya/733c7335c39eb6e7ff08f1eba9e0bf77 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script setup> | |
import { Head, Link } from '@inertiajs/inertia-vue3'; | |
import '@vime/core/themes/default.css'; | |
import {computed, ref} from "vue"; | |
import {getLangByCode} from '~/Data/lang' | |
import { | |
ArrowLeftIcon | |
} from '@heroicons/vue/solid' | |
const props = defineProps(['canDownload', 'adsense', 'video', 'poster', 'subtitles', 'id', 'settings', 'source', 'canDownload', 'videos']) | |
const videoSettings = ref(props.settings?.settings ? JSON.parse(props.settings.settings) : null) | |
const videoLink = ref(null) | |
const getVideoLink = (video) => { | |
if (!videoLink.value) { | |
videoLink.value = video | |
} | |
if (video) { | |
return route('stream.video', {id: props.id, filename: video}) | |
} | |
return null | |
} | |
const getPosterLink = computed(() => { | |
if (props.poster) { | |
return route('view.file.content', {id: props.id, filename: props.poster}) | |
} | |
return null | |
}) | |
const getSubtitleLink = (subtitle) => { | |
return route('view.file.content', {id: props.id, filename: subtitle}) | |
} | |
const downloadVideo = (t) => { | |
let link = route('download.video', {id: props.id, filename: videoLink.value}) | |
window.open(link, '_blank'); | |
} | |
const options = ref({ | |
quality: { | |
default: '1080p' | |
}, | |
controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume','settings','pip','download','fullscreen'], | |
// quality: { default: 576, options: [1080, 720, 576] }, | |
storage: { enabled: false }, | |
ads: { | |
enabled: !!props.adsense, | |
tagUrl: props.adsense, | |
} | |
}) | |
const isVideoOnly = computed(() => { | |
return !!(videoSettings.value && videoSettings.value.videos && !props.video); | |
}) | |
const isYoutubeVideo = computed(() => { | |
return !!(!(videoSettings.value && videoSettings.value.videos && !props.video) && videoSettings.value.external.youtube); | |
}) | |
const isVimeoVideo = computed(() => { | |
return !!(!(videoSettings.value && videoSettings.value.videos && !props.video) && videoSettings.value.external.vimeo); | |
}) | |
const isTrailer = computed(() => { | |
return !!props.video; | |
}) | |
</script> | |
<template> | |
<vue-plyr v-if="isVideoOnly" :options="options"> | |
<video controls | |
crossorigin | |
playsinline | |
:data-poster="getPosterLink"> | |
<template v-for="vid in videoSettings.videos"> | |
<source :src="getVideoLink(vid)"/> | |
</template> | |
<track v-for="(subtitle, code) in videoSettings?.subtitles" | |
default | |
kind="subtitles" | |
:src="getSubtitleLink(subtitle)" | |
:srclang="code" | |
:label="getLangByCode(code).label" | |
/> | |
</video> | |
</vue-plyr> | |
<!-- youtube div element --> | |
<vue-plyr | |
v-if="isYoutubeVideo" :options="options"> | |
<div data-plyr-provider="youtube" :data-plyr-embed-id="videoSettings?.external?.youtube"></div> | |
</vue-plyr> | |
<!-- youtube div element --> | |
<vue-plyr | |
v-if="isTrailer" :options="options"> | |
<div data-plyr-provider="youtube" :data-plyr-embed-id="video"></div> | |
</vue-plyr> | |
<!-- vimeo div element --> | |
<vue-plyr | |
v-if="isVimeoVideo" :options="options"> | |
<div data-plyr-provider="vimeo" :data-plyr-embed-id="videoSettings?.external?.vimeo"></div> | |
</vue-plyr> | |
<Link :href="route('browse')" class="flex text-white mt-2 hover:text-gray-200" as="button"> | |
<ArrowLeftIcon class="h-5 w-5 mr-1"/> | |
<span>Back</span> | |
</Link> | |
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Services; | |
use Exception; | |
use Illuminate\Contracts\Filesystem\FileNotFoundException; | |
use Illuminate\Filesystem\FilesystemAdapter; | |
use Illuminate\Http\Response; | |
use Illuminate\Support\Arr; | |
use Illuminate\Support\Facades\Storage; | |
use Illuminate\Support\Str; | |
use League\Flysystem\Filesystem; | |
use Symfony\Component\HttpFoundation\StreamedResponse; | |
class VideoStream { | |
/** | |
* Name of adapter | |
* | |
* @var string | |
*/ | |
private $adapterName; | |
/** | |
* Storage disk | |
* | |
* @var FilesystemAdapter | |
*/ | |
private $disk; | |
/** | |
* @var int file end byte | |
*/ | |
private $end; | |
/** | |
* @var string | |
*/ | |
private $filePath; | |
/** | |
* Human-known filename | |
* | |
* @var string|null | |
*/ | |
private $humanName; | |
/** | |
* @var bool storing if request is a range (or a full file) | |
*/ | |
private $isRange = false; | |
/** | |
* @var int|null length of bytes requested | |
*/ | |
private $length = null; | |
/** | |
* @var array | |
*/ | |
private $returnHeaders = []; | |
/** | |
* @var int file size | |
*/ | |
private $size; | |
/** | |
* @var int start byte | |
*/ | |
private $start; | |
/** | |
* S3FileStream constructor. | |
* @param string $filePath | |
* @param string $adapter | |
* @param string $humanName | |
*/ | |
public function __construct(string $filePath, string $adapter = 's3', ?string $humanName = null) | |
{ | |
$options = [ | |
'region' => env("AWS_DEFAULT_REGION"), | |
'version' => 'latest' | |
]; | |
$this->filePath = $filePath; | |
$this->adapterName = $adapter; | |
$this->disk = Storage::disk($this->adapterName); | |
$this->client = new \Aws\S3\S3Client($options); | |
$this->humanName = $humanName; | |
$this->start = 0; | |
$this->size = 0; | |
$this->end = 0; | |
} | |
/** | |
* Output file to client. | |
*/ | |
public function output() | |
{ | |
return $this->setHeadersAndStream(); | |
} | |
/** | |
* Output headers to client. | |
* @return Response|StreamedResponse | |
*/ | |
protected function setHeadersAndStream() | |
{ | |
if (!$this->disk->exists($this->filePath)) { | |
report(new Exception('S3 File Not Found in S3FileStream - ' . $this->adapterName . ' - ' . $this->disk->path($this->filePath))); | |
throw new FileNotFoundException("File not found " . $this->filePath, 404); | |
} | |
$this->start = 0; | |
$this->size = $this->disk->size($this->filePath); | |
$this->end = $this->size - 1; | |
$this->length = $this->size; | |
$this->isRange = false; | |
//Set headers | |
$this->returnHeaders = [ | |
'Last-Modified' => $this->disk->lastModified($this->filePath), | |
'Accept-Ranges' => 'bytes', | |
'Content-Type' => $this->disk->mimeType($this->filePath), | |
'Content-Disposition' => 'inline; filename=' . ($this->humanName ?? basename($this->filePath) . '.' . Arr::last(explode('.', $this->filePath))), | |
'Content-Length' => $this->length, | |
]; | |
//Handle ranges here | |
if (!is_null(request()->server('HTTP_RANGE'))) { | |
$cStart = $this->start; | |
$cEnd = $this->end; | |
$range = Str::after(request()->server('HTTP_RANGE'), '='); | |
if (strpos($range, ',') !== false) { | |
return response('416 Requested Range Not Satisfiable', 416, [ | |
'Content-Range' => 'bytes */' . $this->size, | |
]); | |
} | |
if (substr($range, 0, 1) == '-') { | |
$cStart = $this->size - intval(substr($range, 1)) - 1; | |
} else { | |
$range = explode('-', $range); | |
$cStart = intval($range[0]); | |
$cEnd = (isset($range[1]) && is_numeric($range[1])) ? intval($range[1]) : $cEnd; | |
} | |
$cEnd = min($cEnd, $this->size - 1); | |
if ($cStart > $cEnd || $cStart > $this->size - 1) { | |
return response('416 Requested Range Not Satisfiable', 416, [ | |
'Content-Range' => 'bytes */' . $this->size, | |
]); | |
} | |
$this->start = intval($cStart); | |
$this->end = intval($cEnd); | |
$this->length = min($this->end - $this->start + 1, $this->size); | |
$this->returnHeaders['Content-Length'] = $this->length; | |
$this->returnHeaders['Content-Range'] = 'bytes ' . $this->start . '-' . $this->end . '/' . $this->size; | |
$this->isRange = true; | |
} | |
return $this->stream(); | |
} | |
/** | |
* Stream file to client. | |
* @throws Exception | |
* @return StreamedResponse | |
*/ | |
protected function stream(): StreamedResponse | |
{ | |
$stream = $this->disk->readStream($this->filePath); | |
if (isset($this->start) && $this->start > 0) { | |
fseek($stream, $this->start, SEEK_SET); | |
} | |
$remainingBytes = $this->length ?? $this->size; | |
$chunkSize = 100; | |
return response()->stream( | |
function () use ($stream, $remainingBytes, $chunkSize) { | |
while (!feof($stream) && $remainingBytes > 0) { | |
$toGrab = min($chunkSize, $remainingBytes); | |
echo fread($stream, $toGrab); | |
$remainingBytes -= $toGrab; | |
flush(); | |
} | |
fclose($stream); | |
}, | |
($this->isRange ? 206 : 200), | |
$this->returnHeaders | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment