Refactor bumper preview functionality: update API endpoints to support rendering previews for all bumper sub-blocks, modify related query and handler to generate individual asset previews, and enhance frontend components for improved user experience with variant-specific previews. Update translations for consistency in terminology.

This commit is contained in:
Leonid Pershin
2026-07-25 13:31:38 +03:00
parent a65bcf4258
commit 18325fdfcc
6 changed files with 116 additions and 89 deletions
@@ -47,7 +47,7 @@ import {
removeBumperVariant,
removeChannelAd,
removeChannelShow,
renderBumperPreview,
renderBumperPreviews,
setBumperTemplateBackground,
updateBumperTemplate,
updateBumperVariant,
@@ -694,7 +694,12 @@ function BumperTemplateEditor({
</div>
<div className="flex flex-col gap-2 border-t border-border pt-3">
<BumperPreviewPlayer channelId={channelId} templateId={template.id} onError={onError} />
<BumperPreviewPlayer
channelId={channelId}
templateId={template.id}
variants={template.variants}
onError={onError}
/>
</div>
<div className="flex justify-end">
@@ -871,19 +876,20 @@ function BumperVariantEditor({
function BumperPreviewPlayer({
channelId,
templateId,
variants,
onError,
}: {
channelId: string
templateId: string
variants: BumperTextVariantDto[]
onError: (e: unknown) => void
}) {
const { t } = useTranslation()
const videoRef = useRef<HTMLVideoElement>(null)
const [ready, setReady] = useState(false)
const [bust, setBust] = useState(0)
const render = useMutation({
mutationFn: () => renderBumperPreview(channelId, templateId),
mutationFn: () => renderBumperPreviews(channelId, templateId),
onSuccess: () => {
setBust(Date.now())
setReady(true)
@@ -891,30 +897,6 @@ function BumperPreviewPlayer({
onError,
})
// Грузим отрендеренный превью-плейлист через hls.js, добавляя Bearer-токен (admin-роут под JWT).
useEffect(() => {
if (!ready) return
const video = videoRef.current
if (!video) return
const src = `${bumperPreviewPlaylistUrl(channelId, templateId)}?t=${bust}`
let hls: Hls | null = null
if (Hls.isSupported()) {
hls = new Hls({
xhrSetup: (xhr) => {
const token = getAccessToken()
if (token) xhr.setRequestHeader('Authorization', `Bearer ${token}`)
},
})
hls.loadSource(src)
hls.attachMedia(video)
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = src
}
return () => {
hls?.destroy()
}
}, [ready, bust, channelId, templateId])
return (
<>
<div className="flex flex-wrap items-center gap-2">
@@ -933,17 +915,56 @@ function BumperPreviewPlayer({
</span>
</div>
{ready && (
<video
ref={videoRef}
controls
playsInline
className="aspect-video w-full max-w-sm rounded-md border border-border bg-black"
/>
<div className="grid gap-3 sm:grid-cols-2">
{[...variants]
.sort((a, b) => a.position - b.position)
.map((v) => (
<div key={v.id} className="flex flex-col gap-1">
<span className="text-xs text-muted-foreground">{v.name}</span>
<PreviewVideo
src={`${bumperPreviewPlaylistUrl(channelId, templateId, v.id)}?t=${bust}`}
/>
</div>
))}
</div>
)}
</>
)
}
/** Мини-плеер одного превью: грузит HLS через hls.js с Bearer-токеном (admin-роут под JWT). */
function PreviewVideo({ src }: { src: string }) {
const videoRef = useRef<HTMLVideoElement>(null)
useEffect(() => {
const video = videoRef.current
if (!video) return
let hls: Hls | null = null
if (Hls.isSupported()) {
hls = new Hls({
xhrSetup: (xhr) => {
const token = getAccessToken()
if (token) xhr.setRequestHeader('Authorization', `Bearer ${token}`)
},
})
hls.loadSource(src)
hls.attachMedia(video)
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = src
}
return () => {
hls?.destroy()
}
}, [src])
return (
<video
ref={videoRef}
controls
playsInline
className="aspect-video w-full rounded-md border border-border bg-black"
/>
)
}
function BumperBackgroundField({
channelId,
templateId,
+4 -4
View File
@@ -195,15 +195,15 @@ export function clearBumperTemplateBackground(id: string, templateId: string) {
})
}
/** Синхронно рендерит пример заставки блока (сервер собирает ffmpeg-клип). */
export function renderBumperPreview(id: string, templateId: string) {
/** Синхронно рендерит примеры всех подблоков блока (сервер собирает ffmpeg-клипы). */
export function renderBumperPreviews(id: string, templateId: string) {
return apiRequest<void>(`/admin/channels/${id}/bumper/templates/${templateId}/preview`, {
method: 'POST',
})
}
export function bumperPreviewPlaylistUrl(id: string, templateId: string) {
return `/api/admin/channels/${id}/bumper/templates/${templateId}/preview/index.m3u8`
export function bumperPreviewPlaylistUrl(id: string, templateId: string, variantId: string) {
return `/api/admin/channels/${id}/bumper/templates/${templateId}/preview/${variantId}/index.m3u8`
}
export type OverrideBody = {