From e092c3ae80461fc50a0f2812bb95176953c8f7e4 Mon Sep 17 00:00:00 2001 From: Denis Tingaikin <denis.tingajkin@xored.com> Date: Mon, 17 Mar 2025 13:44:37 +0300 Subject: [PATCH] use huly stream instedof cf stream (#8245) Signed-off-by: denis-tingaikin <denis.tingajkin@xored.com> --- dev/docker-compose.yaml | 3 +- packages/presentation/src/preview.ts | 9 ++-- .../components/AttachmentVideoPreview.svelte | 4 +- .../src/components/viewer/VideoViewer.svelte | 4 +- services/datalake/pod-datalake/src/config.ts | 2 + .../pod-datalake/src/handlers/blob.ts | 5 +++ .../pod-datalake/src/handlers/multipart.ts | 6 +++ .../pod-datalake/src/handlers/video.ts | 44 +++++++++++++++++++ 8 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 services/datalake/pod-datalake/src/handlers/video.ts diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml index 1d9c99020f..ec957b7669 100644 --- a/dev/docker-compose.yaml +++ b/dev/docker-compose.yaml @@ -1,12 +1,13 @@ services: stream: - image: 'hardcoreeng/huly-stream' + image: 'hardcoreeng/stream' extra_hosts: - 'huly.local:host-gateway' container_name: stream environment: - STREAM_ENDPOINT_URL=s3://huly.local:9000 - STREAM_INSECURE=true + - STREAM_SERVER_SECRET=secret - AWS_ACCESS_KEY_ID=minioadmin - AWS_SECRET_ACCESS_KEY=minioadmin ports: diff --git a/packages/presentation/src/preview.ts b/packages/presentation/src/preview.ts index b91827c2bc..d9b7d7da71 100644 --- a/packages/presentation/src/preview.ts +++ b/packages/presentation/src/preview.ts @@ -11,9 +11,12 @@ export interface PreviewConfig { } export interface VideoMeta { - status: 'ready' | 'error' | 'inprogress' | 'queued' | 'downloading' | 'pendingupload' - thumbnail: string - hls: string + hls?: HLSMeta +} + +export interface HLSMeta { + thumbnail?: string + source?: string } const defaultImagePreview = (): string => `/files/${getCurrentWorkspaceUuid()}?file=:blobId&size=:size` diff --git a/plugins/attachment-resources/src/components/AttachmentVideoPreview.svelte b/plugins/attachment-resources/src/components/AttachmentVideoPreview.svelte index f97936a96d..a6d4d2c87c 100644 --- a/plugins/attachment-resources/src/components/AttachmentVideoPreview.svelte +++ b/plugins/attachment-resources/src/components/AttachmentVideoPreview.svelte @@ -64,8 +64,8 @@ {#await getVideoMeta(value.file, value.name) then meta} {@const src = getFileUrl(value.file, value.name)} - {#if meta && meta.status === 'ready'} - <HlsVideo {src} {preload} hlsSrc={meta.hls} hlsThumbnail={meta.thumbnail} name={value.name} /> + {#if meta?.hls?.source !== undefined} + <HlsVideo {src} {preload} hlsSrc={meta.hls.source} hlsThumbnail={meta.hls.thumbnail} name={value.name} /> {:else} <Video {src} {preload} name={value.name} /> {/if} diff --git a/plugins/view-resources/src/components/viewer/VideoViewer.svelte b/plugins/view-resources/src/components/viewer/VideoViewer.svelte index 9e053518c6..cb06898314 100644 --- a/plugins/view-resources/src/components/viewer/VideoViewer.svelte +++ b/plugins/view-resources/src/components/viewer/VideoViewer.svelte @@ -41,9 +41,9 @@ <HlsVideo {src} hlsSrc={src} preload={true} /> {:else} {#await getVideoMeta(value, name) then meta} - {#if meta !== undefined && meta.status === 'ready'} + {#if meta?.hls?.source !== undefined} {@const src = getFileUrl(value, name)} - <HlsVideo {src} {name} hlsSrc={meta.hls} hlsThumbnail={meta.thumbnail} preload={false} /> + <HlsVideo {src} {name} hlsSrc={meta.hls.source} hlsThumbnail={meta.hls.thumbnail} preload={false} /> {:else} {@const src = getFileUrl(value, name)} <Video {src} {name} /> diff --git a/services/datalake/pod-datalake/src/config.ts b/services/datalake/pod-datalake/src/config.ts index 89c89c7be8..e310e80f46 100644 --- a/services/datalake/pod-datalake/src/config.ts +++ b/services/datalake/pod-datalake/src/config.ts @@ -26,6 +26,7 @@ export interface Config { Port: number Secret: string AccountsUrl: string + StreamUrl?: string DbUrl: string Buckets: BucketConfig[] } @@ -75,6 +76,7 @@ const config: Config = (() => { Secret: process.env.SECRET, AccountsUrl: process.env.ACCOUNTS_URL, DbUrl: process.env.DB_URL, + StreamUrl: process.env.STREAM_URL, Buckets: parseBucketsConfig(process.env.BUCKETS) } diff --git a/services/datalake/pod-datalake/src/handlers/blob.ts b/services/datalake/pod-datalake/src/handlers/blob.ts index 5def00c4ce..c3c1338fbd 100644 --- a/services/datalake/pod-datalake/src/handlers/blob.ts +++ b/services/datalake/pod-datalake/src/handlers/blob.ts @@ -18,6 +18,7 @@ import { type Request, type Response } from 'express' import { UploadedFile } from 'express-fileupload' import fs from 'fs' +import { requestHLS } from './video' import { cacheControl } from '../const' import { type Datalake } from '../datalake' import { getBufferSha256, getStreamSha256 } from '../hash' @@ -223,6 +224,10 @@ export async function handleUploadFormData ( lastModified: Date.now() }) + if (contentType.startsWith('video/')) { + void requestHLS(ctx, workspace, name) + } + return { key, metadata } } catch (err: any) { const error = err instanceof Error ? err.message : String(err) diff --git a/services/datalake/pod-datalake/src/handlers/multipart.ts b/services/datalake/pod-datalake/src/handlers/multipart.ts index cd54029d85..4bde568c93 100644 --- a/services/datalake/pod-datalake/src/handlers/multipart.ts +++ b/services/datalake/pod-datalake/src/handlers/multipart.ts @@ -17,6 +17,7 @@ import { MeasureContext } from '@hcengineering/core' import { type Request, type Response } from 'express' import { cacheControl } from '../const' import { Datalake } from '../datalake' +import { requestHLS } from './video' export interface MultipartUpload { key: string @@ -120,5 +121,10 @@ export async function handleMultipartUploadAbort ( const { bucket } = await datalake.selectStorage(ctx, workspace) await bucket.abortMultipartUpload(ctx, name, { uploadId }) + const contentType = req.headers['content-type'] ?? 'application/octet-stream' + if (contentType.startsWith('video/')) { + void requestHLS(ctx, workspace, name) + } + res.status(204).send() } diff --git a/services/datalake/pod-datalake/src/handlers/video.ts b/services/datalake/pod-datalake/src/handlers/video.ts new file mode 100644 index 0000000000..1192916462 --- /dev/null +++ b/services/datalake/pod-datalake/src/handlers/video.ts @@ -0,0 +1,44 @@ +// +// Copyright © 2025 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { MeasureContext } from '@hcengineering/core' +import config from '../config' + +interface StreamRequest { + source: string + format: string + workspace: string + metadata?: Record<string, string> +} + +export async function requestHLS (ctx: MeasureContext, workspace: string, name: string): Promise<void> { + if (config.StreamUrl === undefined) { + return + } + const streamReq: StreamRequest = { format: 'hls', source: name, workspace } + + const request = new Request(config.StreamUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(streamReq) + }) + + const resp = await fetch(request) + if (!resp.ok) { + ctx.error(resp.statusText) + } +}