mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-23 03:49:49 +00:00
feat: hls video support (#6542)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
parent
80dc1f5aed
commit
4eee28641f
@ -1586,6 +1586,9 @@ dependencies:
|
||||
highlight.js:
|
||||
specifier: ~11.8.0
|
||||
version: 11.8.0
|
||||
hls.js:
|
||||
specifier: ^1.5.15
|
||||
version: 1.5.15
|
||||
html-to-text:
|
||||
specifier: ^9.0.3
|
||||
version: 9.0.5
|
||||
@ -15598,6 +15601,10 @@ packages:
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
|
||||
/hls.js@1.5.15:
|
||||
resolution: {integrity: sha512-6cD7xN6bycBHaXz2WyPIaHn/iXFizE5au2yvY5q9aC4wfihxAr16C9fUy4nxh2a3wOw0fEgLRa9dN6wsYjlpNg==}
|
||||
dev: false
|
||||
|
||||
/hogan.js@3.0.2:
|
||||
resolution: {integrity: sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==}
|
||||
hasBin: true
|
||||
@ -27291,7 +27298,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/lead-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-TJVh5S1o+GvRWeeNWwXveGOpMtsQTR0n5RMGjK3kXsuAUYCQRPzgMh3noJxe2n4vvijK8IUNjAR9+AjeSPo5kw==, tarball: file:projects/lead-resources.tgz}
|
||||
resolution: {integrity: sha512-xg8Fq55+BYSO+pwIkFTJFDJGPu1CWGB8CiZ64+J2jqzbAHkRaiOCP0u3R4lOw/z6k1tnqhL0m2bvV9pCUCYTHA==, tarball: file:projects/lead-resources.tgz}
|
||||
id: file:projects/lead-resources.tgz
|
||||
name: '@rush-temp/lead-resources'
|
||||
version: 0.0.0
|
||||
@ -30482,6 +30489,7 @@ packages:
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
eslint-plugin-svelte: 2.35.1(eslint@8.56.0)(svelte@4.2.12)(ts-node@10.9.2)
|
||||
fast-equals: 5.0.1
|
||||
hls.js: 1.5.15
|
||||
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
|
||||
png-chunks-extract: 1.0.0
|
||||
prettier: 3.2.5
|
||||
@ -34795,7 +34803,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/tool.tgz(bufferutil@4.0.8)(utf-8-validate@6.0.4):
|
||||
resolution: {integrity: sha512-LwQbmBaSOZ5IKwCHz2mULcIuEr9rZ2b/7tqUGICHCawUzexUlQVxv2Yt0oFf2aZu83Sittt7dZwnN3sXHX9t9g==, tarball: file:projects/tool.tgz}
|
||||
resolution: {integrity: sha512-sZH5yB7zg/kTpuIhLSqPYh0wFgw4aOpsriMq4wad8ZHRzlHASseyJAbEylIP8ltfPbFFN4Yy1nXaUOXS49anHg==, tarball: file:projects/tool.tgz}
|
||||
id: file:projects/tool.tgz
|
||||
name: '@rush-temp/tool'
|
||||
version: 0.0.0
|
||||
@ -35311,7 +35319,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/view-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-l/K7osn3HZ3KIFeCyBe+rxQGUxvLvM+35if2HgylqgbWtD10Gk/rR+vuW1L54o8hT4ADMkbhvBW7VHE19isd+w==, tarball: file:projects/view-resources.tgz}
|
||||
resolution: {integrity: sha512-g6op8hiY1zLsms7Sab4cAs29Ucbk6r20mx9hkZrhxn70uPW/VCLS+JW67cfWf85SyMwMloWuvY6ujfQfwNuScw==, tarball: file:projects/view-resources.tgz}
|
||||
id: file:projects/view-resources.tgz
|
||||
name: '@rush-temp/view-resources'
|
||||
version: 0.0.0
|
||||
@ -35326,6 +35334,7 @@ packages:
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
eslint-plugin-svelte: 2.35.1(eslint@8.56.0)(svelte@4.2.12)(ts-node@10.9.2)
|
||||
fast-equals: 5.0.1
|
||||
hls.js: 1.5.15
|
||||
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
|
||||
prettier: 3.2.5
|
||||
prettier-plugin-svelte: 3.2.2(prettier@3.2.5)(svelte@4.2.12)
|
||||
|
@ -6,27 +6,56 @@ import { getFileUrl, getCurrentWorkspaceId } from './file'
|
||||
import presentation from './plugin'
|
||||
|
||||
export interface PreviewConfig {
|
||||
previewUrl: string
|
||||
image: string
|
||||
video: string
|
||||
}
|
||||
|
||||
const defaultPreview = (): string => `/files/${getCurrentWorkspaceId()}?file=:blobId&size=:size`
|
||||
export interface VideoMeta {
|
||||
status: 'ready' | 'error' | 'inprogress' | 'queued' | 'downloading' | 'pendingupload'
|
||||
thumbnail: string
|
||||
hls: string
|
||||
}
|
||||
|
||||
const defaultImagePreview = (): string => `/files/${getCurrentWorkspaceId()}?file=:blobId&size=:size`
|
||||
|
||||
/**
|
||||
*
|
||||
* PREVIEW_CONFIG env variable format.
|
||||
* previewUrl - an Url with :workspace, :blobId, :downloadFile, :size placeholders, they will be replaced in UI with an appropriate blob values.
|
||||
* - image - an Url with :workspace, :blobId, :downloadFile, :size placeholders.
|
||||
* - video - an Url with :workspace, :blobId placeholders.
|
||||
*/
|
||||
export function parsePreviewConfig (config?: string): PreviewConfig | undefined {
|
||||
if (config === undefined) {
|
||||
return
|
||||
}
|
||||
return { previewUrl: config }
|
||||
|
||||
const previewConfig = { image: defaultImagePreview(), video: '' }
|
||||
|
||||
const configs = config.split(';')
|
||||
for (const c of configs) {
|
||||
if (c.includes('|')) {
|
||||
const [key, value] = c.split('|')
|
||||
if (key === 'image') {
|
||||
previewConfig.image = value
|
||||
} else if (key === 'video') {
|
||||
previewConfig.video = value
|
||||
} else {
|
||||
throw new Error(`Unknown preview config key: ${key}`)
|
||||
}
|
||||
} else {
|
||||
// fallback to image-only config for compatibility
|
||||
previewConfig.image = c
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze(previewConfig)
|
||||
}
|
||||
|
||||
export function getPreviewConfig (): PreviewConfig {
|
||||
return (
|
||||
(getMetadata(presentation.metadata.PreviewConfig) as PreviewConfig) ?? {
|
||||
previewUrl: defaultPreview()
|
||||
image: defaultImagePreview(),
|
||||
video: ''
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -58,7 +87,7 @@ function blobToSrcSet (cfg: PreviewConfig, blob: Ref<Blob>, width: number | unde
|
||||
return ''
|
||||
}
|
||||
|
||||
let url = cfg.previewUrl.replaceAll(':workspace', encodeURIComponent(getCurrentWorkspaceId()))
|
||||
let url = cfg.image.replaceAll(':workspace', encodeURIComponent(getCurrentWorkspaceId()))
|
||||
const downloadUrl = getFileUrl(blob)
|
||||
|
||||
const frontUrl = getMetadata(presentation.metadata.FrontUrl) ?? window.location.origin
|
||||
@ -89,3 +118,25 @@ function blobToSrcSet (cfg: PreviewConfig, blob: Ref<Blob>, width: number | unde
|
||||
export function getFileSrcSet (_blob: Ref<Blob>, width?: number): string {
|
||||
return blobToSrcSet(getPreviewConfig(), _blob, width)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function getVideoMeta (file: string, filename?: string): Promise<VideoMeta | undefined> {
|
||||
const cfg = getPreviewConfig()
|
||||
|
||||
const url = cfg.video
|
||||
.replaceAll(':workspace', encodeURIComponent(getCurrentWorkspaceId()))
|
||||
.replaceAll(':blobId', encodeURIComponent(file))
|
||||
|
||||
if (url === '') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
if (response.ok) {
|
||||
return (await response.json()) as VideoMeta
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
@ -57,6 +57,7 @@
|
||||
"@hcengineering/text-editor-resources": "^0.6.0",
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"@hcengineering/query": "^0.6.12",
|
||||
"fast-equals": "^5.0.1"
|
||||
"fast-equals": "^5.0.1",
|
||||
"hls.js": "^1.5.15"
|
||||
}
|
||||
}
|
||||
|
@ -14,20 +14,46 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { type Blob, type Ref } from '@hcengineering/core'
|
||||
import { getFileUrl, type BlobMetadata } from '@hcengineering/presentation'
|
||||
import { getFileUrl, getVideoMeta, type BlobMetadata } from '@hcengineering/presentation'
|
||||
import HLS from 'hls.js'
|
||||
|
||||
export let value: Ref<Blob>
|
||||
export let name: string
|
||||
export let metadata: BlobMetadata | undefined
|
||||
export let fit: boolean = false
|
||||
|
||||
let video: HTMLVideoElement
|
||||
|
||||
async function fetchVideoMeta (value: Ref<Blob>, name: string): Promise<void> {
|
||||
const src = getFileUrl(value, name)
|
||||
const meta = await getVideoMeta(value, name)
|
||||
if (meta != null && meta.status === 'ready' && HLS.isSupported()) {
|
||||
const hls = new HLS()
|
||||
hls.loadSource(meta.hls)
|
||||
hls.attachMedia(video)
|
||||
video.poster = meta.thumbnail
|
||||
} else {
|
||||
video.src = src
|
||||
}
|
||||
}
|
||||
|
||||
$: aspectRatio =
|
||||
metadata?.originalWidth && metadata?.originalHeight
|
||||
? `${metadata.originalWidth} / ${metadata.originalHeight}`
|
||||
: '16 / 9'
|
||||
$: maxWidth = metadata?.originalWidth ? `min(${metadata.originalWidth}px, 100%)` : undefined
|
||||
$: maxHeight = metadata?.originalHeight ? `min(${metadata.originalHeight}px, 80vh)` : undefined
|
||||
|
||||
$: src = getFileUrl(value, name)
|
||||
$: void fetchVideoMeta(value, name)
|
||||
</script>
|
||||
|
||||
<video style:max-width={fit ? '100%' : maxWidth} style:max-height={fit ? '100%' : maxHeight} controls preload={'auto'}>
|
||||
<source {src} />
|
||||
<video
|
||||
bind:this={video}
|
||||
width="100%"
|
||||
style:aspect-ratio={aspectRatio}
|
||||
style:max-width={fit ? '100%' : maxWidth}
|
||||
style:max-height={fit ? '100%' : maxHeight}
|
||||
controls
|
||||
preload={'auto'}
|
||||
>
|
||||
<track kind="captions" label={name} />
|
||||
</video>
|
||||
|
@ -23,15 +23,12 @@ Front service is suited to deliver application bundles and resource assets, it a
|
||||
|
||||
PREVIEW_CONFIG env variable format.
|
||||
|
||||
A `;` separated list of triples, providerName|previewUrl|supportedFormats.
|
||||
A `;` separated list of pairs, mediaType|previewUrl.
|
||||
|
||||
- providerName - a provider name should be same as in Storage configuration.
|
||||
It coult be empty and it will match by content types.
|
||||
- previewUrl - an Url with :workspace, :blobId, :downloadFile, :size placeholders, they will be replaced in UI with an appropriate blob values.
|
||||
- supportedFormats - a `,` separated list of file extensions.
|
||||
- contentTypes - a ',' separated list of content type patterns.
|
||||
* mediaType - a type of media, image or video.
|
||||
* previewUrl - an Url with :workspace, :blobId, :downloadFile, :size placeholders, they will be replaced in UI with an appropriate blob values.
|
||||
|
||||
PREVIEW_CONFIG=https://front.hc.engineering/files/:workspace/api/preview/?width=:size&image=:downloadFile
|
||||
PREVIEW_CONFIG=image|https://front.hc.engineering/files/:workspace/api/preview/?width=:size&image=:downloadFile
|
||||
|
||||
## Variables
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user