@mingxy/ocosay 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +12 -0
  2. package/dist/config.js +2 -2
  3. package/dist/config.js.map +1 -1
  4. package/dist/plugin.d.ts +2 -4
  5. package/dist/plugin.d.ts.map +1 -1
  6. package/dist/plugin.js +6 -4
  7. package/dist/plugin.js.map +2 -2
  8. package/package.json +1 -1
  9. package/TECH_PLAN.md +0 -352
  10. package/__mocks__/@opencode-ai/plugin.ts +0 -32
  11. package/jest.config.js +0 -15
  12. package/src/config.ts +0 -183
  13. package/src/core/backends/afplay-backend.ts +0 -162
  14. package/src/core/backends/aplay-backend.ts +0 -160
  15. package/src/core/backends/base.ts +0 -117
  16. package/src/core/backends/index.ts +0 -128
  17. package/src/core/backends/naudiodon-backend.ts +0 -164
  18. package/src/core/backends/powershell-backend.ts +0 -173
  19. package/src/core/player.ts +0 -322
  20. package/src/core/speaker.ts +0 -283
  21. package/src/core/stream-player.ts +0 -326
  22. package/src/core/stream-reader.ts +0 -190
  23. package/src/core/streaming-synthesizer.ts +0 -123
  24. package/src/core/types.ts +0 -185
  25. package/src/index.ts +0 -236
  26. package/src/plugin.ts +0 -178
  27. package/src/providers/base.ts +0 -150
  28. package/src/providers/minimax.ts +0 -515
  29. package/src/tools/tts.ts +0 -277
  30. package/src/types/config.ts +0 -38
  31. package/src/types/naudiodon.d.ts +0 -19
  32. package/tests/__mocks__/@opencode-ai/plugin.ts +0 -32
  33. package/tests/backends.test.ts +0 -831
  34. package/tests/config.test.ts +0 -327
  35. package/tests/index.test.ts +0 -201
  36. package/tests/integration-test.d.ts +0 -6
  37. package/tests/integration-test.d.ts.map +0 -1
  38. package/tests/integration-test.js +0 -84
  39. package/tests/integration-test.js.map +0 -1
  40. package/tests/integration-test.ts +0 -93
  41. package/tests/p1-fixes.test.ts +0 -160
  42. package/tests/plugin.test.ts +0 -312
  43. package/tests/provider.test.d.ts +0 -2
  44. package/tests/provider.test.d.ts.map +0 -1
  45. package/tests/provider.test.js +0 -69
  46. package/tests/provider.test.js.map +0 -1
  47. package/tests/provider.test.ts +0 -87
  48. package/tests/speaker.test.d.ts +0 -2
  49. package/tests/speaker.test.d.ts.map +0 -1
  50. package/tests/speaker.test.js +0 -63
  51. package/tests/speaker.test.js.map +0 -1
  52. package/tests/speaker.test.ts +0 -232
  53. package/tests/stream-player.test.ts +0 -303
  54. package/tests/stream-reader.test.ts +0 -269
  55. package/tests/streaming-synthesizer.test.ts +0 -225
  56. package/tests/tts-tools.test.ts +0 -270
  57. package/tests/types.test.d.ts +0 -2
  58. package/tests/types.test.d.ts.map +0 -1
  59. package/tests/types.test.js +0 -61
  60. package/tests/types.test.js.map +0 -1
  61. package/tests/types.test.ts +0 -63
  62. package/tsconfig.json +0 -22
