Add bun.lock for dependency management, update package version to 2025.1.0, refine TypeScript dependencies, and introduce tsconfig.build.json for type declaration output. Enhance RtmpPush class with dependency injection for command building and process management, and implement new interfaces for better extensibility.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import {describe, it, expect, beforeEach, afterEach} from 'bun:test';
|
||||
import {RtmpPush} from '../src/RtmpPush';
|
||||
import {RtmpPush} from '../src/index.js';
|
||||
import type {ICommandBuilder, IProcessManager, ILogger, IProcessSpawner} from '../src/index.js';
|
||||
import {CommandBuilder, ProcessManager, ProcessSpawner, ConsoleLogger} from '../src/index.js';
|
||||
|
||||
describe('RtmpPush', () => {
|
||||
let rtmpPush: RtmpPush;
|
||||
@@ -252,5 +254,179 @@ describe('RtmpPush', () => {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw error when starting an already running stream', () => {
|
||||
const streamKey = 'test-stream';
|
||||
const capabilities = ['h264'];
|
||||
|
||||
// Mock process manager that reports as running
|
||||
const mockProcessManager: IProcessManager = {
|
||||
isRunning: () => true,
|
||||
start: () => {},
|
||||
stop: () => {},
|
||||
on: () => mockProcessManager,
|
||||
emit: () => false,
|
||||
once: () => mockProcessManager,
|
||||
off: () => mockProcessManager,
|
||||
removeListener: () => mockProcessManager,
|
||||
removeAllListeners: () => mockProcessManager,
|
||||
addListener: () => mockProcessManager,
|
||||
setMaxListeners: () => mockProcessManager,
|
||||
getMaxListeners: () => 10,
|
||||
listeners: () => [],
|
||||
rawListeners: () => [],
|
||||
listenerCount: () => 0,
|
||||
prependListener: () => mockProcessManager,
|
||||
prependOnceListener: () => mockProcessManager,
|
||||
eventNames: () => []
|
||||
} as IProcessManager;
|
||||
|
||||
const customRtmpPush = new RtmpPush(mockMediaSourceUri, mockRtmpIngestUri, {
|
||||
processManager: mockProcessManager
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
customRtmpPush.start(streamKey, capabilities);
|
||||
}).toThrow('Stream is already running');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dependency Injection', () => {
|
||||
it('should accept custom command builder', () => {
|
||||
const mockCommandBuilder: ICommandBuilder = {
|
||||
buildCommand: (mediaUri, rtmpUri, streamKey, capabilities) => {
|
||||
expect(mediaUri).toBe(mockMediaSourceUri);
|
||||
expect(rtmpUri).toBe(mockRtmpIngestUri);
|
||||
expect(streamKey).toBe('test-key');
|
||||
expect(capabilities).toEqual(['h264']);
|
||||
return ['custom-ffmpeg', '-custom', 'args'];
|
||||
}
|
||||
};
|
||||
|
||||
const customRtmpPush = new RtmpPush(mockMediaSourceUri, mockRtmpIngestUri, {
|
||||
commandBuilder: mockCommandBuilder
|
||||
});
|
||||
|
||||
expect(customRtmpPush.mediaSourceUri).toBe(mockMediaSourceUri);
|
||||
expect(customRtmpPush.rtmpIngestUri).toBe(mockRtmpIngestUri);
|
||||
});
|
||||
|
||||
it('should accept custom process manager', () => {
|
||||
let startCalled = false;
|
||||
const mockProcessManager: IProcessManager = {
|
||||
isRunning: () => false,
|
||||
start: (command, args) => {
|
||||
startCalled = true;
|
||||
expect(command).toBe('ffmpeg');
|
||||
expect(args).toBeInstanceOf(Array);
|
||||
},
|
||||
stop: () => {},
|
||||
on: () => mockProcessManager,
|
||||
emit: () => false,
|
||||
once: () => mockProcessManager,
|
||||
off: () => mockProcessManager,
|
||||
removeListener: () => mockProcessManager,
|
||||
removeAllListeners: () => mockProcessManager,
|
||||
addListener: () => mockProcessManager,
|
||||
setMaxListeners: () => mockProcessManager,
|
||||
getMaxListeners: () => 10,
|
||||
listeners: () => [],
|
||||
rawListeners: () => [],
|
||||
listenerCount: () => 0,
|
||||
prependListener: () => mockProcessManager,
|
||||
prependOnceListener: () => mockProcessManager,
|
||||
eventNames: () => []
|
||||
} as IProcessManager;
|
||||
|
||||
const customRtmpPush = new RtmpPush(mockMediaSourceUri, mockRtmpIngestUri, {
|
||||
processManager: mockProcessManager
|
||||
});
|
||||
|
||||
try {
|
||||
customRtmpPush.start('test-key', ['h264']);
|
||||
expect(startCalled).toBe(true);
|
||||
} catch (error) {
|
||||
// If ffmpeg is not available, that's expected
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should accept custom ffmpeg command', () => {
|
||||
const customRtmpPush = new RtmpPush(mockMediaSourceUri, mockRtmpIngestUri, {
|
||||
ffmpegCommand: 'custom-ffmpeg-path'
|
||||
});
|
||||
|
||||
expect(customRtmpPush.mediaSourceUri).toBe(mockMediaSourceUri);
|
||||
expect(customRtmpPush.rtmpIngestUri).toBe(mockRtmpIngestUri);
|
||||
});
|
||||
|
||||
it('should use default implementations when none provided', () => {
|
||||
const defaultRtmpPush = new RtmpPush(mockMediaSourceUri, mockRtmpIngestUri);
|
||||
expect(defaultRtmpPush).toBeInstanceOf(RtmpPush);
|
||||
expect(defaultRtmpPush.mediaSourceUri).toBe(mockMediaSourceUri);
|
||||
expect(defaultRtmpPush.rtmpIngestUri).toBe(mockRtmpIngestUri);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Process Manager Events', () => {
|
||||
it('should provide access to process manager for event handling', () => {
|
||||
const processManager = rtmpPush.getProcessManager();
|
||||
expect(processManager).toBeDefined();
|
||||
expect(typeof processManager.on).toBe('function');
|
||||
expect(typeof processManager.emit).toBe('function');
|
||||
});
|
||||
|
||||
it('should allow listening to process events', () => {
|
||||
const processManager = rtmpPush.getProcessManager();
|
||||
let dataReceived = false;
|
||||
let errorReceived = false;
|
||||
let closeReceived = false;
|
||||
|
||||
processManager.on('data', () => {
|
||||
dataReceived = true;
|
||||
});
|
||||
|
||||
processManager.on('error', () => {
|
||||
errorReceived = true;
|
||||
});
|
||||
|
||||
processManager.on('close', () => {
|
||||
closeReceived = true;
|
||||
});
|
||||
|
||||
// Events would be emitted by the actual process, but we can verify the listeners are set up
|
||||
expect(typeof processManager.on).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('CommandBuilder', () => {
|
||||
it('should build correct FFmpeg commands', () => {
|
||||
const builder = new CommandBuilder();
|
||||
const command = builder.buildCommand(
|
||||
'https://example.com/video.ts',
|
||||
'rtmp://ingest.example.com/ingest',
|
||||
'test-key',
|
||||
['h264', 'aac']
|
||||
);
|
||||
|
||||
expect(command[0]).toBe('ffmpeg');
|
||||
expect(command).toContain('-re');
|
||||
expect(command).toContain('-i');
|
||||
expect(command).toContain('https://example.com/video.ts');
|
||||
expect(command[command.length - 1]).toContain('rtmp://ingest.example.com/ingest/test-key');
|
||||
expect(command[command.length - 1]).toContain('capabilities=h264,aac');
|
||||
});
|
||||
|
||||
it('should use custom ffmpeg command when provided', () => {
|
||||
const builder = new CommandBuilder('custom-ffmpeg');
|
||||
const command = builder.buildCommand(
|
||||
'https://example.com/video.ts',
|
||||
'rtmp://ingest.example.com/ingest',
|
||||
'test-key',
|
||||
['h264']
|
||||
);
|
||||
|
||||
expect(command[0]).toBe('custom-ffmpeg');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user