@umituz/react-native-sound 1.2.30 → 1.2.32

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 (33) hide show
  1. package/package.json +1 -5
  2. package/src/application/SoundServiceFacade.ts +112 -0
  3. package/src/application/interfaces/IAudioService.ts +42 -0
  4. package/src/application/interfaces/ISoundCache.ts +21 -0
  5. package/src/application/presenters/SoundPresenter.ts +78 -0
  6. package/src/application/use-cases/BaseUseCase.ts +36 -0
  7. package/src/application/use-cases/ClearCacheUseCase.ts +16 -0
  8. package/src/application/use-cases/PauseSoundUseCase.ts +26 -0
  9. package/src/application/use-cases/PlaySoundUseCase.ts +106 -0
  10. package/src/application/use-cases/PreloadSoundUseCase.ts +56 -0
  11. package/src/application/use-cases/ResumeSoundUseCase.ts +25 -0
  12. package/src/application/use-cases/SeekSoundUseCase.ts +26 -0
  13. package/src/application/use-cases/SetRateUseCase.ts +30 -0
  14. package/src/application/use-cases/SetVolumeUseCase.ts +30 -0
  15. package/src/application/use-cases/StopSoundUseCase.ts +27 -0
  16. package/src/domain/entities/SoundState.ts +83 -0
  17. package/src/domain/errors/SoundError.ts +44 -0
  18. package/src/domain/value-objects/PlaybackPosition.ts +30 -0
  19. package/src/domain/value-objects/Rate.ts +26 -0
  20. package/src/domain/value-objects/SoundId.ts +22 -0
  21. package/src/domain/value-objects/SoundSource.ts +30 -0
  22. package/src/domain/value-objects/Volume.ts +26 -0
  23. package/src/index.ts +36 -6
  24. package/src/infrastructure/AudioConfig.ts +25 -0
  25. package/src/infrastructure/ExpoAudioService.ts +88 -0
  26. package/src/infrastructure/Logger.ts +23 -0
  27. package/src/infrastructure/SoundCache.ts +70 -0
  28. package/src/presentation/hooks/useSound.ts +47 -0
  29. package/src/{store.ts → presentation/store.ts} +22 -9
  30. package/src/types.ts +6 -5
  31. package/src/utils.ts +13 -17
  32. package/src/AudioManager.ts +0 -338
  33. package/src/useSound.ts +0 -107
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-sound",
3
- "version": "1.2.30",
3
+ "version": "1.2.32",
4
4
  "description": "Universal sound playback and caching library for React Native apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -35,10 +35,6 @@