@@ -1,87 +0,0 @@
1
- import {
2
- registerProvider,
3
- getProvider,
4
- listProviders,
5
- hasProvider,
6
- unregisterProvider,
7
- BaseTTSProvider
8
- } from '../src/providers/base'
9
- import { TTSError, TTSErrorCode, TTSCapabilities } from '../src/core/types'
10
-
11
- // 创建测试 Provider
12
- class TestProvider extends BaseTTSProvider {
13
- name = 'test'
14
- capabilities: TTSCapabilities = { speak: true }
15
-
16
- protected async doSpeak(text: string, voice: string | undefined, model: any) {
17
- return {
18
- audioData: Buffer.from([]),
19
- format: 'mp3',
20
- isStream: model === 'stream'
21
- }
22
- }
23
- }
24
-
25
- describe('Provider Registry', () => {
26
- beforeEach(() => {
27
- // 清理
28
- unregisterProvider('test')
29
- })
30
-
31
- it('should register provider', () => {
32
- const provider = new TestProvider()
33
- registerProvider('test', provider)
34
- expect(hasProvider('test')).toBe(true)
35
- })
36
-
37
- it('should get registered provider', () => {
38
- const provider = new TestProvider()
39
- registerProvider('test', provider)
40
- const retrieved = getProvider('test')
41
- expect(retrieved).toBe(provider)
42
- })
43
-
44
- it('should throw when getting non-existent provider', () => {
45
- expect(() => getProvider('non-existent')).toThrow(TTSError)
46
- })
47
-
48
- it('should list all providers', () => {
49
- registerProvider('test', new TestProvider())
50
- const list = listProviders()
51
- expect(list).toContain('test')
52
- })
53
-
54
- it('should unregister provider', () => {
55
- registerProvider('test', new TestProvider())
56
- unregisterProvider('test')
57
- expect(hasProvider('test')).toBe(false)
58
- })
59
-
60
- it('should throw when registering duplicate provider', () => {
61
- registerProvider('test', new TestProvider())
62
- expect(() => registerProvider('test', new TestProvider())).toThrow()
63
- })
64
- })
65
-
66
- describe('BaseTTSProvider', () => {
67
- let provider: TestProvider
68
-
69
- beforeEach(() => {
70
- provider = new TestProvider()
71
- })
72
-
73
- it('should speak with options', async () => {
74
- const result = await provider.speak('Hello', { model: 'sync' })
75
- expect(result.format).toBe('mp3')
76
- expect(result.isStream).toBe(false)
77
- })
78
-
79
- it('should throw on empty text', async () => {
80
- await expect(provider.speak('')).rejects.toThrow(TTSError)
81
- await expect(provider.speak(' ')).rejects.toThrow(TTSError)
82
- })
83
-
84
- it('should stop without error', async () => {
85
- await expect(provider.stop()).resolves.toBeUndefined()
86
- })
87
- })
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=speaker.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"speaker.test.d.ts","sourceRoot":"","sources":["speaker.test.ts"],"names":[],"mappings":""}
@@ -1,63 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const speaker_1 = require("../src/core/speaker");
4
- const types_1 = require("../src/core/types");
5
- const base_1 = require("../src/providers/base");
6
- // Mock Provider
7
- class MockProvider extends base_1.BaseTTSProvider {
8
- name = 'mock';
9
- capabilities = { speak: true, stream: true };
10
- async doSpeak(text, voice, model) {
11
- return {
12
- audioData: Buffer.from([1, 2, 3]),
13
- format: 'mp3',
14
- isStream: model === 'stream',
15
- duration: 1.0
16
- };
17
- }
18
- }
19
- describe('Speaker', () => {
20
- let speaker;
21
- beforeEach(() => {
22
- (0, base_1.registerProvider)('mock', new MockProvider());
23
- speaker = new speaker_1.Speaker({ defaultProvider: 'mock' });
24
- });
25
- afterEach(() => {
26
- (0, base_1.unregisterProvider)('mock');
27
- });
28
- describe('initialization', () => {
29
- it('should create speaker with options', () => {
30
- const s = new speaker_1.Speaker({ defaultProvider: 'mock', defaultModel: 'sync' });
31
- expect(s.getProviders()).toContain('mock');
32
- });
33
- });
34
- describe('speak', () => {
35
- it('should throw on empty text', async () => {
36
- await expect(speaker.speak('')).rejects.toThrow(types_1.TTSError);
37
- });
38
- });
39
- describe('control methods', () => {
40
- it('should pause without error', () => {
41
- expect(() => speaker.pause()).not.toThrow();
42
- });
43
- it('should resume without error', () => {
44
- expect(() => speaker.resume()).not.toThrow();
45
- });
46
- it('should stop without error', async () => {
47
- await expect(speaker.stop()).resolves.toBeUndefined();
48
- });
49
- });
50
- describe('listVoices', () => {
51
- it('should return voices from provider', async () => {
52
- const voices = await speaker.listVoices('mock');
53
- expect(Array.isArray(voices)).toBe(true);
54
- });
55
- });
56
- describe('getCapabilities', () => {
57
- it('should return provider capabilities', () => {
58
- const caps = speaker.getCapabilities('mock');
59
- expect(caps.speak).toBe(true);
60
- });
61
- });
62
- });
63
- //# sourceMappingURL=speaker.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"speaker.test.js","sourceRoot":"","sources":["speaker.test.ts"],"names":[],"mappings":"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"}
@@ -1,232 +0,0 @@
1
- import { Speaker, getDefaultSpeaker, speak, stop, pause, resume, listVoices } from '../src/core/speaker'
2
- import { TTSError, TTSErrorCode, TTSCapabilities, Voice } from '../src/core/types'
3
- import { registerProvider, unregisterProvider, BaseTTSProvider } from '../src/providers/base'
4
- import { AudioPlayer } from '../src/core/player'
5
-
6
- jest.mock('../src/core/player')
7
-
8
- const MockAudioPlayer = AudioPlayer as jest.MockedClass<typeof AudioPlayer>
9
-
10
- class MockProvider extends BaseTTSProvider {
11
- name = 'mock'
12
- capabilities: TTSCapabilities = { speak: true, stream: true }
13
-
14
- protected async doSpeak(text: string, voice: string | undefined, model: any) {
15
- if (text === 'error') {
16
- throw new TTSError('Provider error', TTSErrorCode.UNKNOWN, this.name)
17
- }
18
- return {
19
- audioData: Buffer.from([1, 2, 3]),
20
- format: 'mp3',
21
- isStream: model === 'stream',
22
- duration: 1.0
23
- }
24
- }
25
-
26
- async listVoices(): Promise<Voice[]> {
27
- return [
28
- { id: 'voice1', name: 'Voice 1', language: 'zh-CN' },
29
- { id: 'voice2', name: 'Voice 2', language: 'en-US', gender: 'male' as const }
30
- ]
31
- }
32
- }
33
-
34
- describe('Speaker', () => {
35
- let speaker: Speaker
36
- let mockPlayer: any
37
- let capturedEvents: any
38
-
39
- beforeEach(() => {
40
- jest.clearAllMocks()
41
- registerProvider('mock', new MockProvider())
42
-
43
- capturedEvents = {}
44
- mockPlayer = {
45
- play: jest.fn().mockImplementation(() => {
46
- capturedEvents.onStart?.()
47
- return Promise.resolve()
48
- }),
49
- pause: jest.fn().mockImplementation(() => { capturedEvents.onPause?.() }),
50
- resume: jest.fn().mockImplementation(() => { capturedEvents.onResume?.() }),
51
- stop: jest.fn().mockImplementation(() => {
52
- capturedEvents.onStop?.()
53
- return Promise.resolve()
54
- })
55
- }
56
- MockAudioPlayer.mockImplementation((events?: any) => {
57
- Object.assign(capturedEvents, events)
58
- return mockPlayer
59
- })
60
-
61
- speaker = new Speaker({ defaultProvider: 'mock', defaultModel: 'stream' })
62
- })
63
-
64
- afterEach(() => {
65
- unregisterProvider('mock')
66
- })
67
-
68
- describe('initialization', () => {
69
- it('should create speaker with options', () => {
70
- const s = new Speaker({ defaultProvider: 'mock', defaultModel: 'sync' })
71
- expect(s.getProviders()).toContain('mock')
72
- })
73
-
74
- it('should create speaker with all options', () => {
75
- const s = new Speaker({
76
- defaultProvider: 'mock',
77
- defaultModel: 'stream',
78
- defaultVoice: 'voice1',
79
- onEvent: jest.fn()
80
- })
81
- expect(s.getProviders()).toContain('mock')
82
- })
83
- })
84
-
85
- describe('speak', () => {
86
- it('should throw on empty text', async () => {
87
- await expect(speaker.speak('')).rejects.toThrow(TTSError)
88
- })
89
-
90
- it('should throw on whitespace-only text', async () => {
91
- await expect(speaker.speak(' ')).rejects.toThrow(TTSError)
92
- })
93
-
94
- it('should stop current playback before new speak', async () => {
95
- await speaker.speak('First')
96
- await speaker.speak('Second')
97
- expect(mockPlayer.stop).toHaveBeenCalled()
98
- })
99
-
100
- it('should play audio data', async () => {
101
- await speaker.speak('Hello')
102
- expect(mockPlayer.play).toHaveBeenCalledWith(Buffer.from([1, 2, 3]), 'mp3')
103
- })
104
-
105
- it('should throw on provider error', async () => {
106
- await expect(speaker.speak('error')).rejects.toThrow()
107
- })
108
-
109
- it('should throw when provider not found', async () => {
110
- await expect(speaker.speak('Hello', { provider: 'non-existent' })).rejects.toThrow()
111
- })
112
- })
113
-
114
- describe('control methods', () => {
115
- it('should pause without error', () => {
116
- expect(() => speaker.pause()).not.toThrow()
117
- })
118
-
119
- it('should resume without error', () => {
120
- expect(() => speaker.resume()).not.toThrow()
121
- })
122
-
123
- it('should stop without error', async () => {
124
- await expect(speaker.stop()).resolves.toBeUndefined()
125
- })
126
- })
127
-
128
- describe('listVoices', () => {
129
- it('should return voices from provider', async () => {
130
- const voices = await speaker.listVoices('mock')
131
- expect(Array.isArray(voices)).toBe(true)
132
- expect(voices.length).toBe(2)
133
- })
134
-
135
- it('should use default provider when none specified', async () => {
136
- const voices = await speaker.listVoices()
137
- expect(Array.isArray(voices)).toBe(true)
138
- })
139
- })
140
-
141
- describe('getCapabilities', () => {
142
- it('should return provider capabilities', () => {
143
- const caps = speaker.getCapabilities('mock')
144
- expect(caps.speak).toBe(true)
145
- })
146
-
147
- it('should use default provider when none specified', () => {
148
- const caps = speaker.getCapabilities()
149
- expect(caps.speak).toBe(true)
150
- })
151
- })
152
-
153
- describe('getProviders', () => {
154
- it('should return list of registered providers', () => {
155
- const providers = speaker.getProviders()
156
- expect(providers).toContain('mock')
157
- })
158
- })
159
-
160
- describe('isPlaying', () => {
161
- it('should return false initially', () => {
162
- expect(speaker.isPlaying()).toBe(false)
163
- })
164
-
165
- it('should return true while speaking', async () => {
166
- const speakPromise = speaker.speak('Hello')
167
- expect(speaker.isPlaying()).toBe(true)
168
- await speakPromise
169
- })
170
-
171
- it('should return false after stop', async () => {
172
- await speaker.speak('Hello')
173
- await speaker.stop()
174
- expect(speaker.isPlaying()).toBe(false)
175
- })
176
- })
177
-
178
- describe('isPausedState', () => {
179
- it('should return false initially', () => {
180
- expect(speaker.isPausedState()).toBe(false)
181
- })
182
- })
183
-
184
- describe('destroy', () => {
185
- it('should stop player on destroy', async () => {
186
- await speaker.destroy()
187
- expect(mockPlayer.stop).toHaveBeenCalled()
188
- })
189
-
190
- it('should set isSpeaking to false', async () => {
191
- await speaker.speak('Hello')
192
- await speaker.destroy()
193
- expect(speaker.isPlaying()).toBe(false)
194
- })
195
-
196
- it('should be callable multiple times', async () => {
197
- await speaker.destroy()
198
- await expect(speaker.destroy()).resolves.not.toThrow()
199
- })
200
- })
201
-
202
- describe('events', () => {
203
- it('should emit start event', async () => {
204
- const startCallback = jest.fn()
205
- speaker.on('start', startCallback)
206
- await speaker.speak('Hello')
207
- expect(startCallback).toHaveBeenCalled()
208
- })
209
-
210
- it('should emit stop event on stop', async () => {
211
- const stopCallback = jest.fn()
212
- speaker.on('stop', stopCallback)
213
- await speaker.stop()
214
- expect(stopCallback).toHaveBeenCalled()
215
- })
216
- })
217
- })
218
-
219
- describe('getDefaultSpeaker', () => {
220
- beforeEach(() => {
221
- registerProvider('mock', new MockProvider())
222
- })
223
-
224
- afterEach(() => {
225
- unregisterProvider('mock')
226
- })
227
-
228
- it('should return a Speaker instance', () => {
229
- const speaker = getDefaultSpeaker()
230
- expect(speaker).toBeInstanceOf(Speaker)
231
- })
232
- })
@@ -1,303 +0,0 @@
1
- import { StreamPlayer } from '../src/core/stream-player'
2
- import { spawn } from 'child_process'
3
- import { createWriteStream } from 'fs'
4
-
5
- jest.mock('child_process')
6
- jest.mock('fs')
7
-
8
- const mockSpawn = spawn as jest.MockedFunction<typeof spawn>
9
- const mockCreateWriteStream = createWriteStream as jest.MockedFunction<typeof createWriteStream>
10
-
11
- describe('StreamPlayer', () => {
12
- let mockWriteStream: any
13
- let mockPlayerProcess: any
14
-
15
- beforeEach(() => {
16
- jest.clearAllMocks()
17
-
18
- mockWriteStream = {
19
- write: jest.fn().mockReturnValue(true),
20
- end: jest.fn(),
21
- destroy: jest.fn(),
22
- on: jest.fn(),
23
- once: jest.fn()
24
- }
25
-
26
- mockPlayerProcess = {
27
- kill: jest.fn(),
28
- on: jest.fn(),
29
- stdin: { end: jest.fn() }
30
- }
31
-
32
- mockCreateWriteStream.mockReturnValue(mockWriteStream as any)
33
- mockSpawn.mockImplementation(() => mockPlayerProcess as any)
34
- })
35
-
36
- afterEach(() => {
37
- jest.restoreAllMocks()
38
- })
39
-
40
- function createPlayer(options?: any): StreamPlayer {
41
- return new StreamPlayer(options)
42
- }
43
-
44
- describe('initialization', () => {
45
- it('should create StreamPlayer with default options', () => {
46
- const player = createPlayer()
47
- expect(player).toBeInstanceOf(StreamPlayer)
48
- })
49
-
50
- it('should create StreamPlayer with custom format', () => {
51
- const player = createPlayer({ format: 'wav' })
52
- expect(player).toBeInstanceOf(StreamPlayer)
53
- })
54
-
55
- it('should create StreamPlayer with events callback', () => {
56
- const events = {
57
- onStart: jest.fn(),
58
- onEnd: jest.fn(),
59
- onProgress: jest.fn(),
60
- onError: jest.fn(),
61
- onStop: jest.fn()
62
- }
63
- const player = createPlayer({ events })
64
- expect(player).toBeInstanceOf(StreamPlayer)
65
- })
66
- })
67
-
68
- describe('write', () => {
69
- it('should auto-start if not started', () => {
70
- const player = createPlayer()
71
- player.write(Buffer.from([1, 2, 3]))
72
- expect(player.isStarted()).toBe(true)
73
- })
74
-
75
- it('should ignore write if stopped', () => {
76
- const player = createPlayer()
77
- player.start()
78
- player.stop()
79
- player.write(Buffer.from([1, 2, 3]))
80
- expect(player.getBytesWritten()).toBe(0)
81
- })
82
-
83
- it('should write chunk to stream', () => {
84
- const player = createPlayer()
85
- player.write(Buffer.from([1, 2, 3]))
86
- expect(mockWriteStream.write).toHaveBeenCalledWith(Buffer.from([1, 2, 3]))
87
- })
88
-
89
- it('should increment bytesWritten counter', () => {
90
- const player = createPlayer()
91
- player.write(Buffer.from([1, 2, 3]))
92
- expect(player.getBytesWritten()).toBe(3)
93
- })
94
-
95
- it('should call onProgress callback', () => {
96
- const onProgress = jest.fn()
97
- const player = createPlayer({ events: { onProgress } })
98
- player.write(Buffer.from([1, 2, 3]))
99
- expect(onProgress).toHaveBeenCalledWith(3)
100
- })
101
-
102
- it('should emit progress event', () => {
103
- const player = createPlayer()
104
- const progressCallback = jest.fn()
105
- player.on('progress', progressCallback)
106
- player.write(Buffer.from([1, 2, 3]))
107
- expect(progressCallback).toHaveBeenCalledWith(3)
108
- })
109
- })
110
-
111
- describe('start', () => {
112
- it('should set started flag to true', () => {
113
- const player = createPlayer()
114
- player.start()
115
- expect(player.isStarted()).toBe(true)
116
- })
117
-
118
- it('should not start twice', () => {
119
- const player = createPlayer()
120
- player.start()
121
- player.start()
122
- expect(mockCreateWriteStream).toHaveBeenCalledTimes(1)
123
- })
124
-
125
- it('should call onStart callback', () => {
126
- const onStart = jest.fn()
127
- const player = createPlayer({ events: { onStart } })
128
- player.start()
129
- expect(onStart).toHaveBeenCalled()
130
- })
131
-
132
- it('should emit start event', () => {
133
- const player = createPlayer()
134
- const startCallback = jest.fn()
135
- player.on('start', startCallback)
136
- player.start()
137
- expect(startCallback).toHaveBeenCalled()
138
- })
139
- })
140
-
141
- describe('stop', () => {
142
- it('should set stopped flag to true', () => {
143
- const player = createPlayer()
144
- player.start()
145
- player.stop()
146
- expect(player.isStopped()).toBe(true)
147
- })
148
-
149
- it('should kill player process', () => {
150
- const player = createPlayer()
151
- player.start()
152
- player.stop()
153
- expect(mockPlayerProcess.kill).toHaveBeenCalledWith('SIGTERM')
154
- })
155
-
156
- it('should call onStop callback', () => {
157
- const onStop = jest.fn()
158
- const player = createPlayer({ events: { onStop } })
159
- player.start()
160
- player.stop()
161
- expect(onStop).toHaveBeenCalled()
162
- })
163
-
164
- it('should emit stop event', () => {
165
- const player = createPlayer()
166
- player.start()
167
- const stopCallback = jest.fn()
168
- player.on('stop', stopCallback)
169
- player.stop()
170
- expect(stopCallback).toHaveBeenCalled()
171
- })
172
- })
173
-
174
- describe('pause', () => {
175
- it('should set paused flag to true', () => {
176
- const player = createPlayer()
177
- player.start()
178
- player.pause()
179
- expect(player.isPaused()).toBe(true)
180
- })
181
-
182
- it('should send SIGSTOP to player process', () => {
183
- const player = createPlayer()
184
- player.start()
185
- player.pause()
186
- expect(mockPlayerProcess.kill).toHaveBeenCalledWith('SIGSTOP')
187
- })
188
-
189
- it('should call onPause callback', () => {
190
- const onPause = jest.fn()
191
- const player = createPlayer({ events: { onPause } })
192
- player.start()
193
- player.pause()
194
- expect(onPause).toHaveBeenCalled()
195
- })
196
-
197
- it('should emit pause event', () => {
198
- const player = createPlayer()
199
- player.start()
200
- const pauseCallback = jest.fn()
201
- player.on('pause', pauseCallback)
202
- player.pause()
203
- expect(pauseCallback).toHaveBeenCalled()
204
- })
205
-
206
- it('should not pause if not started', () => {
207
- const player = createPlayer()
208
- player.pause()
209
- expect(mockPlayerProcess.kill).not.toHaveBeenCalled()
210
- })
211
-
212
- it('should not pause if already paused', () => {
213
- const player = createPlayer()
214
- player.start()
215
- player.pause()
216
- player.pause()
217
- expect(mockPlayerProcess.kill).toHaveBeenCalledTimes(1)
218
- })
219
- })
220
-
221
- describe('resume', () => {
222
- it('should set paused flag to false', () => {
223
- const player = createPlayer()
224
- player.start()
225
- player.pause()
226
- player.resume()
227
- expect(player.isPaused()).toBe(false)
228
- })
229
-
230
- it('should send SIGCONT to player process', () => {
231
- const player = createPlayer()
232
- player.start()
233
- player.pause()
234
- player.resume()
235
- expect(mockPlayerProcess.kill).toHaveBeenCalledWith('SIGCONT')
236
- })
237
-
238
- it('should call onResume callback', () => {
239
- const onResume = jest.fn()
240
- const player = createPlayer({ events: { onResume } })
241
- player.start()
242
- player.pause()
243
- player.resume()
244
- expect(onResume).toHaveBeenCalled()
245
- })
246
-
247
- it('should emit resume event', () => {
248
- const player = createPlayer()
249
- player.start()
250
- player.pause()
251
- const resumeCallback = jest.fn()
252
- player.on('resume', resumeCallback)
253
- player.resume()
254
- expect(resumeCallback).toHaveBeenCalled()
255
- })
256
-
257
- it('should not resume if not paused', () => {
258
- const player = createPlayer()
259
- player.start()
260
- player.resume()
261
- expect(mockPlayerProcess.kill).not.toHaveBeenCalledWith('SIGCONT')
262
- })
263
- })
264
-
265
- describe('end', () => {
266
- it('should end write stream without stopping player', () => {
267
- const player = createPlayer()
268
- player.start()
269
- player.end()
270
- expect(mockWriteStream.end).toHaveBeenCalled()
271
- })
272
- })
273
-
274
- describe('state queries', () => {
275
- it('isStarted should return false initially', () => {
276
- const player = createPlayer()
277
- expect(player.isStarted()).toBe(false)
278
- })
279
-
280
- it('isPaused should return false initially', () => {
281
- const player = createPlayer()
282
- expect(player.isPaused()).toBe(false)
283
- })
284
-
285
- it('isStopped should return false initially', () => {
286
- const player = createPlayer()
287
- expect(player.isStopped()).toBe(false)
288
- })
289
-
290
- it('getBytesWritten should return 0 initially', () => {
291
- const player = createPlayer()
292
- expect(player.getBytesWritten()).toBe(0)
293
- })
294
- })
295
-
296
- describe('error handling', () => {
297
- it('should have error handler registered', () => {
298
- const player = createPlayer()
299
- player.start()
300
- expect(mockWriteStream.on).toHaveBeenCalledWith('error', expect.any(Function))
301
- })
302
- })
303
- })