Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions apps/common-app/src/demos/Record/Record.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ const Record: FC = () => {
const [state, setState] = useState<RecordingState>(RecordingState.Idle);
const [hasPermissions, setHasPermissions] = useState<boolean>(false);

const setNotification = (paused: boolean) => {
const updateNotification = (paused: boolean) => {
RecordingNotificationManager.show({
paused,
});
};

const setupNotification = (paused: boolean) => {
RecordingNotificationManager.show({
title: 'Recording Demo',
contentText: paused ? 'Paused recording' : 'Recording...',
Expand All @@ -34,7 +40,7 @@ const Record: FC = () => {
resumeIconResourceName: 'resume',
color: 0xff6200,
});
};
}

const onStartRecording = useCallback(async () => {
if (state !== RecordingState.Idle) {
Expand Down Expand Up @@ -62,7 +68,7 @@ const Record: FC = () => {
}

const result = Recorder.start();
setNotification(false);
setupNotification(false);

if (result.status === 'success') {
console.log('Recording started, file path:', result.path);
Expand All @@ -77,13 +83,13 @@ const Record: FC = () => {

const onPauseRecording = useCallback(() => {
Recorder.pause();
setNotification(true);
updateNotification(true);
setState(RecordingState.Paused);
}, []);

const onResumeRecording = useCallback(() => {
Recorder.resume();
setNotification(false);
updateNotification(false);
setState(RecordingState.Recording);
}, []);

Expand Down
7 changes: 4 additions & 3 deletions apps/common-app/src/examples/AudioFile/AudioPlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class AudioPlayer {
}

this.isPlaying = true;
PlaybackNotificationManager.update({
PlaybackNotificationManager.show({
state: 'playing',
});

Expand Down Expand Up @@ -75,8 +75,9 @@ class AudioPlayer {
this.sourceNode?.stop(this.audioContext.currentTime);

await this.audioContext.suspend();
PlaybackNotificationManager.update({
PlaybackNotificationManager.show({
state: 'paused',
elapsedTime: this.currentElapsedTime,
});

this.isPlaying = false;
Expand All @@ -90,7 +91,7 @@ class AudioPlayer {
} else if (this.currentElapsedTime > this.getDuration()) {
this.currentElapsedTime = this.getDuration();
}
PlaybackNotificationManager.update({
PlaybackNotificationManager.show({
elapsedTime: this.currentElapsedTime,
});

Expand Down
23 changes: 3 additions & 20 deletions packages/audiodocs/docs/system/audio-manager.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ interface EventTypeWithValue {
}

interface OnInterruptionEventType {
type: 'ended' | 'began'; / /if interruption event has started or ended
type: 'ended' | 'began'; // if interruption event has started or ended
shouldResume: boolean; // if the interruption was temporary and we can resume the playback/recording
}

Expand Down Expand Up @@ -284,28 +284,11 @@ type SystemEventCallback<Name extends SystemEventName> = (
<details>
<summary>Type definitions</summary>
```typescript
class AudioEventSubscription {
private readonly audioEventEmitter: AudioEventEmitter;
private readonly eventName: AudioEventName;
interface AudioEventSubscription {
/** @internal */
public readonly subscriptionId: string;

constructor(
subscriptionId: string,
eventName: AudioEventName,
audioEventEmitter: AudioEventEmitter
) {
this.subscriptionId = subscriptionId;
this.eventName = eventName;
this.audioEventEmitter = audioEventEmitter;
}

public remove(): void {
this.audioEventEmitter.removeAudioEventListener(
this.eventName,
this.subscriptionId
);
}
public remove(): void; // used to remove the subscription
}
```
</details>
Expand Down
140 changes: 50 additions & 90 deletions packages/audiodocs/docs/system/playback-notification-manager.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,20 @@ The `PlaybackNotificationManager` provides media session integration and playbac

**iOS Requirements:**
- Notification controls only appear when an active `AudioContext` is running
- `show()`, `update()` or `hide()` only update metadata - they don't control notification visibility
- `show()` or `hide()` only update metadata - they don't control notification visibility
- The notification automatically appears/disappears based on audio session state
- To show: create and resume an AudioContext
- To hide: suspend or close the AudioContext

**Android:**
- Notification visibility is directly controlled by `show()` and `hide()` methods
- Works independently of AudioContext state

:::

## Example

```tsx
// Register and show notification
await PlaybackNotificationManager.register();
// show notification
await PlaybackNotificationManager.show({
title: 'My Song',
artist: 'My Artist',
Expand All @@ -45,50 +43,38 @@ const playListener = PlaybackNotificationManager.addEventListener(
'playbackNotificationPlay',
() => {
// Handle play action
PlaybackNotificationManager.update({ state: 'playing' });
PlaybackNotificationManager.show({ state: 'playing' });
}
);

const pauseListener = PlaybackNotificationManager.addEventListener(
'playbackNotificationPause',
() => {
// Handle pause action
PlaybackNotificationManager.update({ state: 'paused' });
PlaybackNotificationManager.show({ state: 'paused' });
}
);

const seekToListener = PlaybackNotificationManager.addEventListener(
'playbackNotificationSeekTo',
(event) => {
// Handle seek to position (event.value is in seconds)
PlaybackNotificationManager.update({ elapsedTime: event.value });
PlaybackNotificationManager.show({ elapsedTime: event.value });
}
);

// Update progress
PlaybackNotificationManager.update({ elapsedTime: 60 });
PlaybackNotificationManager.show({ elapsedTime: 60 });

// Cleanup
PlaybackNotificationManager.removeEventListener(playListener);
PlaybackNotificationManager.removeEventListener(pauseListener);
PlaybackNotificationManager.removeEventListener(seekToListener);
PlaybackNotificationManager.unregister();
playListener.remove();
pauseListener.remove();
seekToListener.remove();
PlaybackNotificationManager.hide();
```

## Methods

### `register`

Register the playback notification with the system. Must be called before showing any notification.

#### Returns `Promise<void>`.

#### Errors

| Error type | Description |
| :--------: | :---------------------------------------------------------- |
| `Error` | NativeAudioAPIModule is not available or registration fails |

### `show`

Display the notification with initial metadata.
Expand All @@ -97,29 +83,13 @@ Display the notification with initial metadata.
On iOS, this method only sets the metadata. The notification controls will only appear when an `AudioContext` is actively running. Make sure to create and resume an AudioContext before calling `show()`.
:::

| Parameter | Type | Description |
| :-------: | :-----------------------------------------------------------------------------------------------: | :---------------------------- |
| `info` | [`PlaybackNotificationInfo`](/docs/system/playback-notification-manager#playbacknotificationinfo) | Initial notification metadata |

#### Returns `Promise<void>`.

#### Errors

| Error type | Description |
| :--------: | :----------------------------------------------------------------- |
| `Error` | Notification must be registered first or native module unavailable |

### `update`

Update the notification with new metadata or state.

:::note iOS Behavior
On iOS, this method only updates the metadata. It does not control notification visibility - controls remain visible as long as the AudioContext is running.
:::info
Metadata is remembered between calls, so after initial passing the metadata to show function, you can only call it with elements that are supposed to change.
:::

| Parameter | Type | Description |
| :-------: | :-----------------------------------------------------------------------------------------------: | :---------------------------- |
| `info` | [`PlaybackNotificationInfo`](/docs/system/playback-notification-manager#playbacknotificationinfo) | Updated notification metadata |
| Parameter | Type | Description |
| :-------: | :----------: | :----- |
| `info` | [`PlaybackNotificationInfo`](playback-notification-manager#playbacknotificationinfo) | Initial notification metadata |

#### Returns `Promise<void>`.

Expand All @@ -133,20 +103,14 @@ On iOS, this method clears the metadata but does not hide the notification contr

#### Returns `Promise<void>`.

### `unregister`

Unregister the notification from the system. Must call `register()` again to use.

#### Returns `Promise<void>`.

### `enableControl`

Enable or disable specific playback controls.

| Parameter | Type | Description |
| :-------: | :-------------------------------------------------------------------------------------: | :------------------------------------ |
| `control` | [`PlaybackControlName`](/docs/system/playback-notification-manager#playbackcontrolname) | The control to enable/disable |
| `enabled` | `boolean` | Whether the control should be enabled |
| Parameter | Type | Description |
| :-------: | :-----: | :------ |
| `control` | [`PlaybackControlName`](playback-notification-manager#playbackcontrolname) | The control to enable/disable |
| `enabled` | `boolean` | Whether the control should be enabled |

#### Returns `Promise<void>`.

Expand All @@ -160,21 +124,13 @@ Check if the notification is currently active and visible.

Add an event listener for notification actions.

| Parameter | Type | Description |
| :---------: | :---------------------------------------------------------------------------------------------------------: | :---------------------- |
| `eventName` | [`PlaybackNotificationEventName`](/docs/system/playback-notification-manager#playbacknotificationeventname) | The event to listen for |
| `callback` | [`SystemEventCallback`](/docs/system/audio-manager#systemeventname--remotecommandeventname) | Callback function |
| Parameter | Type | Description |
| :---------: | :------: | :------- |
| `eventName` | [`PlaybackNotificationEventName`](playback-notification-manager#playbacknotificationeventname) | The event to listen for |
| `callback` | [`SystemEventCallback`](/docs/system/audio-manager#systemeventname--remotecommandeventname) | Callback function |

#### Returns [`AudioEventSubscription`](/docs/system/audio-manager#audioeventsubscription).

### `removeEventListener`

Remove an event listener.

| Parameter | Type | Description |
| :------------: | :---------------------------------------------------------------------------: | :------------------------- |
| `subscription` | [`AudioEventSubscription`](/docs/system/audio-manager#audioeventsubscription) | The subscription to remove |

## Remarks

### `PlaybackNotificationInfo`
Expand Down Expand Up @@ -205,36 +161,40 @@ interface PlaybackNotificationInfo {

<details>
<summary>Type definitions</summary>
```typescript
type PlaybackControlName =
| 'play'
| 'pause'
| 'next'
| 'previous'
| 'skipForward'
| 'skipBackward'
| 'seekTo';
```
```typescript
type PlaybackControlName =
| 'play'
| 'pause'
| 'next'
| 'previous'
| 'skipForward'
| 'skipBackward'
| 'seekTo';
```
</details>

### `PlaybackNotificationEventName`

<details>
<summary>Type definitions</summary>
```typescript
interface PlaybackNotificationEvent {
playbackNotificationPlay: EventEmptyType;
playbackNotificationPause: EventEmptyType;
playbackNotificationNext: EventEmptyType;
playbackNotificationPrevious: EventEmptyType;
playbackNotificationSkipForward: EventTypeWithValue;
playbackNotificationSkipBackward: EventTypeWithValue;
playbackNotificationSeekTo: EventTypeWithValue;
playbackNotificationDismissed: EventEmptyType;
}

type PlaybackNotificationEventName = keyof PlaybackNotificationEvent;
```
interface EventTypeWithValue {
value: number;
}

interface PlaybackNotificationEvent {
playbackNotificationPlay: EventEmptyType;
playbackNotificationPause: EventEmptyType;
playbackNotificationNext: EventEmptyType;
playbackNotificationPrevious: EventEmptyType;
playbackNotificationSkipForward: EventTypeWithValue;
playbackNotificationSkipBackward: EventTypeWithValue;
playbackNotificationSeekTo: EventTypeWithValue;
playbackNotificationDismissed: EventEmptyType;
}

type PlaybackNotificationEventName = keyof PlaybackNotificationEvent;
```
</details>

```
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ const resumeEventListener = RecordingNotificationManager.addEventListener('recor
console.log('Notification resume action received');
});

RecordingNotificationManager.removeEventListener(pauseEventListener);
RecordingNotificationManager.removeEventListener(resumeEventListener);
pauseEventListener.remove();
resumeEventListener.remove();
RecordingNotificationManager.hide();
```

Expand All @@ -42,9 +42,14 @@ RecordingNotificationManager.hide();

Shows the recording notification with the parameters.

:::info
Metadata is saved between calls, so after the initial pass to the show method, you need only call it with elements that are supposed to change.
:::


| Parameter |Type| Description|
| :-------: | :--: | :----|
| `info` | [`RecordingNotificationInfo`](recording-notification-manager#recordingnotificationinfo) | Initial notification metadata |
| `info` | [`RecordingNotificationInfo`](recording-notification-manager#recordingnotificationinfo) | Initial notification metadata |

#### Returns `Promise<void>`.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import android.view.KeyEvent
import androidx.core.app.NotificationCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.media.app.NotificationCompat.MediaStyle
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableMap
Expand Down
Loading