mirror of
https://github.com/sakaljurgis/kodi-api-server.git
synced 2026-07-08 20:37:41 +00:00
refactor streamer to use stream providers (e.g. to plugin web torrent)
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddStreamProviderColumnToFilesTable1672820632908
|
||||
implements MigrationInterface
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`alter table files
|
||||
add stream_provider TEXT default 'fs' not null;`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`alter table files
|
||||
drop column stream_provider;`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -136,12 +136,12 @@ export class AllFilesService {
|
||||
if (isNaN(id)) {
|
||||
throw new NotFoundException(`id #${fileId} not correct`);
|
||||
}
|
||||
const filePath = await this.videoTitlesProvider.getFilePath(id);
|
||||
const fileEntity = await this.videoTitlesProvider.getFile(id);
|
||||
|
||||
if (filePath === null) {
|
||||
if (fileEntity === null) {
|
||||
throw new NotFoundException(`id #${fileId} not found`);
|
||||
}
|
||||
|
||||
return this.streamerFacade.streamFile(request, response, filePath);
|
||||
return this.streamerFacade.streamVideoFile(request, response, fileEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { FileEntity } from '../../../VideoFiles/Entity/file.entity';
|
||||
import { ReadStreamCreatable } from '../../Interface/read-stream-creatable.interface';
|
||||
import { MimeService } from '../../Mime/mime.service';
|
||||
import { Stats } from 'fs';
|
||||
import { stat } from 'fs/promises';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { ReadStreamCreatableFile } from './read-stream-creatable-file.dto';
|
||||
import { ReadStreamCreatableProviderInterface } from '../read-stream-creatable-provider.interface';
|
||||
import { StreamProviderEnum } from '../stream-provider.enum';
|
||||
|
||||
@Injectable()
|
||||
export class FsReadStreamCreatableProvider
|
||||
implements ReadStreamCreatableProviderInterface
|
||||
{
|
||||
constructor(private readonly mimeService: MimeService) {}
|
||||
|
||||
supports(fileEntity: FileEntity): boolean {
|
||||
return fileEntity.streamProvider === StreamProviderEnum.fs;
|
||||
}
|
||||
|
||||
async getReadStreamCreatable(
|
||||
fileEntity: FileEntity,
|
||||
): Promise<ReadStreamCreatable> {
|
||||
const filePath = fileEntity.path;
|
||||
const stats: Stats | false = await stat(filePath).catch(() => false);
|
||||
if (stats === false) {
|
||||
//todo - set the file to deleted, since no longer available. Events?
|
||||
throw new NotFoundException('file not found');
|
||||
}
|
||||
const mimeType = this.mimeService.getMime(filePath);
|
||||
|
||||
return new ReadStreamCreatableFile(filePath, stats.size, mimeType);
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -1,7 +1,9 @@
|
||||
import { ReadStreamCreatable } from '../Interface/read-stream-creatable.interface';
|
||||
import { createReadStream, ReadStream } from 'fs';
|
||||
import { ReadStreamOptions } from '../Interface/read-stream-options.interface';
|
||||
import { ReadStreamOptions } from '../../Interface/read-stream-options.interface';
|
||||
import { ReadStreamCreatable } from '../../Interface/read-stream-creatable.interface';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class ReadStreamCreatableFile implements ReadStreamCreatable {
|
||||
constructor(
|
||||
private readonly path: string,
|
||||
@@ -0,0 +1,7 @@
|
||||
import { FileEntity } from '../../VideoFiles/Entity/file.entity';
|
||||
import { ReadStreamCreatable } from '../Interface/read-stream-creatable.interface';
|
||||
|
||||
export interface ReadStreamCreatableProviderInterface {
|
||||
supports(fileEntity: FileEntity): boolean;
|
||||
getReadStreamCreatable(fileEntity: FileEntity): Promise<ReadStreamCreatable>;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ReadStreamCreatable } from '../Interface/read-stream-creatable.interface';
|
||||
import { FileEntity } from '../../VideoFiles/Entity/file.entity';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { ReadStreamCreatableProviderInterface } from './read-stream-creatable-provider.interface';
|
||||
|
||||
export const ReadStreamCreatableProviders = 'ReadStreamCreatableProviders';
|
||||
|
||||
@Injectable()
|
||||
export class ReadStreamCreatableProvider {
|
||||
constructor(
|
||||
@Inject(ReadStreamCreatableProviders)
|
||||
private providers: Array<ReadStreamCreatableProviderInterface>,
|
||||
) {}
|
||||
getReadStreamCreatable(fileEntity: FileEntity): Promise<ReadStreamCreatable> {
|
||||
for (const provider of this.providers) {
|
||||
if (provider.supports(fileEntity)) {
|
||||
return provider.getReadStreamCreatable(fileEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum StreamProviderEnum {
|
||||
fs = 'fs', //filesystem
|
||||
wt = 'wt', //web torrent
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { Stats } from 'fs';
|
||||
import { stat } from 'fs/promises';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { ReadStreamCreatableFile } from '../Dto/read-stream-creatable-file.dto';
|
||||
import { StreamerService } from './streamer.service';
|
||||
import { MimeService } from '../Mime/mime.service';
|
||||
|
||||
@Injectable()
|
||||
export class FileStreamerService {
|
||||
constructor(
|
||||
private readonly streamerService: StreamerService,
|
||||
private readonly mimeService: MimeService,
|
||||
) {}
|
||||
async streamFile(
|
||||
request: Request,
|
||||
response: Response,
|
||||
filePath: string,
|
||||
): Promise<void> {
|
||||
const stats: Stats | false = await stat(filePath).catch(() => false);
|
||||
if (stats === false) {
|
||||
//todo - set the file to deleted, since no longer available. Events?
|
||||
throw new NotFoundException('file not found');
|
||||
}
|
||||
const mimeType = this.mimeService.getMime(filePath);
|
||||
const streamCreatable = new ReadStreamCreatableFile(
|
||||
filePath,
|
||||
stats.size,
|
||||
mimeType,
|
||||
);
|
||||
|
||||
return this.streamerService.stream(request, response, streamCreatable);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ReadStreamCreatable } from '../Interface/read-stream-creatable.interface';
|
||||
import { RequestRangeService } from '../RequestRange/request-range.service';
|
||||
import { MimeService } from '../Mime/mime.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
import { ResponseHeadersService } from '../ResponseHeadersService/response-headers.service';
|
||||
@@ -10,7 +9,6 @@ import RequestRangeBytes from '../RequestRange/request-range-bytes.class';
|
||||
@Injectable()
|
||||
export class StreamerService {
|
||||
constructor(
|
||||
private readonly mimeService: MimeService,
|
||||
private readonly requestRangeService: RequestRangeService,
|
||||
private readonly responseHeadersService: ResponseHeadersService,
|
||||
private readonly responseSender: ResponseSenderService,
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
import { FileStreamerService } from './StreamerService/file-streamer.service';
|
||||
import { FileEntity } from '../VideoFiles/Entity/file.entity';
|
||||
import { ReadStreamCreatableProvider } from './ReadStreamProvider/read-stream-creatable.provider';
|
||||
import { StreamerService } from './StreamerService/streamer.service';
|
||||
|
||||
@Injectable()
|
||||
export class StreamerFacade {
|
||||
constructor(private readonly fileStreamerService: FileStreamerService) {}
|
||||
constructor(
|
||||
private readonly streamerService: StreamerService,
|
||||
private readonly readStreamCreatableProvider: ReadStreamCreatableProvider,
|
||||
) {}
|
||||
|
||||
async streamFile(
|
||||
async streamVideoFile(
|
||||
request: Request,
|
||||
response: Response,
|
||||
filePath: string,
|
||||
fileEntity: FileEntity,
|
||||
): Promise<void> {
|
||||
return this.fileStreamerService.streamFile(request, response, filePath);
|
||||
const readStreamCreatable =
|
||||
await this.readStreamCreatableProvider.getReadStreamCreatable(fileEntity);
|
||||
|
||||
return this.streamerService.stream(request, response, readStreamCreatable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,13 @@ import { StreamerFacade } from './streamer.facade';
|
||||
import { MimeService } from './Mime/mime.service';
|
||||
import { RequestRangeService } from './RequestRange/request-range.service';
|
||||
import { StreamerService } from './StreamerService/streamer.service';
|
||||
import { FileStreamerService } from './StreamerService/file-streamer.service';
|
||||
import { ResponseHeadersService } from './ResponseHeadersService/response-headers.service';
|
||||
import { ResponseSenderService } from './ResponseSender/response-sender.service';
|
||||
import {
|
||||
ReadStreamCreatableProvider,
|
||||
ReadStreamCreatableProviders,
|
||||
} from './ReadStreamProvider/read-stream-creatable.provider';
|
||||
import { FsReadStreamCreatableProvider } from './ReadStreamProvider/FileSystem/fs-read-stream-creatable.provider';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
@@ -14,9 +18,15 @@ import { ResponseSenderService } from './ResponseSender/response-sender.service'
|
||||
RequestRangeService,
|
||||
ResponseHeadersService,
|
||||
ResponseSenderService,
|
||||
FileStreamerService,
|
||||
StreamerService,
|
||||
StreamerFacade,
|
||||
ReadStreamCreatableProvider,
|
||||
FsReadStreamCreatableProvider,
|
||||
{
|
||||
provide: ReadStreamCreatableProviders,
|
||||
useFactory: (...providers) => providers,
|
||||
inject: [FsReadStreamCreatableProvider],
|
||||
},
|
||||
],
|
||||
exports: [StreamerFacade],
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||
import { TitleEntity } from './title.entity';
|
||||
import { StreamProviderEnum } from '../../Streamer/ReadStreamProvider/stream-provider.enum';
|
||||
|
||||
@Entity('files')
|
||||
export class FileEntity {
|
||||
@@ -36,4 +37,7 @@ export class FileEntity {
|
||||
|
||||
@Column()
|
||||
duration: number;
|
||||
|
||||
@Column({ name: 'stream_provider' })
|
||||
streamProvider: StreamProviderEnum;
|
||||
}
|
||||
|
||||
@@ -32,15 +32,9 @@ export class VideoFilesProvider {
|
||||
.catch(() => null);
|
||||
}
|
||||
|
||||
async getFilePath(fileId: number): Promise<string | null> {
|
||||
const entity = await this.fileRepository
|
||||
async getFile(fileId: number): Promise<FileEntity | null> {
|
||||
return this.fileRepository
|
||||
.findOne({ where: { id: fileId } })
|
||||
.catch(() => null);
|
||||
|
||||
if (entity === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return entity.path;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user