35
35
  "devDependencies": {
36
36
  "@gorhom/bottom-sheet": "^5.2.8",
37
37
  "@react-native-async-storage/async-storage": "^2.2.0",
38
- "@react-navigation/bottom-tabs": "^7.15.5",
39
- "@react-navigation/elements": "^2.9.10",
40
- "@react-navigation/native": "^7.1.33",
41
- "@react-navigation/stack": "^7.8.5",
42
38
  "@tanstack/react-query": "^5.90.21",
43
39
  "@types/node": "^25.5.0",
44
40
  "@types/react": "~19.1.10",
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Sound Service Facade - Simplified Interface
3
+ */
4
+
5
+ import { AudioConfig } from '../infrastructure/AudioConfig';
6
+ import { ExpoAudioService } from '../infrastructure/ExpoAudioService';
7
+ import { SoundCache } from '../infrastructure/SoundCache';
8
+ import { SoundPresenter } from './presenters/SoundPresenter';
9
+ import { PlaySoundUseCase } from './use-cases/PlaySoundUseCase';
10
+ import { PauseSoundUseCase } from './use-cases/PauseSoundUseCase';
11
+ import { ResumeSoundUseCase } from './use-cases/ResumeSoundUseCase';
12
+ import { StopSoundUseCase } from './use-cases/StopSoundUseCase';
13
+ import { SeekSoundUseCase } from './use-cases/SeekSoundUseCase';
14
+ import { SetVolumeUseCase } from './use-cases/SetVolumeUseCase';
15
+ import { SetRateUseCase } from './use-cases/SetRateUseCase';
16
+ import { PreloadSoundUseCase } from './use-cases/PreloadSoundUseCase';
17
+ import { ClearCacheUseCase } from './use-cases/ClearCacheUseCase';
18
+
19
+ export class SoundServiceFacade {
20
+ private static instance: SoundServiceFacade | null = null;
21
+
22
+ private readonly audioService = new ExpoAudioService();
23
+ private readonly cache = new SoundCache(this.audioService);
24
+ private readonly presenter: SoundPresenter;
25
+
26
+ private readonly playUseCase: PlaySoundUseCase;
27
+ private readonly pauseUseCase: PauseSoundUseCase;
28
+ private readonly resumeUseCase: ResumeSoundUseCase;
29
+ private readonly stopUseCase: StopSoundUseCase;
30
+ private readonly seekUseCase: SeekSoundUseCase;
31
+ private readonly setVolumeUseCase: SetVolumeUseCase;
32
+ private readonly setRateUseCase: SetRateUseCase;
33
+ private readonly preloadUseCase: PreloadSoundUseCase;
34
+ private readonly clearCacheUseCase: ClearCacheUseCase;
35
+
36
+ private constructor(store: SoundPresenter['store']) {
37
+ AudioConfig.configure().catch(() => {});
38
+
39
+ this.presenter = new SoundPresenter(store);
40
+
41
+ this.playUseCase = new PlaySoundUseCase(this.audioService, this.cache, this.presenter);
42
+ this.pauseUseCase = new PauseSoundUseCase(this.audioService, this.presenter);
43
+ this.resumeUseCase = new ResumeSoundUseCase(this.audioService, this.presenter);
44
+ this.stopUseCase = new StopSoundUseCase(this.audioService, this.presenter);
45
+ this.seekUseCase = new SeekSoundUseCase(this.audioService);
46
+ this.setVolumeUseCase = new SetVolumeUseCase(this.audioService, this.presenter);
47
+ this.setRateUseCase = new SetRateUseCase(this.audioService, this.presenter);
48
+ this.preloadUseCase = new PreloadSoundUseCase(this.audioService, this.cache);
49
+ this.clearCacheUseCase = new ClearCacheUseCase(this.cache);
50
+ }
51
+
52
+ static create(store: SoundPresenter['store']): SoundServiceFacade {
53
+ if (!this.instance) {
54
+ this.instance = new SoundServiceFacade(store);
55
+ }
56
+ return this.instance;
57
+ }
58
+
59
+ async play(id: string, source: any, options?: any): Promise<void> {
60
+ await this.playUseCase.execute({ id, source, ...options });
61
+ }
62
+
63
+ async pause(): Promise<void> {
64
+ const sound = this.playUseCase.getCurrentSound();
65
+ await this.pauseUseCase.execute({ sound });
66
+ }
67
+
68
+ async resume(): Promise<void> {
69
+ const sound = this.playUseCase.getCurrentSound();
70
+ await this.resumeUseCase.execute({ sound });
71
+ }
72
+
73
+ async stop(): Promise<void> {
74
+ const sound = this.playUseCase.getCurrentSound();
75
+ await this.stopUseCase.execute({ sound });
76
+ }
77
+
78
+ async seek(positionMillis: number): Promise<void> {
79
+ const sound = this.playUseCase.getCurrentSound();
80
+ await this.seekUseCase.execute({ sound, positionMillis });
81
+ }
82
+
83
+ async setVolume(volume: number): Promise<void> {
84
+ const sound = this.playUseCase.getCurrentSound();
85
+ await this.setVolumeUseCase.execute({ sound, volume });
86
+ }
87
+
88
+ async setRate(rate: number): Promise<void> {
89
+ const sound = this.playUseCase.getCurrentSound();
90
+ await this.setRateUseCase.execute({ sound, rate });
91
+ }
92
+
93
+ async preload(id: string, source: any, options?: any): Promise<void> {
94
+ await this.preloadUseCase.execute({ id, source, ...options });
95
+ }
96
+
97
+ async unload(): Promise<void> {
98
+ await this.playUseCase.unload();
99
+ }
100
+
101
+ clearCache(): void {
102
+ this.clearCacheUseCase.execute();
103
+ }
104
+
105
+ isCached(id: string): boolean {
106
+ return this.cache.has(id);
107
+ }
108
+
109
+ getCurrentId(): string | null {
110
+ return this.playUseCase.getCurrentId();
111
+ }
112
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Audio Service Interface
3
+ */
4
+
5
+ import type { AVPlaybackStatus } from 'expo-av';
6
+ import { SoundSourceValue } from '../../domain/value-objects/SoundSource';
7
+
8
+ export interface PlaybackStatus {
9
+ isLoaded: boolean;
10
+ isPlaying: boolean;
11
+ isBuffering: boolean;
12
+ positionMillis: number;
13
+ durationMillis: number;
14
+ rate?: number;
15
+ isLooping: boolean;
16
+ didJustFinish: boolean;
17
+ error?: string;
18
+ }
19
+
20
+ export interface PlaybackOptions {
21
+ shouldPlay: boolean;
22
+ isLooping: boolean;
23
+ volume: number;
24
+ rate: number;
25
+ positionMillis?: number;
26
+ }
27
+
28
+ export interface IAudioService {
29
+ createSound(
30
+ source: SoundSourceValue,
31
+ options: PlaybackOptions,
32
+ onStatusUpdate: (status: PlaybackStatus) => void
33
+ ): Promise<{ sound: unknown }>;
34
+ play(sound: unknown): Promise<void>;
35
+ pause(sound: unknown): Promise<void>;
36
+ stop(sound: unknown): Promise<void>;
37
+ unload(sound: unknown): Promise<void>;
38
+ setVolume(sound: unknown, volume: number): Promise<void>;
39
+ setRate(sound: unknown, rate: number): Promise<void>;
40
+ setPosition(sound: unknown, positionMillis: number): Promise<void>;
41
+ getStatus(sound: unknown): Promise<PlaybackStatus | null>;
42
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Sound Cache Interface
3
+ */
4
+
5
+ import { SoundSourceValue } from '../../domain/value-objects/SoundSource';
6
+
7
+ export interface CachedSound {
8
+ sound: unknown;
9
+ source: SoundSourceValue;
10
+ loadedAt: number;
11
+ }
12
+
13
+ export interface ISoundCache {
14
+ get(id: string): CachedSound | undefined;
15
+ set(id: string, cached: CachedSound): void;
16
+ delete(id: string): void;
17
+ has(id: string): boolean;
18
+ clear(): void;
19
+ cleanExpired(): void;
20
+ enforceLimit(): void;
21
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Sound Presenter - Manages Store Updates
3
+ */
4
+
5
+ import { PlaybackStatus } from '../interfaces/IAudioService';
6
+ import { SoundSourceValue } from '../../domain/value-objects/SoundSource';
7
+ import { Logger } from '../../infrastructure/Logger';
8
+
9
+ export interface SoundStore {
10
+ setPlaying: (isPlaying: boolean) => void;
11
+ setBuffering: (isBuffering: boolean) => void;
12
+ setProgress: (position: number, duration: number) => void;
13
+ setError: (error: string | null) => void;
14
+ setCurrent: (id: string | null, source: SoundSourceValue | null) => void;
15
+ setVolumeState: (volume: number) => void;
16
+ setRateState: (rate: number) => void;
17
+ setLooping: (isLooping: boolean) => void;
18
+ reset: () => void;
19
+ }
20
+
21
+ export class SoundPresenter {
22
+ constructor(private readonly store: SoundStore) {}
23
+
24
+ onPlaybackUpdate(status: PlaybackStatus): void {
25
+ if (!status.isLoaded && status.error) {
26
+ this.store.setError(status.error);
27
+ this.store.setPlaying(false);
28
+ return;
29
+ }
30
+
31
+ if (status.isLoaded) {
32
+ this.store.setPlaying(status.isPlaying);
33
+ this.store.setBuffering(status.isBuffering);
34
+ this.store.setProgress(status.positionMillis, status.durationMillis);
35
+
36
+ if (status.rate !== undefined) {
37
+ this.store.setRateState(status.rate);
38
+ }
39
+
40
+ if (status.didJustFinish && !status.isLooping) {
41
+ this.store.setPlaying(false);
42
+ this.store.setProgress(status.durationMillis, status.durationMillis);
43
+ }
44
+ }
45
+ }
46
+
47
+ onPlaybackStart(id: string, source: SoundSourceValue, options: { volume: number; rate: number; isLooping: boolean }): void {
48
+ this.store.setCurrent(id, source);
49
+ this.store.setError(null);
50
+ this.store.setVolumeState(options.volume);
51
+ this.store.setRateState(options.rate);
52
+ this.store.setLooping(options.isLooping);
53
+ Logger.debug(`Playback started: ${id}`);
54
+ }
55
+
56
+ onPlaybackError(error: string): void {
57
+ this.store.setError(error);
58
+ Logger.error('Playback error', error);
59
+ }
60
+
61
+ onPlaybackStop(): void {
62
+ this.store.setPlaying(false);
63
+ Logger.debug('Playback stopped');
64
+ }
65
+
66
+ onVolumeChange(volume: number): void {
67
+ this.store.setVolumeState(volume);
68
+ }
69
+
70
+ onRateChange(rate: number): void {
71
+ this.store.setRateState(rate);
72
+ }
73
+
74
+ onReset(): void {
75
+ this.store.reset();
76
+ Logger.debug('State reset');
77
+ }
78
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Base Use Case - Common Error Handling
3
+ */
4
+
5
+ import { SoundError } from '../../domain/errors/SoundError';
6
+ import { Logger } from '../../infrastructure/Logger';
7
+
8
+ export abstract class BaseUseCase<TInput, TOutput> {
9
+ async execute(input: TInput): Promise<TOutput> {
10
+ try {
11
+ return await this.executeImpl(input);
12
+ } catch (error) {
13
+ if (error instanceof SoundError) {
14
+ throw error;
15
+ }
16
+ throw SoundError.playbackFailed(error);
17
+ }
18
+ }
19
+
20
+ protected abstract executeImpl(input: TInput): Promise<TOutput>;
21
+ }
22
+
23
+ export abstract class BaseVoidUseCase<TInput> {
24
+ async execute(input: TInput): Promise<void> {
25
+ try {
26
+ await this.executeImpl(input);
27
+ } catch (error) {
28
+ if (error instanceof SoundError) {
29
+ throw error;
30
+ }
31
+ throw SoundError.playbackFailed(error);
32
+ }
33
+ }
34
+
35
+ protected abstract executeImpl(input: TInput): Promise<void>;
36
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Clear Cache Use Case
3
+ */
4
+
5
+ import { BaseVoidUseCase } from './BaseUseCase';
6
+ import type { ISoundCache } from '../interfaces/ISoundCache';
7
+
8
+ export class ClearCacheUseCase extends BaseVoidUseCase<void> {
9
+ constructor(private readonly cache: ISoundCache) {
10
+ super();
11
+ }
12
+
13
+ protected async executeImpl(): Promise<void> {
14
+ this.cache.clear();
15
+ }
16
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Pause Sound Use Case
3
+ */
4
+
5
+ import { BaseVoidUseCase } from './BaseUseCase';
6
+ import type { IAudioService } from '../interfaces/IAudioService';
7
+ import { SoundPresenter } from '../presenters/SoundPresenter';
8
+
9
+ interface PauseSoundInput {
10
+ sound: unknown;
11
+ }
12
+
13
+ export class PauseSoundUseCase extends BaseVoidUseCase<PauseSoundInput> {
14
+ constructor(
15
+ private readonly audioService: IAudioService,
16
+ private readonly presenter: SoundPresenter
17
+ ) {
18
+ super();
19
+ }
20
+
21
+ protected async executeImpl(input: PauseSoundInput): Promise<void> {
22
+ if (!input.sound) return;
23
+ await this.audioService.pause(input.sound);
24
+ this.presenter.onPlaybackStop();
25
+ }
26
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Play Sound Use Case
3
+ */
4
+
5
+ import { BaseVoidUseCase } from './BaseUseCase';
6
+ import { SoundId } from '../../domain/value-objects/SoundId';
7
+ import { SoundSource } from '../../domain/value-objects/SoundSource';
8
+ import { Volume } from '../../domain/value-objects/Volume';
9
+ import { Rate } from '../../domain/value-objects/Rate';
10
+ import type { IAudioService } from '../interfaces/IAudioService';
11
+ import type { ISoundCache } from '../interfaces/ISoundCache';
12
+ import { SoundPresenter } from '../presenters/SoundPresenter';
13
+
14
+ interface PlaySoundInput {
15
+ id: string;
16
+ source: number | { uri: string; headers?: Record<string, string> };
17
+ volume?: number;
18
+ rate?: number;
19
+ isLooping?: boolean;
20
+ positionMillis?: number;
21
+ }
22
+
23
+ export class PlaySoundUseCase extends BaseVoidUseCase<PlaySoundInput> {
24
+ private currentSound: unknown | null = null;
25
+ private currentId: string | null = null;
26
+
27
+ constructor(
28
+ private readonly audioService: IAudioService,
29
+ private readonly cache: ISoundCache,
30
+ private readonly presenter: SoundPresenter
31
+ ) {
32
+ super();
33
+ }
34
+
35
+ protected async executeImpl(input: PlaySoundInput): Promise<void> {
36
+ const soundId = new SoundId(input.id);
37
+ const soundSource = new SoundSource(input.source);
38
+ const volume = new Volume(input.volume ?? 1.0);
39
+ const rate = new Rate(input.rate ?? 1.0);
40
+
41
+ // Toggle if same sound
42
+ if (this.currentId === soundId.toString() && this.currentSound) {
43
+ const status = await this.audioService.getStatus(this.currentSound);
44
+ if (status?.isPlaying) {
45
+ await this.audioService.pause(this.currentSound);
46
+ this.presenter.onPlaybackStop();
47
+ return;
48
+ }
49
+ }
50
+
51
+ // Unload current
52
+ if (this.currentSound) {
53
+ await this.audioService.unload(this.currentSound);
54
+ }
55
+
56
+ // Try cache first
57
+ const cached = this.cache.get(soundId.toString());
58
+ if (cached) {
59
+ this.currentSound = cached.sound;
60
+ this.cache.delete(soundId.toString());
61
+ } else {
62
+ const { sound } = await this.audioService.createSound(
63
+ soundSource.getValue(),
64
+ {
65
+ shouldPlay: true,
66
+ isLooping: input.isLooping ?? false,
67
+ volume: volume.getValue(),
68
+ rate: rate.getValue(),
69
+ positionMillis: input.positionMillis,
70
+ },
71
+ (status) => this.presenter.onPlaybackUpdate(status)
72
+ );
73
+ this.currentSound = sound;
74
+ }
75
+
76
+ await this.audioService.play(this.currentSound);
77
+ this.currentId = soundId.toString();
78
+
79
+ this.presenter.onPlaybackStart(
80
+ soundId.toString(),
81
+ soundSource.getValue(),
82
+ {
83
+ volume: volume.getValue(),
84
+ rate: rate.getValue(),
85
+ isLooping: input.isLooping ?? false,
86
+ }
87
+ );
88
+ }
89
+
90
+ getCurrentSound(): unknown | null {
91
+ return this.currentSound;
92
+ }
93
+
94
+ getCurrentId(): string | null {
95
+ return this.currentId;
96
+ }
97
+
98
+ async unload(): Promise<void> {
99
+ if (this.currentSound) {
100
+ await this.audioService.unload(this.currentSound);
101
+ }
102
+ this.currentSound = null;
103
+ this.currentId = null;
104
+ this.presenter.onReset();
105
+ }
106
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Preload Sound Use Case
3
+ */
4
+
5
+ import { BaseVoidUseCase } from './BaseUseCase';
6
+ import { SoundId } from '../../domain/value-objects/SoundId';
7
+ import { SoundSource } from '../../domain/value-objects/SoundSource';
8
+ import { Volume } from '../../domain/value-objects/Volume';
9
+ import { Rate } from '../../domain/value-objects/Rate';
10
+ import type { IAudioService } from '../interfaces/IAudioService';
11
+ import type { ISoundCache } from '../interfaces/ISoundCache';
12
+
13
+ interface PreloadSoundInput {
14
+ id: string;
15
+ source: number | { uri: string; headers?: Record<string, string> };
16
+ volume?: number;
17
+ rate?: number;
18
+ isLooping?: boolean;
19
+ }
20
+
21
+ export class PreloadSoundUseCase extends BaseVoidUseCase<PreloadSoundInput> {
22
+ constructor(
23
+ private readonly audioService: IAudioService,
24
+ private readonly cache: ISoundCache
25
+ ) {
26
+ super();
27
+ }
28
+
29
+ protected async executeImpl(input: PreloadSoundInput): Promise<void> {
30
+ const soundId = new SoundId(input.id);
31
+ const soundSource = new SoundSource(input.source);
32
+ const volume = new Volume(input.volume ?? 1.0);
33
+ const rate = new Rate(input.rate ?? 1.0);
34
+
35
+ if (this.cache.has(soundId.toString())) {
36
+ return;
37
+ }
38
+
39
+ const { sound } = await this.audioService.createSound(
40
+ soundSource.getValue(),
41
+ {
42
+ shouldPlay: false,
43
+ isLooping: input.isLooping ?? false,
44
+ volume: volume.getValue(),
45
+ rate: rate.getValue(),
46
+ },
47
+ () => {}
48
+ );
49
+
50
+ this.cache.set(soundId.toString(), {
51
+ sound,
52
+ source: soundSource.getValue(),
53
+ loadedAt: Date.now(),
54
+ });
55
+ }
56
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Resume Sound Use Case
3
+ */
4
+
5
+ import { BaseVoidUseCase } from './BaseUseCase';
6
+ import type { IAudioService } from '../interfaces/IAudioService';
7
+ import { SoundPresenter } from '../presenters/SoundPresenter';
8
+
9
+ interface ResumeSoundInput {
10
+ sound: unknown;
11
+ }
12
+
13
+ export class ResumeSoundUseCase extends BaseVoidUseCase<ResumeSoundInput> {
14
+ constructor(
15
+ private readonly audioService: IAudioService,
16
+ private readonly presenter: SoundPresenter
17
+ ) {
18
+ super();
19
+ }
20
+
21
+ protected async executeImpl(input: ResumeSoundInput): Promise<void> {
22
+ if (!input.sound) return;
23
+ await this.audioService.play(input.sound);
24
+ }
25
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Seek Sound Use Case
3
+ */
4
+
5
+ import { BaseVoidUseCase } from './BaseUseCase';
6
+ import { PlaybackPosition } from '../../domain/value-objects/PlaybackPosition';
7
+ import type { IAudioService } from '../interfaces/IAudioService';
8
+
9
+ interface SeekSoundInput {
10
+ sound: unknown;
11
+ positionMillis: number;
12
+ durationMillis?: number;
13
+ }
14
+
15
+ export class SeekSoundUseCase extends BaseVoidUseCase<SeekSoundInput> {
16
+ constructor(private readonly audioService: IAudioService) {
17
+ super();
18
+ }
19
+
20
+ protected async executeImpl(input: SeekSoundInput): Promise<void> {
21
+ if (!input.sound) return;
22
+
23
+ const position = new PlaybackPosition(input.positionMillis, input.durationMillis);
24
+ await this.audioService.setPosition(input.sound, position.getValue());
25
+ }
26
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Set Rate Use Case
3
+ */
4
+
5
+ import { BaseVoidUseCase } from './BaseUseCase';
6
+ import { Rate } from '../../domain/value-objects/Rate';
7
+ import type { IAudioService } from '../interfaces/IAudioService';
8
+ import { SoundPresenter } from '../presenters/SoundPresenter';
9
+
10
+ interface SetRateInput {
11
+ sound: unknown;
12
+ rate: number;
13
+ }
14
+
15
+ export class SetRateUseCase extends BaseVoidUseCase<SetRateInput> {
16
+ constructor(
17
+ private readonly audioService: IAudioService,
18
+ private readonly presenter: SoundPresenter
19
+ ) {
20
+ super();
21
+ }
22
+
23
+ protected async executeImpl(input: SetRateInput): Promise<void> {
24
+ if (!input.sound) return;
25
+
26
+ const rate = new Rate(input.rate);
27
+ await this.audioService.setRate(input.sound, rate.getValue());
28
+ this.presenter.onRateChange(rate.getValue());
29
+ }
30
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Set Volume Use Case
3
+ */
4
+
5
+ import { BaseVoidUseCase } from './BaseUseCase';
6
+ import { Volume } from '../../domain/value-objects/Volume';
7
+ import type { IAudioService } from '../interfaces/IAudioService';
8
+ import { SoundPresenter } from '../presenters/SoundPresenter';
9
+
10
+ interface SetVolumeInput {
11
+ sound: unknown;
12
+ volume: number;
13
+ }
14
+
15
+ export class SetVolumeUseCase extends BaseVoidUseCase<SetVolumeInput> {
16
+ constructor(
17
+ private readonly audioService: IAudioService,
18
+ private readonly presenter: SoundPresenter
19
+ ) {
20
+ super();
21
+ }
22
+
23
+ protected async executeImpl(input: SetVolumeInput): Promise<void> {
24
+ if (!input.sound) return;
25
+
26
+ const volume = new Volume(input.volume);
27
+ await this.audioService.setVolume(input.sound, volume.getValue());
28
+ this.presenter.onVolumeChange(volume.getValue());
29
+ }
30
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Stop Sound Use Case
3
+ */
4
+
5
+ import { BaseVoidUseCase } from './BaseUseCase';
6
+ import type { IAudioService } from '../interfaces/IAudioService';
7
+ import { SoundPresenter } from '../presenters/SoundPresenter';
8
+
9
+ interface StopSoundInput {
10
+ sound: unknown;
11
+ }
12
+
13
+ export class StopSoundUseCase extends BaseVoidUseCase<StopSoundInput> {
14
+ constructor(
15
+ private readonly audioService: IAudioService,
16
+ private readonly presenter: SoundPresenter
17
+ ) {
18
+ super();
19
+ }
20
+
21
+ protected async executeImpl(input: StopSoundInput): Promise<void> {
22
+ if (!input.sound) return;
23
+ await this.audioService.stop(input.sound);
24
+ this.presenter.onPlaybackStop();
25
+ this.presenter.onReset();
26
+ }
27
+ }