Fix #6 stream video requests by range
This commit is contained in:
parent
2268d8a51b
commit
8a75d7d677
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-public-proxy",
|
"name": "immich-public-proxy",
|
||||||
"version": "1.3.7",
|
"version": "1.3.8",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "ts-node src/index.ts",
|
"dev": "ts-node src/index.ts",
|
||||||
"build": "npx tsc",
|
"build": "npx tsc",
|
||||||
|
@ -12,7 +12,7 @@ class Immich {
|
|||||||
*/
|
*/
|
||||||
async request (endpoint: string) {
|
async request (endpoint: string) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(process.env.IMMICH_URL + '/api' + endpoint)
|
const res = await fetch(this.apiUrl() + endpoint)
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
const contentType = res.headers.get('Content-Type') || ''
|
const contentType = res.headers.get('Content-Type') || ''
|
||||||
if (contentType.includes('application/json')) {
|
if (contentType.includes('application/json')) {
|
||||||
@ -29,6 +29,10 @@ class Immich {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apiUrl () {
|
||||||
|
return process.env.IMMICH_URL + '/api'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request for a shared link `key`. This is the main function which
|
* Handle an incoming request for a shared link `key`. This is the main function which
|
||||||
* communicates with Immich and returns the output back to the visitor.
|
* communicates with Immich and returns the output back to the visitor.
|
||||||
@ -72,7 +76,7 @@ class Immich {
|
|||||||
const asset = link.assets[0]
|
const asset = link.assets[0]
|
||||||
if (asset.type === AssetType.image && !getConfigOption('ipp.singleImageGallery')) {
|
if (asset.type === AssetType.image && !getConfigOption('ipp.singleImageGallery')) {
|
||||||
// For photos, output the image directly unless configured to show a gallery
|
// For photos, output the image directly unless configured to show a gallery
|
||||||
await render.assetBuffer(res, link.assets[0], request.size)
|
await render.assetBuffer(request, res, link.assets[0], request.size)
|
||||||
} else {
|
} else {
|
||||||
// Show a gallery page
|
// Show a gallery page
|
||||||
const openItem = getConfigOption('ipp.singleItemAutoOpen', true) ? 1 : 0
|
const openItem = getConfigOption('ipp.singleItemAutoOpen', true) ? 1 : 0
|
||||||
|
@ -54,12 +54,13 @@ app.get('/:type(photo|video)/:key/:id', async (req, res) => {
|
|||||||
}
|
}
|
||||||
// Check if the key is a valid share link
|
// Check if the key is a valid share link
|
||||||
const sharedLink = (await immich.getShareByKey(req.params.key, password))?.link
|
const sharedLink = (await immich.getShareByKey(req.params.key, password))?.link
|
||||||
|
const request = { key: req.params.key, range: req.headers.range || '' }
|
||||||
if (sharedLink?.assets.length) {
|
if (sharedLink?.assets.length) {
|
||||||
// Check that the requested asset exists in this share
|
// Check that the requested asset exists in this share
|
||||||
const asset = sharedLink.assets.find(x => x.id === req.params.id)
|
const asset = sharedLink.assets.find(x => x.id === req.params.id)
|
||||||
if (asset) {
|
if (asset) {
|
||||||
asset.type = req.params.type === 'video' ? AssetType.video : AssetType.image
|
asset.type = req.params.type === 'video' ? AssetType.video : AssetType.image
|
||||||
render.assetBuffer(res, asset, getSize(req)).then()
|
render.assetBuffer(request, res, asset, getSize(req)).then()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import immich from './immich'
|
import immich from './immich'
|
||||||
import { Response } from 'express-serve-static-core'
|
import { Response } from 'express-serve-static-core'
|
||||||
import { Asset, AssetType, ImageSize, SharedLink } from './types'
|
import { Asset, AssetType, ImageSize, IncomingShareRequest, SharedLink } from './types'
|
||||||
import { getConfigOption } from './functions'
|
import { getConfigOption } from './functions'
|
||||||
|
|
||||||
class Render {
|
class Render {
|
||||||
@ -10,13 +10,44 @@ class Render {
|
|||||||
this.lgConfig = getConfigOption('lightGallery', {})
|
this.lgConfig = getConfigOption('lightGallery', {})
|
||||||
}
|
}
|
||||||
|
|
||||||
async assetBuffer (res: Response, asset: Asset, size?: ImageSize) {
|
/**
|
||||||
const data = await immich.getAssetBuffer(asset, size)
|
* Stream data from Immich back to the client
|
||||||
if (data) {
|
*/
|
||||||
for (const header of ['content-type', 'content-length']) {
|
async assetBuffer (req: IncomingShareRequest, res: Response, asset: Asset, size?: ImageSize) {
|
||||||
res.set(header, data.headers.get(header))
|
// Prepare the request
|
||||||
}
|
size = size === ImageSize.thumbnail ? ImageSize.thumbnail : ImageSize.original
|
||||||
res.send(Buffer.from(await data.arrayBuffer()))
|
const subpath = asset.type === AssetType.video ? '/video/playback' : '/' + size
|
||||||
|
const headers = { range: '' }
|
||||||
|
if (asset.type === AssetType.video && req.range) {
|
||||||
|
const start = req.range.replace(/bytes=/, '').split('-')[0]
|
||||||
|
const startByte = parseInt(start, 10) || 0
|
||||||
|
const endByte = startByte + 2499999
|
||||||
|
headers.range = `bytes=${startByte}-${endByte}`
|
||||||
|
}
|
||||||
|
const url = immich.buildUrl(immich.apiUrl() + '/assets/' + encodeURIComponent(asset.id) + subpath, {
|
||||||
|
key: asset.key,
|
||||||
|
password: asset.password
|
||||||
|
})
|
||||||
|
const data = await fetch(url, { headers })
|
||||||
|
|
||||||
|
// Return the response to the client
|
||||||
|
if (data.status >= 200 && data.status < 300) {
|
||||||
|
// Populate the response headers
|
||||||
|
['content-type', 'content-length', 'last-modified', 'etag', 'content-range']
|
||||||
|
.forEach(header => {
|
||||||
|
const value = data.headers.get(header)
|
||||||
|
if (value) res.setHeader(header, value)
|
||||||
|
})
|
||||||
|
if (headers.range) res.status(206) // Partial Content
|
||||||
|
// Return the body
|
||||||
|
await data.body?.pipeTo(
|
||||||
|
new WritableStream({
|
||||||
|
write (chunk) {
|
||||||
|
res.write(chunk)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
res.end()
|
||||||
} else {
|
} else {
|
||||||
res.status(404).send()
|
res.status(404).send()
|
||||||
}
|
}
|
||||||
|
@ -39,4 +39,5 @@ export interface IncomingShareRequest {
|
|||||||
key: string;
|
key: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
size?: ImageSize;
|
size?: ImageSize;
|
||||||
|
range?: string;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user