@aiozstream/nodejs-client
Version:
AIOZ stream nodejs API client
255 lines (223 loc) • 7.05 kB
text/typescript
/**
* @aiozstream/nodejs-client
* Aioz Stream Service
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated.
* Do not edit the class manually.
*/
import HttpClient from './HttpClient';
import ApiKeyApi from './api/ApiKeyApi';
import PlayersApi from './api/PlayersApi';
import PlaylistApi from './api/PlaylistApi';
import VideoApi from './api/VideoApi';
import VideoChapterApi from './api/VideoChapterApi';
import WebhookApi from './api/WebhookApi';
import { createReadStream, existsSync, statSync } from 'fs';
import UploadProgressEvent from './model/UploadProgressEvent';
import { QueryOptions } from './HttpClient';
import FormData from 'form-data';
import path from 'path';
import crypto from 'crypto';
const PRODUCTION_BASE_URI = 'https://api.aiozstream.network/api';
const DEFAULT_CHUNK_SIZE = 50 * 1024 * 1024;
const MIN_CHUNK_SIZE = 5 * 1024 * 1024;
const MAX_CHUNK_SIZE = 128 * 1024 * 1024;
class StreamClient {
private httpClient: HttpClient;
private _apiKey: ApiKeyApi;
private _players: PlayersApi;
private _playlist: PlaylistApi;
private _video: VideoApi;
private _videoChapter: VideoChapterApi;
private _webhook: WebhookApi;
constructor(params: {
publicKey?: string;
secretKey?: string;
bearerToken?: string;
baseUri?: string;
chunkSize?: number;
applicationName?: string;
applicationVersion?: string;
sdkName?: string;
sdkVersion?: string;
}) {
if (
params.chunkSize &&
(params.chunkSize < MIN_CHUNK_SIZE || params.chunkSize > MAX_CHUNK_SIZE)
) {
throw new Error(
'Invalid chunk size value. Must be greater than ' +
MIN_CHUNK_SIZE +
' bytes and lower than ' +
MAX_CHUNK_SIZE +
' bytes.'
);
}
this.validateOrigin(
'application',
params.applicationName,
params.applicationVersion
);
this.validateOrigin('sdk', params.sdkName, params.sdkVersion);
this.httpClient = new HttpClient({
...params,
baseUri: params.baseUri || PRODUCTION_BASE_URI,
chunkSize: params.chunkSize || DEFAULT_CHUNK_SIZE,
});
this._apiKey = new ApiKeyApi(this.httpClient);
this._players = new PlayersApi(this.httpClient);
this._playlist = new PlaylistApi(this.httpClient);
this._video = new VideoApi(this.httpClient);
this._videoChapter = new VideoChapterApi(this.httpClient);
this._webhook = new WebhookApi(this.httpClient);
}
/**
* Get an ApiKeyApi instance
* @return ApiKeyApi
*/
public get apiKey(): ApiKeyApi {
return this._apiKey;
}
/**
* Get an PlayersApi instance
* @return PlayersApi
*/
public get players(): PlayersApi {
return this._players;
}
/**
* Get an PlaylistApi instance
* @return PlaylistApi
*/
public get playlist(): PlaylistApi {
return this._playlist;
}
/**
* Get an VideoApi instance
* @return VideoApi
*/
public get video(): VideoApi {
return this._video;
}
/**
* Get an VideoChapterApi instance
* @return VideoChapterApi
*/
public get videoChapter(): VideoChapterApi {
return this._videoChapter;
}
/**
* Get an WebhookApi instance
* @return WebhookApi
*/
public get webhook(): WebhookApi {
return this._webhook;
}
private validateOrigin(type: string, name?: string, version?: string) {
if (name && !version) {
throw new Error(
`${type} version is mandatory when ${type} name is set.'`
);
} else if (!name && version) {
throw new Error(
`${type} name is mandatory when ${type} version is set.'`
);
} else if (name && version) {
if (!/^[\w-]{1,50}$/.test(name)) {
throw new Error(
`Invalid ${type} name value. Allowed characters: A-Z, a-z, 0-9, '-', '_'. Max length: 50.`
);
}
if (!/^\d{1,3}(\.\d{1,3}(\.\d{1,3})?)?$/.test(version)) {
throw new Error(
`Invalid ${type} version value. The version should match the xxx[.yyy][.zzz] pattern.`
);
}
}
}
public async uploadVideo(
id: string,
file: string,
progressListener?: (event: UploadProgressEvent) => void
): Promise<void> {
if (!id || typeof id !== 'string') {
throw new Error('Invalid id parameter');
}
if (!existsSync(file)) {
throw new Error(`${file} must be a readable source file`);
}
const fileStats = statSync(file);
const length = fileStats.size;
if (length <= 0) {
throw new Error(`${file} is empty`);
}
const localVarPath = `videos/${encodeURIComponent(id)}/part`;
const chunkSize = this.httpClient.getChunkSize();
const filename = path.basename(file);
const uploadChunk = async (
start: number,
end: number,
chunkNumber: number,
totalChunks: number
): Promise<void> => {
const chunkFormData = new FormData();
const fileStream = createReadStream(file, { start, end });
const hash = crypto.createHash('md5');
for await (const chunk of fileStream) {
hash.update(chunk);
}
const md5Hash = hash.digest('hex');
fileStream.destroy();
const uploadStream = createReadStream(file, { start, end });
chunkFormData.append('file', uploadStream, filename);
chunkFormData.append('index', (chunkNumber + 1).toString());
chunkFormData.append('hash', md5Hash);
const queryParams: QueryOptions = {
method: 'POST',
body: chunkFormData,
headers: {
'Content-Range': `bytes ${start}-${end}/${length}`,
},
};
if (progressListener) {
queryParams.onUploadProgress = (progress) => {
progressListener({
chunksCount: totalChunks,
currentChunk: chunkNumber,
currentChunkUploadedBytes: progress.loaded,
currentChunkTotalBytes: progress.total || 0,
uploadedBytes: Math.min(
length,
chunkNumber * chunkSize + progress.loaded
),
totalBytes: length,
});
};
}
await this.httpClient.call(localVarPath, queryParams);
};
if (chunkSize > length) {
await uploadChunk(0, length - 1, 0, 1);
} else {
const chunksCount = Math.ceil(length / chunkSize);
const uploadPromises: Promise<void>[] = [];
for (let chunkNumber = 0; chunkNumber < chunksCount; chunkNumber++) {
const start = chunkNumber * chunkSize;
const end = Math.min(start + chunkSize - 1, length - 1);
uploadPromises.push(uploadChunk(start, end, chunkNumber, chunksCount));
if (uploadPromises.length >= 5 || chunkNumber === chunksCount - 1) {
await Promise.all(uploadPromises);
uploadPromises.length = 0;
}
}
}
const uploadCompleteLocalVarPath = `videos/${encodeURIComponent(
id
)}/complete`;
await this.httpClient.call(uploadCompleteLocalVarPath, { method: 'GET' });
}
}
export = StreamClient;