diff --git a/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicEvents.kt b/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicEvents.kt index a785c9092..60f159866 100644 --- a/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicEvents.kt +++ b/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicEvents.kt @@ -45,6 +45,7 @@ class MusicEvents(private val reactContext: ReactContext) : BroadcastReceiver() const val PLAYBACK_METADATA = "playback-metadata-received" const val PLAYBACK_PROGRESS_UPDATED = "playback-progress-updated" const val PLAYBACK_ERROR = "playback-error" + const val EQUALIZER_CHANGED = "equalizer-changed" const val EVENT_INTENT = "com.doublesymmetry.trackplayer.event" } diff --git a/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt b/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt index 1b057706a..0c930e2c5 100644 --- a/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt +++ b/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt @@ -615,4 +615,35 @@ class MusicModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaM if (verifyServiceBoundOrReject(callback)) return@launch callback.resolve(Arguments.fromBundle(musicService.getPlayerStateBundle(musicService.state))) } + + @ReactMethod + fun getEqualizerSettings(callback: Promise) = scope.launch { + if (verifyServiceBoundOrReject(callback)) return@launch + callback.resolve(Arguments.fromBundle(musicService.getEqualizerSettings())) + } + + @ReactMethod + fun setEqualizerEnabled(enabled: Boolean, callback: Promise) = scope.launch { + if (verifyServiceBoundOrReject(callback)) return@launch + musicService.setEqualizerEnabled(enabled) + callback.resolve(null) + } + + @ReactMethod + fun setEqualizerPreset(preset: String, callback: Promise) = scope.launch { + if (verifyServiceBoundOrReject(callback)) return@launch + musicService.setEqualizerPreset(preset) + callback.resolve(null) + } + + @ReactMethod + fun setEqualizerLevels(data: ReadableArray, callback: Promise) = scope.launch { + if (verifyServiceBoundOrReject(callback)) return@launch + val levels = ShortArray(data.size()) + for (i in 0 until data.size()) { + levels[i] = data.getInt(i).toShort() + } + musicService.setEqualizerLevels(levels) + callback.resolve(null) + } } diff --git a/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt b/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt index aa7834cb9..5b49710f1 100644 --- a/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt +++ b/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt @@ -2,7 +2,6 @@ package com.doublesymmetry.trackplayer.service import android.app.PendingIntent import android.content.Intent -import android.content.ServiceConnection import android.net.Uri import android.os.Binder import android.os.Build @@ -337,6 +336,58 @@ class MusicService : HeadlessJsTaskService() { player.prepare() } + private fun emitEqualizerChangedEvent() { + emit(MusicEvents.EQUALIZER_CHANGED, getEqualizerSettings()) + } + + @MainThread + fun getEqualizerSettings(): Bundle { + return Bundle().apply{ + this.putIntArray( + "bandLevels", + player.equalizer.properties.bandLevels.map { it.toInt() }.toIntArray() + ) + this.putInt("bandCount", player.equalizer.numberOfBands.toInt()) + this.putStringArray("presets", player.getEqualizerPresets()) + val bandCount = player.equalizer.numberOfBands.toInt() + var centerBandFrequencies = Array(bandCount) {0} + for (i in 0 until bandCount) { + centerBandFrequencies[i] = player.equalizer.getCenterFreq(i.toShort()) + } + this.putIntArray("centerBandFrequencies", centerBandFrequencies.toIntArray()) + this.putInt("lowerBandLevelLimit", player.equalizer.bandLevelRange[0].toInt()) + this.putInt("upperBandLevelLimit", player.equalizer.bandLevelRange[1].toInt()) + if (player.equalizer.properties.curPreset >= 0) { + this.putString( + "activePreset", + player.equalizer.getPresetName(player.equalizer.properties.curPreset) + ) + } + this.putBoolean("enabled", player.equalizer.enabled) + } + } + + @MainThread + fun setEqualizerEnabled(enabled: Boolean) { + if (player.setEqualizerEnabled(enabled)) { + emitEqualizerChangedEvent() + } + } + + @MainThread + fun setEqualizerPreset(name: String) { + if (player.setEqualizerPreset(name)) { + emitEqualizerChangedEvent() + } + } + + @MainThread + fun setEqualizerLevels(levels: ShortArray) { + if (player.setEqualizerLevels(levels)) { + emitEqualizerChangedEvent() + } + } + @MainThread fun getCurrentTrackIndex(): Int = player.currentIndex diff --git a/src/constants/Event.ts b/src/constants/Event.ts index a81853de6..3c1743b7a 100644 --- a/src/constants/Event.ts +++ b/src/constants/Event.ts @@ -1,4 +1,6 @@ export enum Event { + /** Fired when the equalizer settings have changed. */ + EqualizerChanged = 'equalizer-changed', /** Fired when the state of the player changes. */ PlaybackState = 'playback-state', /** Fired when a playback error occurs. */ diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 73d309019..9b4d06bd8 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,5 +1,6 @@ export * from './useActiveTrack'; -export * from './usePlayWhenReady'; +export * from './useEqualizer'; export * from './usePlaybackState'; +export * from './usePlayWhenReady'; export * from './useProgress'; export * from './useTrackPlayerEvents'; diff --git a/src/hooks/useEqualizer.ts b/src/hooks/useEqualizer.ts new file mode 100644 index 000000000..c0c5fe473 --- /dev/null +++ b/src/hooks/useEqualizer.ts @@ -0,0 +1,36 @@ +import { useEffect, useState } from 'react'; +import { Event } from '../constants'; +import { EqualizerSettings } from '../interfaces'; +import { addEventListener, getEqualizerSettings } from '../trackPlayer'; + +export const useEqualizer = (): EqualizerSettings | undefined => { + const [equalizerSettings, setEqualizerSettings] = useState< + EqualizerSettings | undefined + >(); + useEffect(() => { + let mounted = true; + + getEqualizerSettings() + .then((fetchedEqualizerSettings) => { + if (!mounted) return; + // Only set if it wasn't already set by the listener below: + setEqualizerSettings((currentEqualizerSettings) => + currentEqualizerSettings ? currentEqualizerSettings : fetchedEqualizerSettings + ); + }) + .catch(() => { + /** only throws while you haven't yet setup, ignore failure. */ + }); + + const sub = addEventListener(Event.EqualizerChanged, (equalizerSettings) => { + setEqualizerSettings(equalizerSettings); + }); + + return () => { + mounted = false; + sub.remove(); + }; + }, []); + + return equalizerSettings; +}; diff --git a/src/interfaces/EqualizerSettings.ts b/src/interfaces/EqualizerSettings.ts new file mode 100644 index 000000000..42762e4ac --- /dev/null +++ b/src/interfaces/EqualizerSettings.ts @@ -0,0 +1,12 @@ + + +export type EqualizerSettings = { + activePreset?: string; + bandCount: number; + bandLevels: number[]; + centerBandFrequencies: number[]; + enabled: boolean; + lowerBandLevelLimit: number; + presets: string[]; + upperBandLevelLimit: number; +} diff --git a/src/interfaces/events/EventPayloadByEvent.ts b/src/interfaces/events/EventPayloadByEvent.ts index 1f590ff0d..c99d82c80 100644 --- a/src/interfaces/events/EventPayloadByEvent.ts +++ b/src/interfaces/events/EventPayloadByEvent.ts @@ -1,23 +1,25 @@ import { Event } from '../../constants'; +import { EqualizerSettings } from '../EqualizerSettings'; import type { PlaybackState } from '../PlaybackState'; -import type { PlaybackErrorEvent } from './PlaybackErrorEvent'; -import type { PlaybackQueueEndedEvent } from './PlaybackQueueEndedEvent'; -import type { PlaybackTrackChangedEvent } from './PlaybackTrackChangedEvent'; import type { PlaybackActiveTrackChangedEvent } from './PlaybackActiveTrackChangedEvent'; +import type { PlaybackErrorEvent } from './PlaybackErrorEvent'; import type { PlaybackMetadataReceivedEvent } from './PlaybackMetadataReceivedEvent'; import type { PlaybackPlayWhenReadyChangedEvent } from './PlaybackPlayWhenReadyChangedEvent'; import type { PlaybackProgressUpdatedEvent } from './PlaybackProgressUpdatedEvent'; +import type { PlaybackQueueEndedEvent } from './PlaybackQueueEndedEvent'; +import type { PlaybackTrackChangedEvent } from './PlaybackTrackChangedEvent'; +import type { RemoteDuckEvent } from './RemoteDuckEvent'; +import type { RemoteJumpBackwardEvent } from './RemoteJumpBackwardEvent'; +import type { RemoteJumpForwardEvent } from './RemoteJumpForwardEvent'; import type { RemotePlayIdEvent } from './RemotePlayIdEvent'; import type { RemotePlaySearchEvent } from './RemotePlaySearchEvent'; -import type { RemoteSkipEvent } from './RemoteSkipEvent'; -import type { RemoteJumpForwardEvent } from './RemoteJumpForwardEvent'; -import type { RemoteJumpBackwardEvent } from './RemoteJumpBackwardEvent'; import type { RemoteSeekEvent } from './RemoteSeekEvent'; import type { RemoteSetRatingEvent } from './RemoteSetRatingEvent'; -import type { RemoteDuckEvent } from './RemoteDuckEvent'; +import type { RemoteSkipEvent } from './RemoteSkipEvent'; export interface EventPayloadByEvent { + [Event.EqualizerChanged]: EqualizerSettings; [Event.PlaybackState]: PlaybackState; [Event.PlaybackError]: PlaybackErrorEvent; [Event.PlaybackQueueEnded]: PlaybackQueueEndedEvent; diff --git a/src/interfaces/events/EventsPayloadByEvent.ts b/src/interfaces/events/EventsPayloadByEvent.ts index bf3a1045d..38fa1ec1b 100644 --- a/src/interfaces/events/EventsPayloadByEvent.ts +++ b/src/interfaces/events/EventsPayloadByEvent.ts @@ -1,23 +1,25 @@ import { Event } from '../../constants'; +import { EqualizerSettings } from '../EqualizerSettings'; import type { PlaybackState } from '../PlaybackState'; -import type { PlaybackErrorEvent } from './PlaybackErrorEvent'; -import type { PlaybackQueueEndedEvent } from './PlaybackQueueEndedEvent'; -import type { PlaybackTrackChangedEvent } from './PlaybackTrackChangedEvent'; import type { PlaybackActiveTrackChangedEvent } from './PlaybackActiveTrackChangedEvent'; +import type { PlaybackErrorEvent } from './PlaybackErrorEvent'; import type { PlaybackMetadataReceivedEvent } from './PlaybackMetadataReceivedEvent'; import type { PlaybackPlayWhenReadyChangedEvent } from './PlaybackPlayWhenReadyChangedEvent'; import type { PlaybackProgressUpdatedEvent } from './PlaybackProgressUpdatedEvent'; +import type { PlaybackQueueEndedEvent } from './PlaybackQueueEndedEvent'; +import type { PlaybackTrackChangedEvent } from './PlaybackTrackChangedEvent'; +import type { RemoteDuckEvent } from './RemoteDuckEvent'; +import type { RemoteJumpBackwardEvent } from './RemoteJumpBackwardEvent'; +import type { RemoteJumpForwardEvent } from './RemoteJumpForwardEvent'; import type { RemotePlayIdEvent } from './RemotePlayIdEvent'; import type { RemotePlaySearchEvent } from './RemotePlaySearchEvent'; -import type { RemoteSkipEvent } from './RemoteSkipEvent'; -import type { RemoteJumpForwardEvent } from './RemoteJumpForwardEvent'; -import type { RemoteJumpBackwardEvent } from './RemoteJumpBackwardEvent'; import type { RemoteSeekEvent } from './RemoteSeekEvent'; import type { RemoteSetRatingEvent } from './RemoteSetRatingEvent'; -import type { RemoteDuckEvent } from './RemoteDuckEvent'; +import type { RemoteSkipEvent } from './RemoteSkipEvent'; export interface EventsPayloadByEvent { + [Event.EqualizerChanged]: EqualizerSettings & { type: Event.EqualizerChanged }; [Event.PlaybackState]: PlaybackState & { type: Event.PlaybackState }; [Event.PlaybackError]: PlaybackErrorEvent & { type: Event.PlaybackError }; [Event.PlaybackQueueEnded]: PlaybackQueueEndedEvent & { diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index a9cc240e1..d62a26f56 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -1,4 +1,6 @@ export * from './AndroidOptions'; +export * from './EqualizerSettings'; +export * from './events'; export * from './FeedbackOptions'; export * from './MetadataOptions'; export * from './NowPlayingMetadata'; @@ -10,4 +12,3 @@ export * from './ServiceHandler'; export * from './Track'; export * from './TrackMetadataBase'; export * from './UpdateOptions'; -export * from './events'; diff --git a/src/trackPlayer.ts b/src/trackPlayer.ts index cfdf08415..62b6bdb9a 100644 --- a/src/trackPlayer.ts +++ b/src/trackPlayer.ts @@ -3,13 +3,14 @@ import { DeviceEventEmitter, NativeEventEmitter, NativeModules, - Platform, + Platform } from 'react-native'; // @ts-expect-error because resolveAssetSource is untyped import { default as resolveAssetSource } from 'react-native/Libraries/Image/resolveAssetSource'; import { Event, RepeatMode, State } from './constants'; import type { + EqualizerSettings, EventPayloadByEvent, NowPlayingMetadata, PlaybackState, @@ -18,7 +19,7 @@ import type { ServiceHandler, Track, TrackMetadataBase, - UpdateOptions, + UpdateOptions } from './interfaces'; const { TrackPlayerModule: TrackPlayer } = NativeModules; @@ -509,3 +510,19 @@ export async function getRepeatMode(): Promise { export async function retry() { return TrackPlayer.retry(); } + +export async function getEqualizerSettings(): Promise { + return TrackPlayer.getEqualizerSettings(); +} + +export async function setEqualizerEnabled(enabled: boolean): Promise { + return TrackPlayer.setEqualizerEnabled(enabled); +} + +export async function setEqualizerPreset(preset: string): Promise { + return TrackPlayer.setEqualizerPreset(preset); +} + +export async function setEqualizerLevels(levels: number[]): Promise { + return TrackPlayer.setEqualizerLevels(levels); +}