@umituz/react-native-sound 1.2.32 → 1.2.34
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.
- package/package.json +1 -1
- package/src/application/SoundCommands.ts +233 -0
- package/src/application/SoundEvents.ts +101 -0
- package/src/application/SoundService.ts +185 -0
- package/src/application/interfaces/IAudioService.ts +1 -1
- package/src/application/interfaces/ISoundCache.ts +4 -6
- package/src/domain/value-objects.ts +128 -0
- package/src/index.ts +35 -26
- package/src/infrastructure/AudioConfig.ts +0 -1
- package/src/infrastructure/AudioRepository.ts +216 -0
- package/src/presentation/SoundStore.ts +156 -0
- package/src/presentation/useSound.ts +183 -0
- package/src/types.ts +1 -2
- package/src/utils.ts +167 -5
- package/src/application/SoundServiceFacade.ts +0 -112
- package/src/application/presenters/SoundPresenter.ts +0 -78
- package/src/application/use-cases/BaseUseCase.ts +0 -36
- package/src/application/use-cases/ClearCacheUseCase.ts +0 -16
- package/src/application/use-cases/PauseSoundUseCase.ts +0 -26
- package/src/application/use-cases/PlaySoundUseCase.ts +0 -106
- package/src/application/use-cases/PreloadSoundUseCase.ts +0 -56
- package/src/application/use-cases/ResumeSoundUseCase.ts +0 -25
- package/src/application/use-cases/SeekSoundUseCase.ts +0 -26
- package/src/application/use-cases/SetRateUseCase.ts +0 -30
- package/src/application/use-cases/SetVolumeUseCase.ts +0 -30
- package/src/application/use-cases/StopSoundUseCase.ts +0 -27
- package/src/domain/entities/SoundState.ts +0 -83
- package/src/domain/value-objects/PlaybackPosition.ts +0 -30
- package/src/domain/value-objects/Rate.ts +0 -26
- package/src/domain/value-objects/SoundId.ts +0 -22
- package/src/domain/value-objects/SoundSource.ts +0 -30
- package/src/domain/value-objects/Volume.ts +0 -26
- package/src/infrastructure/ExpoAudioService.ts +0 -88
- package/src/infrastructure/SoundCache.ts +0 -70
- package/src/presentation/hooks/useSound.ts +0 -47
- package/src/presentation/store.ts +0 -58
|
@@ -1,30 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sound State Entity
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { SoundSource } from '../value-objects/SoundSource';
|
|
6
|
-
import { Volume } from '../value-objects/Volume';
|
|
7
|
-
import { Rate } from '../value-objects/Rate';
|
|
8
|
-
|
|
9
|
-
export interface SoundStateData {
|
|
10
|
-
isPlaying: boolean;
|
|
11
|
-
isBuffering: boolean;
|
|
12
|
-
positionMillis: number;
|
|
13
|
-
durationMillis: number;
|
|
14
|
-
volume: number;
|
|
15
|
-
rate: number;
|
|
16
|
-
isLooping: boolean;
|
|
17
|
-
error: string | null;
|
|
18
|
-
currentSource: SoundSource | null;
|
|
19
|
-
currentId: string | null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class SoundState {
|
|
23
|
-
constructor(private data: SoundStateData) {}
|
|
24
|
-
|
|
25
|
-
isPlaying(): boolean {
|
|
26
|
-
return this.data.isPlaying;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
isBuffering(): boolean {
|
|
30
|
-
return this.data.isBuffering;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
getPosition(): number {
|
|
34
|
-
return this.data.positionMillis;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
getDuration(): number {
|
|
38
|
-
return this.data.durationMillis;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
getVolume(): number {
|
|
42
|
-
return this.data.volume;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
getRate(): number {
|
|
46
|
-
return this.data.rate;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
isLooping(): boolean {
|
|
50
|
-
return this.data.isLooping;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
getError(): string | null {
|
|
54
|
-
return this.data.error;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
getCurrentSource(): SoundSource | null {
|
|
58
|
-
return this.data.currentSource;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
getCurrentId(): string | null {
|
|
62
|
-
return this.data.currentId;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
toData(): SoundStateData {
|
|
66
|
-
return { ...this.data };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
static createEmpty(): SoundState {
|
|
70
|
-
return new SoundState({
|
|
71
|
-
isPlaying: false,
|
|
72
|
-
isBuffering: false,
|
|
73
|
-
positionMillis: 0,
|
|
74
|
-
durationMillis: 0,
|
|
75
|
-
volume: 1.0,
|
|
76
|
-
rate: 1.0,
|
|
77
|
-
isLooping: false,
|
|
78
|
-
error: null,
|
|
79
|
-
currentSource: null,
|
|
80
|
-
currentId: null,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Playback Position Value Object
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { SoundError } from '../errors/SoundError';
|
|
6
|
-
|
|
7
|
-
export class PlaybackPosition {
|
|
8
|
-
private readonly value: number;
|
|
9
|
-
private readonly duration?: number;
|
|
10
|
-
|
|
11
|
-
constructor(value: number, duration?: number) {
|
|
12
|
-
if (!Number.isFinite(value)) {
|
|
13
|
-
throw SoundError.invalidPosition();
|
|
14
|
-
}
|
|
15
|
-
this.value = duration ? this.clamp(value, duration) : value;
|
|
16
|
-
this.duration = duration;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
private clamp(value: number, max: number): number {
|
|
20
|
-
return Math.max(0, Math.min(max, value));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
getValue(): number {
|
|
24
|
-
return this.value;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
getDuration(): number | undefined {
|
|
28
|
-
return this.duration;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rate Value Object
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export class Rate {
|
|
6
|
-
private readonly MIN = 0.5;
|
|
7
|
-
private readonly MAX = 2.0;
|
|
8
|
-
private readonly value: number;
|
|
9
|
-
|
|
10
|
-
constructor(value: number) {
|
|
11
|
-
this.value = this.clamp(value);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
private clamp(value: number): number {
|
|
15
|
-
if (!Number.isFinite(value)) return 1.0;
|
|
16
|
-
return Math.max(this.MIN, Math.min(this.MAX, value));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
getValue(): number {
|
|
20
|
-
return this.value;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
equals(other: Rate): boolean {
|
|
24
|
-
return this.value === other.value;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sound ID Value Object
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export class SoundId {
|
|
6
|
-
private readonly value: string;
|
|
7
|
-
|
|
8
|
-
constructor(value: string) {
|
|
9
|
-
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
10
|
-
throw new Error('Sound id must be a non-empty string');
|
|
11
|
-
}
|
|
12
|
-
this.value = value.trim();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
toString(): string {
|
|
16
|
-
return this.value;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
equals(other: SoundId): boolean {
|
|
20
|
-
return this.value === other.value;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sound Source Value Object
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { SoundError } from '../errors/SoundError';
|
|
6
|
-
|
|
7
|
-
export type SoundSourceValue = number | { uri: string; headers?: Record<string, string> };
|
|
8
|
-
|
|
9
|
-
export class SoundSource {
|
|
10
|
-
private readonly value: SoundSourceValue;
|
|
11
|
-
|
|
12
|
-
constructor(value: SoundSourceValue | null | undefined) {
|
|
13
|
-
if (value === null || value === undefined) {
|
|
14
|
-
throw SoundError.invalidSoundSource();
|
|
15
|
-
}
|
|
16
|
-
this.value = value;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
getValue(): SoundSourceValue {
|
|
20
|
-
return this.value;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
isLocal(): boolean {
|
|
24
|
-
return typeof this.value === 'number';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
isRemote(): boolean {
|
|
28
|
-
return typeof this.value === 'object' && 'uri' in this.value;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Volume Value Object
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export class Volume {
|
|
6
|
-
private readonly MIN = 0;
|
|
7
|
-
private readonly MAX = 1;
|
|
8
|
-
private readonly value: number;
|
|
9
|
-
|
|
10
|
-
constructor(value: number) {
|
|
11
|
-
this.value = this.clamp(value);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
private clamp(value: number): number {
|
|
15
|
-
if (!Number.isFinite(value)) return 1.0;
|
|
16
|
-
return Math.max(this.MIN, Math.min(this.MAX, value));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
getValue(): number {
|
|
20
|
-
return this.value;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
equals(other: Volume): boolean {
|
|
24
|
-
return this.value === other.value;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Expo Audio Service Implementation
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Audio } from 'expo-av';
|
|
6
|
-
import type { IAudioService, PlaybackOptions, PlaybackStatus } from '../application/interfaces/IAudioService';
|
|
7
|
-
import { SoundSourceValue } from '../domain/value-objects/SoundSource';
|
|
8
|
-
import { Logger } from './Logger';
|
|
9
|
-
|
|
10
|
-
export class ExpoAudioService implements IAudioService {
|
|
11
|
-
async createSound(
|
|
12
|
-
source: SoundSourceValue,
|
|
13
|
-
options: PlaybackOptions,
|
|
14
|
-
onStatusUpdate: (status: PlaybackStatus) => void
|
|
15
|
-
): Promise<{ sound: Audio.Sound }> {
|
|
16
|
-
const { sound } = await Audio.Sound.createAsync(
|
|
17
|
-
source,
|
|
18
|
-
{
|
|
19
|
-
shouldPlay: options.shouldPlay,
|
|
20
|
-
isLooping: options.isLooping,
|
|
21
|
-
volume: options.volume,
|
|
22
|
-
rate: options.rate,
|
|
23
|
-
positionMillis: options.positionMillis,
|
|
24
|
-
},
|
|
25
|
-
(status) => onStatusUpdate(this.mapStatus(status))
|
|
26
|
-
);
|
|
27
|
-
Logger.debug('Sound created');
|
|
28
|
-
return { sound };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async play(sound: unknown): Promise<void> {
|
|
32
|
-
await (sound as Audio.Sound).playAsync();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async pause(sound: unknown): Promise<void> {
|
|
36
|
-
await (sound as Audio.Sound).pauseAsync();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async stop(sound: unknown): Promise<void> {
|
|
40
|
-
await (sound as Audio.Sound).stopAsync();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async unload(sound: unknown): Promise<void> {
|
|
44
|
-
await (sound as Audio.Sound).unloadAsync();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async setVolume(sound: unknown, volume: number): Promise<void> {
|
|
48
|
-
await (sound as Audio.Sound).setVolumeAsync(volume);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async setRate(sound: unknown, rate: number): Promise<void> {
|
|
52
|
-
await (sound as Audio.Sound).setRateAsync(rate, false);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async setPosition(sound: unknown, positionMillis: number): Promise<void> {
|
|
56
|
-
await (sound as Audio.Sound).setStatusAsync({ positionMillis });
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async getStatus(sound: unknown): Promise<PlaybackStatus | null> {
|
|
60
|
-
const status = await (sound as Audio.Sound).getStatusAsync();
|
|
61
|
-
return status.isLoaded ? this.mapStatus(status) : null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private mapStatus(status: { isLoaded: boolean } & any): PlaybackStatus {
|
|
65
|
-
if (!status.isLoaded) {
|
|
66
|
-
return {
|
|
67
|
-
isLoaded: false,
|
|
68
|
-
isPlaying: false,
|
|
69
|
-
isBuffering: false,
|
|
70
|
-
positionMillis: 0,
|
|
71
|
-
durationMillis: 0,
|
|
72
|
-
isLooping: false,
|
|
73
|
-
didJustFinish: false,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
isLoaded: true,
|
|
78
|
-
isPlaying: status.isPlaying,
|
|
79
|
-
isBuffering: status.isBuffering,
|
|
80
|
-
positionMillis: status.positionMillis,
|
|
81
|
-
durationMillis: status.durationMillis || 0,
|
|
82
|
-
rate: status.rate,
|
|
83
|
-
isLooping: status.isLooping,
|
|
84
|
-
didJustFinish: status.didJustFinish,
|
|
85
|
-
error: status.error,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sound Cache Implementation
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { ISoundCache, CachedSound } from '../application/interfaces/ISoundCache';
|
|
6
|
-
import { IAudioService } from '../application/interfaces/IAudioService';
|
|
7
|
-
import { Logger } from './Logger';
|
|
8
|
-
|
|
9
|
-
export class SoundCache implements ISoundCache {
|
|
10
|
-
private cache = new Map<string, CachedSound>();
|
|
11
|
-
private readonly maxCacheSize = 3;
|
|
12
|
-
private readonly cacheExpireMs = 5 * 60 * 1000;
|
|
13
|
-
|
|
14
|
-
constructor(private readonly audioService: IAudioService) {}
|
|
15
|
-
|
|
16
|
-
get(id: string): CachedSound | undefined {
|
|
17
|
-
return this.cache.get(id);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
set(id: string, cached: CachedSound): void {
|
|
21
|
-
this.cleanExpired();
|
|
22
|
-
this.enforceLimit();
|
|
23
|
-
this.cache.set(id, cached);
|
|
24
|
-
Logger.debug(`Cached: ${id}`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
delete(id: string): void {
|
|
28
|
-
const cached = this.cache.get(id);
|
|
29
|
-
if (cached) {
|
|
30
|
-
this.audioService.unload(cached.sound).catch((error) => {
|
|
31
|
-
Logger.warn(`Failed to unload cache: ${id}`, error);
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
this.cache.delete(id);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
has(id: string): boolean {
|
|
38
|
-
return this.cache.has(id);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
clear(): void {
|
|
42
|
-
for (const cached of this.cache.values()) {
|
|
43
|
-
this.audioService.unload(cached.sound).catch((error) => {
|
|
44
|
-
Logger.warn('Failed to unload during cache clear', error);
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
this.cache.clear();
|
|
48
|
-
Logger.debug('Cache cleared');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
cleanExpired(): void {
|
|
52
|
-
const now = Date.now();
|
|
53
|
-
for (const [id, cached] of this.cache) {
|
|
54
|
-
if (now - cached.loadedAt > this.cacheExpireMs) {
|
|
55
|
-
this.delete(id);
|
|
56
|
-
Logger.debug(`Expired cache removed: ${id}`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
enforceLimit(): void {
|
|
62
|
-
if (this.cache.size >= this.maxCacheSize) {
|
|
63
|
-
const firstKey = this.cache.keys().next().value;
|
|
64
|
-
if (firstKey) {
|
|
65
|
-
this.delete(firstKey);
|
|
66
|
-
Logger.debug(`Cache limit enforced, removed: ${firstKey}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useSound Hook - Presentation Layer
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { useSoundStore } from '../store';
|
|
6
|
-
import { SoundServiceFacade } from '../../application/SoundServiceFacade';
|
|
7
|
-
|
|
8
|
-
let facade: SoundServiceFacade | null = null;
|
|
9
|
-
|
|
10
|
-
const getFacade = (): SoundServiceFacade => {
|
|
11
|
-
if (!facade) {
|
|
12
|
-
facade = SoundServiceFacade.create(useSoundStore.getState());
|
|
13
|
-
}
|
|
14
|
-
return facade;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const useSound = () => {
|
|
18
|
-
const state = useSoundStore();
|
|
19
|
-
|
|
20
|
-
const facade = getFacade();
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
// State
|
|
24
|
-
isPlaying: state.isPlaying,
|
|
25
|
-
isBuffering: state.isBuffering,
|
|
26
|
-
isLooping: state.isLooping,
|
|
27
|
-
position: state.positionMillis,
|
|
28
|
-
duration: state.durationMillis,
|
|
29
|
-
currentId: state.currentId,
|
|
30
|
-
error: state.error,
|
|
31
|
-
volume: state.volume,
|
|
32
|
-
rate: state.rate,
|
|
33
|
-
|
|
34
|
-
// Actions
|
|
35
|
-
play: (id: string, source: any, options?: any) => facade.play(id, source, options),
|
|
36
|
-
pause: () => facade.pause(),
|
|
37
|
-
resume: () => facade.resume(),
|
|
38
|
-
stop: () => facade.stop(),
|
|
39
|
-
seek: (positionMillis: number) => facade.seek(positionMillis),
|
|
40
|
-
setVolume: (volume: number) => facade.setVolume(volume),
|
|
41
|
-
setRate: (rate: number) => facade.setRate(rate),
|
|
42
|
-
preload: (id: string, source: any, options?: any) => facade.preload(id, source, options),
|
|
43
|
-
unload: () => facade.unload(),
|
|
44
|
-
clearCache: () => facade.clearCache(),
|
|
45
|
-
isCached: (id: string) => facade.isCached(id),
|
|
46
|
-
};
|
|
47
|
-
};
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sound Store - State Management
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { createStore } from '@umituz/react-native-design-system/storage';
|
|
6
|
-
|
|
7
|
-
interface SoundState {
|
|
8
|
-
isPlaying: boolean;
|
|
9
|
-
isBuffering: boolean;
|
|
10
|
-
positionMillis: number;
|
|
11
|
-
durationMillis: number;
|
|
12
|
-
volume: number;
|
|
13
|
-
rate: number;
|
|
14
|
-
isLooping: boolean;
|
|
15
|
-
error: string | null;
|
|
16
|
-
currentId: string | null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface SoundActions {
|
|
20
|
-
setPlaying: (isPlaying: boolean) => void;
|
|
21
|
-
setBuffering: (isBuffering: boolean) => void;
|
|
22
|
-
setProgress: (position: number, duration: number) => void;
|
|
23
|
-
setError: (error: string | null) => void;
|
|
24
|
-
setCurrent: (id: string | null, source: any) => void;
|
|
25
|
-
setVolumeState: (volume: number) => void;
|
|
26
|
-
setRateState: (rate: number) => void;
|
|
27
|
-
setLooping: (isLooping: boolean) => void;
|
|
28
|
-
reset: () => void;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const initialState: SoundState = {
|
|
32
|
-
isPlaying: false,
|
|
33
|
-
isBuffering: false,
|
|
34
|
-
positionMillis: 0,
|
|
35
|
-
durationMillis: 0,
|
|
36
|
-
volume: 1.0,
|
|
37
|
-
rate: 1.0,
|
|
38
|
-
isLooping: false,
|
|
39
|
-
error: null,
|
|
40
|
-
currentId: null,
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export const useSoundStore = createStore<SoundState, SoundActions>({
|
|
44
|
-
name: 'sound-store',
|
|
45
|
-
initialState,
|
|
46
|
-
persist: false,
|
|
47
|
-
actions: (set) => ({
|
|
48
|
-
setPlaying: (isPlaying) => set({ isPlaying }),
|
|
49
|
-
setBuffering: (isBuffering) => set({ isBuffering }),
|
|
50
|
-
setProgress: (position, duration) => set({ positionMillis: position, durationMillis: duration }),
|
|
51
|
-
setError: (error) => set({ error }),
|
|
52
|
-
setCurrent: (currentId) => set({ currentId }),
|
|
53
|
-
setVolumeState: (volume) => set({ volume }),
|
|
54
|
-
setRateState: (rate) => set({ rate }),
|
|
55
|
-
setLooping: (isLooping) => set({ isLooping }),
|
|
56
|
-
reset: () => set(initialState),
|
|
57
|
-
}),
|
|
58
|
-
});
|