Initial Commit
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
@@ -74,23 +75,38 @@ abstract class MediaCacher
|
|||||||
|
|
||||||
private function download(string $url, string $pathWithoutExtension): ?string
|
private function download(string $url, string $pathWithoutExtension): ?string
|
||||||
{
|
{
|
||||||
|
// Stream the response straight to disk via Guzzle's `sink` option
|
||||||
|
// instead of buffering the whole body in memory — a several-MB
|
||||||
|
// video/reel can easily exceed PHP's default memory_limit if
|
||||||
|
// fetched the normal way (Http::get()->body() reads it all into a
|
||||||
|
// string first). The extension isn't known until we see the
|
||||||
|
// response, so this writes to a temp file and renames it after.
|
||||||
|
$tempPath = $pathWithoutExtension.'.downloading-'.uniqid();
|
||||||
|
$absoluteTempPath = Storage::disk('public')->path($tempPath);
|
||||||
|
|
||||||
|
File::ensureDirectoryExists(dirname($absoluteTempPath));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$response = Http::withHeaders(['User-Agent' => 'Mozilla/5.0'])->timeout(30)->get($url);
|
$response = Http::withHeaders(['User-Agent' => 'Mozilla/5.0'])
|
||||||
|
->timeout(60)
|
||||||
|
->withOptions(['sink' => $absoluteTempPath])
|
||||||
|
->get($url);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
Log::warning(static::class.": failed to fetch {$url}: {$e->getMessage()}");
|
Log::warning(static::class.": failed to fetch {$url}: {$e->getMessage()}");
|
||||||
|
@unlink($absoluteTempPath);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $response->successful()) {
|
if (! $response->successful()) {
|
||||||
Log::warning(static::class.": got {$response->status()} fetching {$url}");
|
Log::warning(static::class.": got {$response->status()} fetching {$url}");
|
||||||
|
@unlink($absoluteTempPath);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$path = $pathWithoutExtension.'.'.$this->guessExtension($url, $response->header('Content-Type'));
|
$path = $pathWithoutExtension.'.'.$this->guessExtension($url, $response->header('Content-Type'));
|
||||||
|
Storage::disk('public')->move($tempPath, $path);
|
||||||
Storage::disk('public')->put($path, $response->body());
|
|
||||||
|
|
||||||
return '/storage/'.$path;
|
return '/storage/'.$path;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Avatar from '@/components/dredge/Avatar.vue';
|
import Avatar from '@/components/dredge/Avatar.vue';
|
||||||
|
import PostVideo from './PostVideo.vue';
|
||||||
import type { Post } from '@/types/dredge.types';
|
import type { Post } from '@/types/dredge.types';
|
||||||
import { usePauseOffscreen } from '@/composables/usePauseOffScreen';
|
import { usePauseOffscreen } from '@/composables/usePauseOffScreen';
|
||||||
import { ComponentPublicInstance, ref } from 'vue';
|
import { ComponentPublicInstance, ref } from 'vue';
|
||||||
@@ -76,20 +77,12 @@ usePauseOffscreen(videoRefs);
|
|||||||
:style="{ '--slide-count': carouselSlides.length }"
|
:style="{ '--slide-count': carouselSlides.length }"
|
||||||
>
|
>
|
||||||
<v-carousel-item v-for="(slide, i) in carouselSlides" :key="i">
|
<v-carousel-item v-for="(slide, i) in carouselSlides" :key="i">
|
||||||
<video
|
<PostVideo
|
||||||
v-if="slide.video_url"
|
v-if="slide.video_url"
|
||||||
:src="slide.video_url"
|
:src="slide.video_url"
|
||||||
:aria-label="slide.alt"
|
:aria-label="slide.alt"
|
||||||
:ref="registerVideo"
|
:register-video="registerVideo"
|
||||||
controls
|
/>
|
||||||
autoplay
|
|
||||||
muted
|
|
||||||
preload="metadata"
|
|
||||||
playsinline
|
|
||||||
class="card__video"
|
|
||||||
>
|
|
||||||
Your browser doesn't support video playback.
|
|
||||||
</video>
|
|
||||||
<v-img
|
<v-img
|
||||||
v-else-if="slide.url"
|
v-else-if="slide.url"
|
||||||
:src="slide.url"
|
:src="slide.url"
|
||||||
@@ -117,21 +110,13 @@ usePauseOffscreen(videoRefs);
|
|||||||
</v-carousel>
|
</v-carousel>
|
||||||
|
|
||||||
<!-- Single video -->
|
<!-- Single video -->
|
||||||
<video
|
<PostVideo
|
||||||
v-else-if="post.is_video && post.video_url"
|
v-else-if="post.is_video && post.video_url"
|
||||||
:src="post.video_url"
|
:src="post.video_url"
|
||||||
:aria-label="imageAlt"
|
:aria-label="imageAlt"
|
||||||
:ref="registerVideo"
|
:poster="post.image_url"
|
||||||
:poster="post.image_url ?? undefined"
|
:register-video="registerVideo"
|
||||||
controls
|
/>
|
||||||
autoplay
|
|
||||||
muted
|
|
||||||
preload="metadata"
|
|
||||||
playsinline
|
|
||||||
class="card__video"
|
|
||||||
>
|
|
||||||
Your browser doesn't support video playback.
|
|
||||||
</video>
|
|
||||||
|
|
||||||
<!-- Single image -->
|
<!-- Single image -->
|
||||||
<v-img
|
<v-img
|
||||||
@@ -313,14 +298,6 @@ usePauseOffscreen(videoRefs);
|
|||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card__video {
|
|
||||||
width: 100%;
|
|
||||||
max-height: 600px;
|
|
||||||
object-fit: contain;
|
|
||||||
background: #000;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Delimiter row: allow it to fit or scroll instead of clipping */
|
/* Delimiter row: allow it to fit or scroll instead of clipping */
|
||||||
:deep(.v-carousel__controls) {
|
:deep(.v-carousel__controls) {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
src: string;
|
||||||
|
ariaLabel: string;
|
||||||
|
poster?: string | null;
|
||||||
|
registerVideo: (el: Element | null) => void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const videoEl = ref<HTMLVideoElement | null>(null);
|
||||||
|
const isMuted = ref(true);
|
||||||
|
|
||||||
|
function setVideoRef(el: Element | null) {
|
||||||
|
videoEl.value = el as HTMLVideoElement | null;
|
||||||
|
props.registerVideo(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMute() {
|
||||||
|
if (!videoEl.value) return;
|
||||||
|
videoEl.value.muted = !videoEl.value.muted;
|
||||||
|
isMuted.value = videoEl.value.muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePlay() {
|
||||||
|
if (!videoEl.value) return;
|
||||||
|
if (videoEl.value.paused) {
|
||||||
|
videoEl.value.play().catch(() => {});
|
||||||
|
} else {
|
||||||
|
videoEl.value.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="post-video">
|
||||||
|
<video
|
||||||
|
:ref="setVideoRef"
|
||||||
|
:src="src"
|
||||||
|
:aria-label="ariaLabel"
|
||||||
|
:poster="poster ?? undefined"
|
||||||
|
autoplay
|
||||||
|
muted
|
||||||
|
preload="metadata"
|
||||||
|
playsinline
|
||||||
|
class="post-video__el"
|
||||||
|
@click="togglePlay"
|
||||||
|
>
|
||||||
|
Your browser doesn't support video playback.
|
||||||
|
</video>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="post-video__mute"
|
||||||
|
:aria-label="isMuted ? 'Unmute video' : 'Mute video'"
|
||||||
|
@click.stop="toggleMute"
|
||||||
|
>
|
||||||
|
<v-icon :icon="isMuted ? 'mdi-volume-off' : 'mdi-volume-high'" size="16" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.post-video {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.post-video__el {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 600px;
|
||||||
|
object-fit: contain;
|
||||||
|
background: #000;
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.post-video__mute {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0.6rem;
|
||||||
|
right: 0.6rem;
|
||||||
|
width: 1.8rem;
|
||||||
|
height: 1.8rem;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: none;
|
||||||
|
background: rgba(0, 0, 0, 0.55);
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.post-video__mute:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.75);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user