mirror of
https://github.com/LuanRT/googlevideo.git
synced 2026-06-25 15:52:04 +00:00
chore(examples): clean up
This commit is contained in:
@@ -1,8 +1,16 @@
|
||||
import ffmpeg from 'fluent-ffmpeg';
|
||||
import cliProgress from 'cli-progress';
|
||||
import type { WriteStream } from 'node:fs';
|
||||
import { createWriteStream, unlink } from 'node:fs';
|
||||
import { Innertube, UniversalCache } from 'youtubei.js';
|
||||
import GoogleVideo, { concatenateChunks, type Format, MediaType } from '../../dist/src/index.js';
|
||||
import ffmpeg from 'fluent-ffmpeg';
|
||||
import GoogleVideo, { type Format, MediaType } from '../../dist/src/index.js';
|
||||
|
||||
const progressBars = new cliProgress.MultiBar({
|
||||
clearOnComplete: false,
|
||||
hideCursor: true
|
||||
}, cliProgress.Presets.rect);
|
||||
|
||||
const formatProgressBars = new Map<string, cliProgress.SingleBar>();
|
||||
|
||||
const innertube = await Innertube.create({ cache: new UniversalCache(true) });
|
||||
const info = await innertube.getBasicInfo('wRNnMQEKo7o');
|
||||
@@ -15,7 +23,7 @@ console.info(`
|
||||
Video ID: ${info.basic_info.id}
|
||||
`);
|
||||
|
||||
const durationMs = info.basic_info?.duration ? info.basic_info.duration * 1000 : 0;
|
||||
const durationMs = (info.basic_info?.duration ?? 0) * 1000;
|
||||
const sanitizedTitle = info.basic_info.title?.replace(/[^a-z0-9]/gi, '_');
|
||||
|
||||
let audioOutput: WriteStream | undefined;
|
||||
@@ -56,39 +64,53 @@ const serverAbrStream = new GoogleVideo.ServerAbrStream({
|
||||
durationMs
|
||||
});
|
||||
|
||||
serverAbrStream.on('data', (data) => {
|
||||
let progressText = '';
|
||||
let downloadedBytesAudio = 0;
|
||||
let downloadedBytesVideo = 0;
|
||||
|
||||
for (const initializedFormat of data.initializedFormats) {
|
||||
const isVideo = initializedFormat.mimeType?.includes('video');
|
||||
const mediaFormat = info.streaming_data?.adaptive_formats.find((f) => f.itag === initializedFormat.formatId.itag);
|
||||
serverAbrStream.on('data', (streamData) => {
|
||||
for (const formatData of streamData.initializedFormats) {
|
||||
const isVideo = formatData.mimeType?.includes('video');
|
||||
const mediaFormat = info.streaming_data?.adaptive_formats.find((f) => f.itag === formatData.formatId.itag);
|
||||
const formatKey = formatData.formatKey;
|
||||
|
||||
const data = concatenateChunks(initializedFormat.mediaChunks);
|
||||
let bar = formatProgressBars.get(formatKey);
|
||||
|
||||
if (isVideo && data.length) {
|
||||
if (!videoOutput) {
|
||||
videoOutputFilename = `${sanitizedTitle}.${initializedFormat.formatId.itag}.webm`;
|
||||
videoOutput = createWriteStream(videoOutputFilename);
|
||||
}
|
||||
videoOutput.write(data);
|
||||
} else if (data.length) {
|
||||
if (!audioOutput) {
|
||||
audioOutputFilename = `${sanitizedTitle}.${initializedFormat.formatId.itag}.webm`;
|
||||
audioOutput = createWriteStream(audioOutputFilename);
|
||||
}
|
||||
audioOutput.write(data);
|
||||
if (!bar) {
|
||||
bar = progressBars.create(100, 0, undefined, { format: `${isVideo ? 'video' : 'audio'} (${formatData.formatId.itag}) [{bar}] {percentage}% | ETA: {eta}s` });
|
||||
formatProgressBars.set(formatKey, bar);
|
||||
}
|
||||
|
||||
const fmtIdentifier = `${initializedFormat.formatId.itag}_${initializedFormat.mimeType?.split(';')[0]}`;
|
||||
const percentage = Math.round((initializedFormat.sequenceList.at(-1)?.startDataRange ?? 0) / (mediaFormat?.content_length ?? 0) * 100);
|
||||
const mediaChunks = formatData.mediaChunks;
|
||||
|
||||
if (percentage)
|
||||
progressText += `${fmtIdentifier}: ${percentage}% | `;
|
||||
if (isVideo && mediaChunks.length) {
|
||||
if (!videoOutput) {
|
||||
videoOutputFilename = `${sanitizedTitle}.${formatData.formatId.itag}.webm`;
|
||||
videoOutput = createWriteStream(videoOutputFilename);
|
||||
}
|
||||
|
||||
for (const chunk of mediaChunks) {
|
||||
downloadedBytesVideo += chunk.length;
|
||||
videoOutput.write(chunk);
|
||||
}
|
||||
} else if (mediaChunks.length) {
|
||||
if (!audioOutput) {
|
||||
audioOutputFilename = `${sanitizedTitle}.${formatData.formatId.itag}.webm`;
|
||||
audioOutput = createWriteStream(audioOutputFilename);
|
||||
}
|
||||
for (const chunk of mediaChunks) {
|
||||
downloadedBytesAudio += chunk.length;
|
||||
audioOutput.write(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
const contentLength = mediaFormat?.content_length ?? 0;
|
||||
const downloadedBytes = isVideo ? downloadedBytesVideo : downloadedBytesAudio;
|
||||
|
||||
if (contentLength > 0) {
|
||||
const percentage = (downloadedBytes / contentLength) * 100;
|
||||
bar.update(percentage);
|
||||
}
|
||||
}
|
||||
|
||||
process.stdout.clearLine(0);
|
||||
process.stdout.cursorTo(0);
|
||||
process.stdout.write(progressText);
|
||||
});
|
||||
|
||||
serverAbrStream.on('error', (error) => {
|
||||
@@ -121,11 +143,6 @@ await new Promise<void>((resolve, reject) => {
|
||||
.input(audioOutputFilename)
|
||||
.videoCodec('copy')
|
||||
.audioCodec('copy')
|
||||
.on('progress', (progress) => {
|
||||
process.stdout.clearLine(0);
|
||||
process.stdout.cursorTo(0);
|
||||
process.stdout.write(`Processing: ${progress.timemark} (${progress.percent?.toFixed(2)}%)`);
|
||||
})
|
||||
.on('end', () => {
|
||||
if (videoOutputFilename) {
|
||||
unlink(videoOutputFilename, (err) => {
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import cliProgress from 'cli-progress';
|
||||
import type { WriteStream } from 'node:fs';
|
||||
import { createWriteStream } from 'node:fs';
|
||||
import { Innertube, UniversalCache } from 'youtubei.js';
|
||||
import GoogleVideo, { concatenateChunks, type Format, MediaType } from '../../dist/src/index.js';
|
||||
import GoogleVideo, { type Format, MediaType } from '../../dist/src/index.js';
|
||||
|
||||
const progressBars = new cliProgress.MultiBar({
|
||||
clearOnComplete: false,
|
||||
hideCursor: true
|
||||
}, cliProgress.Presets.rect);
|
||||
|
||||
const formatProgressBars = new Map<string, cliProgress.SingleBar>();
|
||||
|
||||
const innertube = await Innertube.create({ cache: new UniversalCache(true) });
|
||||
const info = await innertube.getBasicInfo('el68RBQBlCs');
|
||||
const info = await innertube.getBasicInfo('mzqO7oKTJKI');
|
||||
|
||||
console.info(`
|
||||
Title: ${info.basic_info.title}
|
||||
@@ -14,14 +22,14 @@ console.info(`
|
||||
Video ID: ${info.basic_info.id}
|
||||
`);
|
||||
|
||||
const durationMs = info.basic_info?.duration ? info.basic_info.duration * 1000 : 0;
|
||||
const durationMs = (info.basic_info?.duration ?? 0) * 1000;
|
||||
const sanitizedTitle = info.basic_info.title?.replace(/[^a-z0-9]/gi, '_');
|
||||
|
||||
let audioOutput: WriteStream | undefined;
|
||||
let videoOutput: WriteStream | undefined;
|
||||
|
||||
const audioFormat = info.chooseFormat({ quality: 'best', format: 'webm', type: 'audio' });
|
||||
const videoFormat = info.chooseFormat({ quality: '1080p', format: 'webm', type: 'video' });
|
||||
const videoFormat = info.chooseFormat({ quality: '720p', format: 'webm', type: 'video' });
|
||||
|
||||
const selectedAudioFormat: Format = {
|
||||
itag: audioFormat.itag,
|
||||
@@ -47,10 +55,19 @@ if (!serverAbrStreamingUrl)
|
||||
throw new Error('serverAbrStreamingUrl not found');
|
||||
|
||||
const determineFileExtension = (mimeType: string) => {
|
||||
if (mimeType.includes('video'))
|
||||
if (mimeType.includes('video')) {
|
||||
return mimeType.includes('webm') ? 'webm' : 'mp4';
|
||||
else if (mimeType.includes('audio'))
|
||||
} else if (mimeType.includes('audio')) {
|
||||
return mimeType.includes('webm') ? 'webm' : 'm4a';
|
||||
}
|
||||
return 'bin';
|
||||
};
|
||||
|
||||
const getOutputStream = (isVideo: boolean, mimeType: string, formatId?: number) => {
|
||||
const type = isVideo ? 'video' : 'audio';
|
||||
const extension = determineFileExtension(mimeType);
|
||||
const stream = createWriteStream(`${sanitizedTitle}.${formatId}.${type}.${extension}`);
|
||||
return stream;
|
||||
};
|
||||
|
||||
const serverAbrStream = new GoogleVideo.ServerAbrStream({
|
||||
@@ -60,46 +77,57 @@ const serverAbrStream = new GoogleVideo.ServerAbrStream({
|
||||
durationMs
|
||||
});
|
||||
|
||||
serverAbrStream.on('data', (data) => {
|
||||
let progressText = '';
|
||||
let downloadedBytesAudio = 0;
|
||||
let downloadedBytesVideo = 0;
|
||||
|
||||
for (const initializedFormat of data.initializedFormats) {
|
||||
const isVideo = initializedFormat.mimeType?.includes('video');
|
||||
const mediaFormat = info.streaming_data?.adaptive_formats.find((f) => f.itag === initializedFormat.formatId.itag);
|
||||
serverAbrStream.on('data', (streamData) => {
|
||||
for (const formatData of streamData.initializedFormats) {
|
||||
const isVideo = formatData.mimeType?.includes('video');
|
||||
const mediaFormat = info.streaming_data?.adaptive_formats.find((f) => f.itag === formatData.formatId.itag);
|
||||
const formatKey = formatData.formatKey;
|
||||
|
||||
const data = concatenateChunks(initializedFormat.mediaChunks);
|
||||
let bar = formatProgressBars.get(formatKey);
|
||||
|
||||
if (isVideo && data.length) {
|
||||
if (!videoOutput)
|
||||
videoOutput = createWriteStream(`${sanitizedTitle}.${initializedFormat.formatId.itag}.${determineFileExtension(initializedFormat.mimeType || '')}`);
|
||||
videoOutput.write(data);
|
||||
} else if (data.length) {
|
||||
if (!audioOutput)
|
||||
audioOutput = createWriteStream(`${sanitizedTitle}.${initializedFormat.formatId.itag}.${determineFileExtension(initializedFormat.mimeType || '')}`);
|
||||
audioOutput.write(data);
|
||||
if (!bar) {
|
||||
bar = progressBars.create(100, 0, undefined, { format: `${isVideo ? 'video' : 'audio'} (${formatData.formatId.itag}) [{bar}] {percentage}% | ETA: {eta}s` });
|
||||
formatProgressBars.set(formatKey, bar);
|
||||
}
|
||||
|
||||
const fmtIdentifier = `${initializedFormat.formatId.itag}_${initializedFormat.mimeType?.split(';')[0]}`;
|
||||
const mediaChunks = formatData.mediaChunks;
|
||||
|
||||
const percentage = Math.round((initializedFormat.sequenceList.at(-1)?.startDataRange ?? 0) / (mediaFormat?.content_length ?? 0) * 100);
|
||||
if (isVideo && mediaChunks.length) {
|
||||
if (!videoOutput)
|
||||
videoOutput = getOutputStream(true, formatData.mimeType || '', formatData.formatId?.itag);
|
||||
for (const chunk of mediaChunks) {
|
||||
downloadedBytesVideo += chunk.length;
|
||||
videoOutput.write(chunk);
|
||||
}
|
||||
} else if (mediaChunks.length) {
|
||||
if (!audioOutput)
|
||||
audioOutput = getOutputStream(false, formatData.mimeType || '', formatData.formatId?.itag);
|
||||
for (const chunk of mediaChunks) {
|
||||
downloadedBytesAudio += chunk.length;
|
||||
audioOutput.write(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
if (percentage)
|
||||
progressText += `${fmtIdentifier}: ${percentage}% | `;
|
||||
const contentLength = mediaFormat?.content_length ?? 0;
|
||||
const downloadedBytes = isVideo ? downloadedBytesVideo : downloadedBytesAudio;
|
||||
|
||||
if (contentLength > 0) {
|
||||
const percentage = (downloadedBytes / contentLength) * 100;
|
||||
bar.update(percentage);
|
||||
}
|
||||
}
|
||||
|
||||
process.stdout.clearLine(0);
|
||||
process.stdout.cursorTo(0);
|
||||
process.stdout.write(progressText);
|
||||
});
|
||||
|
||||
serverAbrStream.on('error', (error) => {
|
||||
progressBars.stop();
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
serverAbrStream.on('end', () => {
|
||||
process.stdout.clearLine(0);
|
||||
process.stdout.cursorTo(0);
|
||||
process.stdout.write('Done!');
|
||||
progressBars.stop();
|
||||
|
||||
if (audioOutput)
|
||||
audioOutput.end();
|
||||
|
||||
67
examples/downloader/package-lock.json
generated
67
examples/downloader/package-lock.json
generated
@@ -9,11 +9,13 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cli-progress": "^3.12.0",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"shaka-player": "^4.11.2",
|
||||
"youtubei.js": "github:LuanRT/YouTube.js#refactor/update-protos"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cli-progress": "^3.11.6",
|
||||
"@types/fluent-ffmpeg": "^2.1.26",
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
@@ -31,6 +33,15 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cli-progress": {
|
||||
"version": "3.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.6.tgz",
|
||||
"integrity": "sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/fluent-ffmpeg": {
|
||||
"version": "2.1.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.26.tgz",
|
||||
@@ -60,16 +71,40 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "0.2.10",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
|
||||
"integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ=="
|
||||
},
|
||||
"node_modules/cli-progress": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz",
|
||||
"integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/eme-encryption-scheme-polyfill": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/eme-encryption-scheme-polyfill/-/eme-encryption-scheme-polyfill-2.1.5.tgz",
|
||||
"integrity": "sha512-z9BKXV4TCYjmar0wiZLObZ0J8HE13VIg7Zq/iyPWdbEfROtxVXEJalknWKtBR5XNezzy15/zWS964TGbcAWlPg=="
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"node_modules/fluent-ffmpeg": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz",
|
||||
@@ -82,6 +117,14 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
@@ -109,6 +152,30 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
||||
|
||||
@@ -12,11 +12,13 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cli-progress": "^3.12.0",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"shaka-player": "^4.11.2",
|
||||
"youtubei.js": "github:LuanRT/YouTube.js#refactor/update-protos"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cli-progress": "^3.11.6",
|
||||
"@types/fluent-ffmpeg": "^2.1.26",
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
|
||||
@@ -39,15 +39,15 @@ export class ServerAbrStream extends EventEmitterLike {
|
||||
this.totalDurationMs = args.durationMs;
|
||||
}
|
||||
|
||||
public on(event: 'end', listener: (data: ServerAbrResponse) => void): void;
|
||||
public on(event: 'data', listener: (data: ServerAbrResponse) => void): void;
|
||||
public on(event: 'end', listener: (streamData: ServerAbrResponse) => void): void;
|
||||
public on(event: 'data', listener: (streamData: ServerAbrResponse) => void): void;
|
||||
public on(event: 'error', listener: (error: Error) => void): void;
|
||||
public on(event: string, listener: (...data: any[]) => void): void {
|
||||
super.on(event, listener);
|
||||
}
|
||||
|
||||
public once(event: 'end', listener: (data: ServerAbrResponse) => void): void;
|
||||
public once(event: 'data', listener: (data: ServerAbrResponse) => void): void;
|
||||
public once(event: 'end', listener: (streamData: ServerAbrResponse) => void): void;
|
||||
public once(event: 'data', listener: (streamData: ServerAbrResponse) => void): void;
|
||||
public once(event: 'error', listener: (error: Error) => void): void;
|
||||
public once(event: string, listener: (...args: any[]) => void): void {
|
||||
super.once(event, listener);
|
||||
@@ -88,8 +88,8 @@ export class ServerAbrStream extends EventEmitterLike {
|
||||
if (typeof mediaInfo.startTimeMs !== 'number')
|
||||
throw new Error('Invalid media start time');
|
||||
|
||||
while (mediaInfo.startTimeMs < this.totalDurationMs) {
|
||||
try {
|
||||
try {
|
||||
while (mediaInfo.startTimeMs < this.totalDurationMs) {
|
||||
const data = await this.fetchMedia({ mediaInfo, audioFormatIds, videoFormatIds });
|
||||
|
||||
this.emit('data', data);
|
||||
@@ -115,10 +115,9 @@ export class ServerAbrStream extends EventEmitterLike {
|
||||
}
|
||||
|
||||
mediaInfo.startTimeMs += mainFormat.sequenceList.reduce((acc, seq) => acc + (seq.durationMs || 0), 0);
|
||||
} catch (error) {
|
||||
this.emit('error', error);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
this.emit('error', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,6 +282,7 @@ export class ServerAbrStream extends EventEmitterLike {
|
||||
if (!this.formatsByKey.has(formatKey)) {
|
||||
this.initializedFormats.push({
|
||||
formatId: formatInitializationMetadata.formatId,
|
||||
formatKey: getFormatKey(formatInitializationMetadata.formatId),
|
||||
durationMs: formatInitializationMetadata.durationMs,
|
||||
mimeType: formatInitializationMetadata.mimeType,
|
||||
sequenceCount: formatInitializationMetadata.field4,
|
||||
|
||||
@@ -41,6 +41,7 @@ export type Sequence = {
|
||||
|
||||
export type InitializedFormat = {
|
||||
formatId: FormatId;
|
||||
formatKey: string;
|
||||
durationMs?: number;
|
||||
mimeType?: string;
|
||||
sequenceCount?: number;
|
||||
|
||||
Reference in New Issue
Block a user