Files
app.zeusln.zeus/utils/IOSAudioKeepAliveUtils.ts

298 lines
8.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NativeModules, NativeEventEmitter, Platform } from 'react-native';
export interface AudioTrack {
index: number;
name: string;
isSelected: boolean;
}
export interface AudioKeepAliveStatus {
isActive: boolean;
playerPlaying: boolean;
isMuted: boolean;
currentTrackIndex: number;
currentTrackName: string;
availableTracks: string[];
uptimeSeconds: number;
backgroundDuration: number;
disconnectCount: number;
lastDisconnectReason: string;
iosVersion: string;
deviceModel: string;
currentOutput: string;
}
export interface AudioInterruptedPayload {
reason: string;
disconnectCount: number;
uptimeSeconds: number;
}
export interface AudioInterruptionEndedPayload {
shouldResume: boolean;
uptimeSeconds: number;
}
export interface AudioRouteChangedPayload {
reason: string;
currentOutput: string;
uptimeSeconds: number;
}
export interface AudioStatusUpdatePayload extends AudioKeepAliveStatus {
reason?: string;
suspectedSuspension?: boolean;
}
export interface AudioTrackChangedPayload {
trackIndex: number;
trackName: string;
}
export type AudioKeepAliveEventType =
| 'NWCAudioInterrupted'
| 'NWCAudioInterruptionEnded'
| 'NWCAudioRouteChanged'
| 'NWCAudioStatusUpdate'
| 'NWCAudioSuspended'
| 'NWCAudioTrackChanged';
interface NWCAudioKeepAliveModule {
startAudioKeepAlive(): Promise<AudioKeepAliveStatus>;
stopAudioKeepAlive(): Promise<AudioKeepAliveStatus>;
getStatus(): Promise<AudioKeepAliveStatus>;
getAvailableTracks(): Promise<AudioTrack[]>;
setTrack(index: number): Promise<AudioKeepAliveStatus>;
setTrackPreference(index: number): Promise<AudioKeepAliveStatus>;
nextTrack(): Promise<AudioKeepAliveStatus>;
previousTrack(): Promise<AudioKeepAliveStatus>;
setMuted(muted: boolean): Promise<AudioKeepAliveStatus>;
armNWCAudio(): Promise<boolean>;
disarmNWCAudio(): Promise<boolean>;
addListener(eventType: string): void;
removeListeners(count: number): void;
}
class IOSAudioKeepAliveUtils {
private nativeModule: NWCAudioKeepAliveModule | null = null;
private emitter: NativeEventEmitter | null = null;
private moduleChecked = false;
private getModule(): NWCAudioKeepAliveModule | null {
if (Platform.OS !== 'ios') return null;
if (!this.moduleChecked) {
this.moduleChecked = true;
const mod = NativeModules.NWCAudioKeepAlive as
| NWCAudioKeepAliveModule
| undefined;
if (mod) {
this.nativeModule = mod;
this.emitter = new NativeEventEmitter(mod as any);
console.log('[NWCAudio] Native module loaded');
} else {
console.error('[NWCAudio] Native module not found');
}
}
return this.nativeModule;
}
private getEmitter(): NativeEventEmitter | null {
this.getModule();
return this.emitter;
}
async start(): Promise<AudioKeepAliveStatus | null> {
const mod = this.getModule();
if (!mod) return null;
try {
const status = await mod.startAudioKeepAlive();
console.log(
`[NWCAudio] Started iOS ${status.iosVersion} on ${status.deviceModel}`
);
return status;
} catch (e) {
console.error('[NWCAudio] start() failed:', e);
return null;
}
}
async stop(): Promise<AudioKeepAliveStatus | null> {
const mod = this.getModule();
if (!mod) return null;
try {
const status = await mod.stopAudioKeepAlive();
console.log(
`[NWCAudio] Stopped uptime ${status.uptimeSeconds.toFixed(
0
)}s`
);
return status;
} catch (e) {
console.error('[NWCAudio] stop() failed:', e);
return null;
}
}
async getStatus(): Promise<AudioKeepAliveStatus | null> {
const mod = this.getModule();
if (!mod) return null;
try {
return await mod.getStatus();
} catch (e) {
console.error('[NWCAudio] getStatus() failed:', e);
return null;
}
}
async getAvailableTracks(): Promise<AudioTrack[] | null> {
const mod = this.getModule();
if (!mod) return null;
try {
return await mod.getAvailableTracks();
} catch (e) {
console.error('[NWCAudio] getAvailableTracks() failed:', e);
return null;
}
}
async setTrack(index: number): Promise<AudioKeepAliveStatus | null> {
const mod = this.getModule();
if (!mod) return null;
try {
return await mod.setTrack(index);
} catch (e) {
console.error('[NWCAudio] setTrack() failed:', e);
return null;
}
}
async setTrackPreference(
index: number
): Promise<AudioKeepAliveStatus | null> {
const mod = this.getModule();
if (!mod) return null;
try {
return await mod.setTrackPreference(index);
} catch (e) {
console.error('[NWCAudio] setTrackPreference() failed:', e);
return null;
}
}
async nextTrack(): Promise<AudioKeepAliveStatus | null> {
const mod = this.getModule();
if (!mod) return null;
try {
return await mod.nextTrack();
} catch (e) {
console.error('[NWCAudio] nextTrack() failed:', e);
return null;
}
}
async previousTrack(): Promise<AudioKeepAliveStatus | null> {
const mod = this.getModule();
if (!mod) return null;
try {
return await mod.previousTrack();
} catch (e) {
console.error('[NWCAudio] previousTrack() failed:', e);
return null;
}
}
async setMuted(muted: boolean): Promise<AudioKeepAliveStatus | null> {
const mod = this.getModule();
if (!mod) return null;
try {
return await mod.setMuted(muted);
} catch (e) {
console.error('[NWCAudio] setMuted() failed:', e);
return null;
}
}
async arm(): Promise<void> {
const mod = this.getModule();
if (!mod) return;
try {
await mod.armNWCAudio();
} catch (e) {
console.error('[NWCAudio] arm() failed:', e);
}
}
async disarm(): Promise<void> {
const mod = this.getModule();
if (!mod) return;
try {
await mod.disarmNWCAudio();
} catch (e) {
console.error('[NWCAudio] disarm() failed:', e);
}
}
onInterrupted(
handler: (payload: AudioInterruptedPayload) => void
): (() => void) | null {
const emitter = this.getEmitter();
if (!emitter) return null;
const sub = emitter.addListener('NWCAudioInterrupted', handler);
return () => sub.remove();
}
onInterruptionEnded(
handler: (payload: AudioInterruptionEndedPayload) => void
): (() => void) | null {
const emitter = this.getEmitter();
if (!emitter) return null;
const sub = emitter.addListener('NWCAudioInterruptionEnded', handler);
return () => sub.remove();
}
onRouteChanged(
handler: (payload: AudioRouteChangedPayload) => void
): (() => void) | null {
const emitter = this.getEmitter();
if (!emitter) return null;
const sub = emitter.addListener('NWCAudioRouteChanged', handler);
return () => sub.remove();
}
onStatusUpdate(
handler: (payload: AudioStatusUpdatePayload) => void
): (() => void) | null {
const emitter = this.getEmitter();
if (!emitter) return null;
const sub = emitter.addListener('NWCAudioStatusUpdate', handler);
return () => sub.remove();
}
onSuspended(
handler: (payload: { reason: string; uptimeSeconds: number }) => void
): (() => void) | null {
const emitter = this.getEmitter();
if (!emitter) return null;
const sub = emitter.addListener('NWCAudioSuspended', handler);
return () => sub.remove();
}
onTrackChanged(
handler: (payload: AudioTrackChangedPayload) => void
): (() => void) | null {
const emitter = this.getEmitter();
if (!emitter) return null;
const sub = emitter.addListener('NWCAudioTrackChanged', handler);
return () => sub.remove();
}
isAvailable(): boolean {
return this.getModule() !== null;
}
}
export default new IOSAudioKeepAliveUtils();