capacitor-plugin-playlist 0.10.3 → 0.10.8

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.
@@ -410,9 +410,13 @@ public class PlaylistPlugin : Plugin(), OnStatusReportListener {
410
410
  Handler(Looper.getMainLooper()).post {
411
411
  val position = call.getFloat("position", 0f)!!
412
412
  val prewarm = call.getBoolean("prewarm", false) ?: false
413
- audioPlayerImpl!!.resumeAfterVideoHandoff(position, prewarm)
414
- call.resolve()
415
- Log.i(TAG, "resumeAfterVideoHandoff prewarm=$prewarm")
413
+ // Default true preserves legacy Android in-place play when `play` is omitted.
414
+ val play = call.getBoolean("play", true) ?: true
415
+ val resumed = audioPlayerImpl!!.resumeAfterVideoHandoff(position, prewarm, play)
416
+ val result = JSObject()
417
+ result.put("resumed", resumed)
418
+ call.resolve(result)
419
+ Log.i(TAG, "resumeAfterVideoHandoff prewarm=$prewarm play=$play resumed=$resumed")
416
420
  }
417
421
  }
418
422
 
@@ -471,24 +471,39 @@ public class RmxAudioPlayer implements PlaybackStatusListener<AudioTrack>,
471
471
  }
472
472
  }
473
473
 
474
- public void resumeAfterVideoHandoff(float positionSec) {
475
- resumeAfterVideoHandoff(positionSec, false);
474
+ public boolean resumeAfterVideoHandoff(float positionSec) {
475
+ return resumeAfterVideoHandoff(positionSec, false, true);
476
476
  }
477
477
 
478
- public void resumeAfterVideoHandoff(float positionSec, boolean prewarm) {
478
+ /**
479
+ * @return {@code true} when in-place resume already seeked and started playback
480
+ * (JS should skip redundant seekTo/play); {@code false} for prewarm, paused handoff,
481
+ * or last-resort beginPlayback.
482
+ */
483
+ public boolean resumeAfterVideoHandoff(float positionSec, boolean prewarm) {
484
+ return resumeAfterVideoHandoff(positionSec, prewarm, true);
485
+ }
486
+
487
+ public boolean resumeAfterVideoHandoff(float positionSec, boolean prewarm, boolean play) {
479
488
  lastKnownHandoffPositionSec = positionSec;
480
489
  long positionMs = (long) (positionSec * 1000f);
481
490
  if (prewarm) {
482
491
  playlistManager.setVideoHandoffForegroundRetain(true);
483
492
  playlistManager.beginPlayback(positionMs, true);
484
- return;
493
+ return false;
494
+ }
495
+ if (!play) {
496
+ // Paused video exit — do not start audio; JS will seekTo only.
497
+ playlistManager.setVideoHandoffForegroundRetain(false);
498
+ return false;
485
499
  }
486
500
  if (tryResumeVideoHandoffInPlace(positionMs)) {
487
- return;
501
+ return true;
488
502
  }
489
503
  // Service not foreground — last resort (may be muted on Android 17 when backgrounded).
490
504
  playlistManager.setVideoHandoffForegroundRetain(false);
491
505
  playlistManager.beginPlayback(positionMs, true);
506
+ return false;
492
507
  }
493
508
 
494
509
  /**
@@ -93,22 +93,21 @@ public class AudioPlaylistHandler<I extends PlaylistItem, M extends BasePlaylist
93
93
  }
94
94
 
95
95
  /**
96
- * Resume at {@code positionMs} after native video ends. Re-requests focus and clears a stale
97
- * isPlaying() state before {@link #play()}.
96
+ * Resume at {@code positionMs} after native video ends. Re-requests focus and starts playback.
97
+ * <p>
98
+ * Must {@link #play()} before {@link #seek(long)}: playlistcore {@code performSeek} sets
99
+ * {@code playingBeforeSeek = isPlaying()}. After video handoff the player is paused, so
100
+ * seek-then-play races with {@code onSeekComplete}, which calls {@code pause()} when
101
+ * {@code playingBeforeSeek} is false — UI briefly shows playing while native audio stays silent.
98
102
  */
99
103
  public void resumePlaybackAfterVideoHandoff(long positionMs) {
100
104
  ((PlaylistManager) getPlaylistManager()).setVideoHandoffForegroundRetain(false);
105
+ setStartPaused(false);
101
106
  getAudioFocusProvider().requestFocus();
102
- com.devbrackets.android.playlistcore.api.MediaPlayerApi<I> mediaPlayer = getCurrentMediaPlayer();
103
- if (mediaPlayer != null) {
104
- if (positionMs > 0) {
105
- seek(positionMs);
106
- }
107
- if (mediaPlayer.isPlaying()) {
108
- mediaPlayer.pause();
109
- }
110
- }
111
107
  play();
108
+ if (positionMs > 0) {
109
+ seek(positionMs);
110
+ }
112
111
  }
113
112
 
114
113
  /**
package/dist/docs.json CHANGED
@@ -11,18 +11,27 @@
11
11
  "parameters": [
12
12
  {
13
13
  "name": "eventName",
14
- "docs": "",
14
+ "docs": "Must be `'status'`.",
15
15
  "type": "'status'"
16
16
  },
17
17
  {
18
18
  "name": "listenerFunc",
19
- "docs": "",
19
+ "docs": "Callback receiving `{ action, status }` where `status.msgType` is a `RmxAudioStatusMessage`.",
20
20
  "type": "PlaylistStatusChangeCallback"
21
21
  }
22
22
  ],
23
23
  "returns": "Promise<PluginListenerHandle>",
24
- "tags": [],
25
- "docs": "Listen for screen reader state change (on/off)",
24
+ "tags": [
25
+ {
26
+ "name": "param",
27
+ "text": "eventName Must be `'status'`."
28
+ },
29
+ {
30
+ "name": "param",
31
+ "text": "listenerFunc Callback receiving `{ action, status }` where `status.msgType` is a `RmxAudioStatusMessage`."
32
+ }
33
+ ],
34
+ "docs": "Subscribe to native playback status events (track changes, position, errors, etc.).",
26
35
  "complexTypes": [
27
36
  "PluginListenerHandle",
28
37
  "PlaylistStatusChangeCallback"
@@ -41,7 +50,7 @@
41
50
  ],
42
51
  "returns": "Promise<void>",
43
52
  "tags": [],
44
- "docs": "",
53
+ "docs": "Configure plugin behaviour (verbose logging, stream pause handling, notification icon).\nCan be called at any time; not required before playback.",
45
54
  "complexTypes": [
46
55
  "AudioPlayerOptions"
47
56
  ],
@@ -53,7 +62,7 @@
53
62
  "parameters": [],
54
63
  "returns": "Promise<void>",
55
64
  "tags": [],
56
- "docs": "",
65
+ "docs": "Initialise the native player, register status callbacks, and arm lock-screen / notification controls.\nCall once before playback (e.g. on app start).",
57
66
  "complexTypes": [],
58
67
  "slug": "initialize"
59
68
  },
@@ -63,7 +72,7 @@
63
72
  "parameters": [],
64
73
  "returns": "Promise<void>",
65
74
  "tags": [],
66
- "docs": "",
75
+ "docs": "Tear down native resources (audio session, media service, observers).\nCall when the app no longer needs background audio (e.g. on logout).",
67
76
  "complexTypes": [],
68
77
  "slug": "release"
69
78
  },
@@ -79,7 +88,7 @@
79
88
  ],
80
89
  "returns": "Promise<void>",
81
90
  "tags": [],
82
- "docs": "",
91
+ "docs": "Replace the entire playlist. Clears all previous items.\nUse `options.retainPosition` to keep the current track and playback position.",
83
92
  "complexTypes": [
84
93
  "PlaylistOptions"
85
94
  ],
@@ -97,7 +106,7 @@
97
106
  ],
98
107
  "returns": "Promise<void>",
99
108
  "tags": [],
100
- "docs": "",
109
+ "docs": "Append a single track to the end of the playlist.",
101
110
  "complexTypes": [
102
111
  "AddItemOptions"
103
112
  ],
@@ -115,7 +124,7 @@
115
124
  ],
116
125
  "returns": "Promise<void>",
117
126
  "tags": [],
118
- "docs": "",
127
+ "docs": "Append multiple tracks to the end of the playlist.\nRaises one `RMXSTATUS_ITEM_ADDED` event per track.",
119
128
  "complexTypes": [
120
129
  "AddAllItemOptions"
121
130
  ],
@@ -133,7 +142,7 @@
133
142
  ],
134
143
  "returns": "Promise<void>",
135
144
  "tags": [],
136
- "docs": "",
145
+ "docs": "Remove a track by index (preferred) or id.\nIf the removed track is currently playing, the next track starts automatically.",
137
146
  "complexTypes": [
138
147
  "RemoveItemOptions"
139
148
  ],
@@ -151,7 +160,7 @@
151
160
  ],
152
161
  "returns": "Promise<void>",
153
162
  "tags": [],
154
- "docs": "",
163
+ "docs": "Remove multiple tracks in a single batch.\nIf the currently playing track is removed, the next available track starts automatically.",
155
164
  "complexTypes": [
156
165
  "RemoveItemsOptions"
157
166
  ],
@@ -163,7 +172,7 @@
163
172
  "parameters": [],
164
173
  "returns": "Promise<void>",
165
174
  "tags": [],
166
- "docs": "",
175
+ "docs": "Remove all tracks from the playlist. Raises `RMXSTATUS_PLAYLIST_CLEARED` and `RMXSTATUS_STOPPED`.",
167
176
  "complexTypes": [],
168
177
  "slug": "clearallitems"
169
178
  },
@@ -173,7 +182,7 @@
173
182
  "parameters": [],
174
183
  "returns": "Promise<GetPlaylistResult>",
175
184
  "tags": [],
176
- "docs": "",
185
+ "docs": "Return a snapshot of the current playlist items.",
177
186
  "complexTypes": [
178
187
  "GetPlaylistResult"
179
188
  ],
@@ -185,7 +194,7 @@
185
194
  "parameters": [],
186
195
  "returns": "Promise<void>",
187
196
  "tags": [],
188
- "docs": "",
197
+ "docs": "Start or resume playback of the current track.\nNo-op if the playlist is empty.",
189
198
  "complexTypes": [],
190
199
  "slug": "play"
191
200
  },
@@ -195,7 +204,7 @@
195
204
  "parameters": [],
196
205
  "returns": "Promise<void>",
197
206
  "tags": [],
198
- "docs": "",
207
+ "docs": "Pause playback of the current track.",
199
208
  "complexTypes": [],
200
209
  "slug": "pause"
201
210
  },
@@ -205,7 +214,7 @@
205
214
  "parameters": [],
206
215
  "returns": "Promise<void>",
207
216
  "tags": [],
208
- "docs": "",
217
+ "docs": "Skip to the next track. At the end of the playlist, wraps to the beginning when loop is enabled.",
209
218
  "complexTypes": [],
210
219
  "slug": "skipforward"
211
220
  },
@@ -215,7 +224,7 @@
215
224
  "parameters": [],
216
225
  "returns": "Promise<void>",
217
226
  "tags": [],
218
- "docs": "",
227
+ "docs": "Skip to the previous track. No-op when already at the first track.",
219
228
  "complexTypes": [],
220
229
  "slug": "skipback"
221
230
  },
@@ -231,7 +240,7 @@
231
240
  ],
232
241
  "returns": "Promise<void>",
233
242
  "tags": [],
234
- "docs": "",
243
+ "docs": "Seek to a position (seconds) in the currently playing track.\nIf the position exceeds track length, playback advances to the next track.",
235
244
  "complexTypes": [
236
245
  "SeekToOptions"
237
246
  ],
@@ -249,7 +258,7 @@
249
258
  ],
250
259
  "returns": "Promise<void>",
251
260
  "tags": [],
252
- "docs": "",
261
+ "docs": "Jump to the track at the given 0-based index and start playback.",
253
262
  "complexTypes": [
254
263
  "PlayByIndexOptions"
255
264
  ],
@@ -267,7 +276,7 @@
267
276
  ],
268
277
  "returns": "Promise<void>",
269
278
  "tags": [],
270
- "docs": "",
279
+ "docs": "Jump to the track with the given id and start playback.",
271
280
  "complexTypes": [
272
281
  "PlayByIdOptions"
273
282
  ],
@@ -285,7 +294,7 @@
285
294
  ],
286
295
  "returns": "Promise<void>",
287
296
  "tags": [],
288
- "docs": "",
297
+ "docs": "Select the track at the given index without necessarily starting playback.",
289
298
  "complexTypes": [
290
299
  "SelectByIndexOptions"
291
300
  ],
@@ -303,7 +312,7 @@
303
312
  ],
304
313
  "returns": "Promise<void>",
305
314
  "tags": [],
306
- "docs": "",
315
+ "docs": "Select the track with the given id without necessarily starting playback.",
307
316
  "complexTypes": [
308
317
  "SelectByIdOptions"
309
318
  ],
@@ -321,7 +330,7 @@
321
330
  ],
322
331
  "returns": "Promise<void>",
323
332
  "tags": [],
324
- "docs": "",
333
+ "docs": "Set media stream volume. Float in range [0, 1].\nHardware volume controls still apply on top of this value.",
325
334
  "complexTypes": [
326
335
  "SetPlaybackVolumeOptions"
327
336
  ],
@@ -339,7 +348,7 @@
339
348
  ],
340
349
  "returns": "Promise<void>",
341
350
  "tags": [],
342
- "docs": "",
351
+ "docs": "When true, the playlist loops back to the first track after the last track completes.",
343
352
  "complexTypes": [
344
353
  "SetLoopOptions"
345
354
  ],
@@ -357,7 +366,7 @@
357
366
  ],
358
367
  "returns": "Promise<void>",
359
368
  "tags": [],
360
- "docs": "",
369
+ "docs": "Set playback speed. Float value; 0 pauses, 1 is normal speed.",
361
370
  "complexTypes": [
362
371
  "SetPlaybackRateOptions"
363
372
  ],
@@ -369,13 +378,13 @@
369
378
  "parameters": [],
370
379
  "returns": "Promise<void>",
371
380
  "tags": [],
372
- "docs": "Epic 45 — release native audio session / focus so the video player can own playback.\nCall immediately before CapacitorVideoPlayer.initPlayer (Critical Rule 2).",
381
+ "docs": "Release native audio session / focus so a video player can own playback.\n\n**Android:** pauses current track, abandons audio focus, stores head position. Does not stop the foreground media service.\n**iOS:** pauses, captures head position, deactivates `AVAudioSession` with `notifyOthersOnDeactivation`.\n**Web:** pauses HTMLAudioElement and stores `currentTime`.\n\nCall immediately before native video starts (e.g. your video plugin's init method).",
373
382
  "complexTypes": [],
374
383
  "slug": "prepareforvideohandoff"
375
384
  },
376
385
  {
377
386
  "name": "resumeAfterVideoHandoff",
378
- "signature": "(options: ResumeAfterVideoHandoffOptions) => Promise<void>",
387
+ "signature": "(options: ResumeAfterVideoHandoffOptions) => Promise<ResumeAfterVideoHandoffResult>",
379
388
  "parameters": [
380
389
  {
381
390
  "name": "options",
@@ -383,10 +392,11 @@
383
392
  "type": "ResumeAfterVideoHandoffOptions"
384
393
  }
385
394
  ],
386
- "returns": "Promise<void>",
395
+ "returns": "Promise<ResumeAfterVideoHandoffResult>",
387
396
  "tags": [],
388
- "docs": "Epic 45 optional resume hook after video exits (full behaviour in Stories 45.3/45.4).",
397
+ "docs": "Re-arm native audio after video ends or, on Android, prewarm the media service before video starts.\n\n**Without `prewarm` (typical exit path):**\n- Android: when `play` is true (default), re-acquires focus and resumes at `position`. When `resumed` is `true`, JS should skip redundant `seekTo`/`play`. When `play` is false, clears handoff retain and returns `{ resumed: false }` so JS can seek without playing.\n- iOS: restores pinned track, reactivates `AVAudioSession`, seeks to `position`, and when `play` is true starts playback (seek-then-play). Returns `{ resumed: true }` when native handled the handoff.\n- Web: stores position only (no native session); returns `{ resumed: false }`.\n\n**With `prewarm: true` (Android, before video):** starts `MediaService` in foreground at `position` but stays silent — no audio focus, no audible playback. Always returns `{ resumed: false }`.",
389
398
  "complexTypes": [
399
+ "ResumeAfterVideoHandoffResult",
390
400
  "ResumeAfterVideoHandoffOptions"
391
401
  ],
392
402
  "slug": "resumeaftervideohandoff"
@@ -397,7 +407,7 @@
397
407
  "parameters": [],
398
408
  "returns": "Promise<GetLastKnownPositionResult>",
399
409
  "tags": [],
400
- "docs": "Epic 45 — last audio head captured during prepare (seconds).",
410
+ "docs": "Return the audio head position (seconds) captured during the most recent `prepareForVideoHandoff`\nor passed to `resumeAfterVideoHandoff`.",
401
411
  "complexTypes": [
402
412
  "GetLastKnownPositionResult"
403
413
  ],
@@ -1903,6 +1913,22 @@
1903
1913
  }
1904
1914
  ]
1905
1915
  },
1916
+ {
1917
+ "name": "ResumeAfterVideoHandoffResult",
1918
+ "slug": "resumeaftervideohandoffresult",
1919
+ "docs": "",
1920
+ "tags": [],
1921
+ "methods": [],
1922
+ "properties": [
1923
+ {
1924
+ "name": "resumed",
1925
+ "tags": [],
1926
+ "docs": "`true` when native already handled seek (and play when requested) in place.\nWhen `true`, JS should skip redundant `seekTo` / `play` to avoid a stutter.\n`false` on web, prewarm, paused Android handoff, and Android last-resort `beginPlayback`.",
1927
+ "complexTypes": [],
1928
+ "type": "boolean"
1929
+ }
1930
+ ]
1931
+ },
1906
1932
  {
1907
1933
  "name": "ResumeAfterVideoHandoffOptions",
1908
1934
  "slug": "resumeaftervideohandoffoptions",
@@ -1913,14 +1939,21 @@
1913
1939
  {
1914
1940
  "name": "position",
1915
1941
  "tags": [],
1916
- "docs": "",
1942
+ "docs": "Resume position in seconds (video exit head or saved audio position).",
1917
1943
  "complexTypes": [],
1918
1944
  "type": "number"
1919
1945
  },
1920
1946
  {
1921
1947
  "name": "prewarm",
1922
1948
  "tags": [],
1923
- "docs": "Android: keep MediaService FGS alive (with WIU) while native video plays.",
1949
+ "docs": "**Android only.** When `true`, promote `MediaService` to foreground and prepare at `position`\nwithout requesting audio focus or playing audio. Use immediately after `prepareForVideoHandoff`\nand before native video starts, while the app is still foregrounded.\nIgnored on iOS (no-op). Not applicable on web.",
1950
+ "complexTypes": [],
1951
+ "type": "boolean | undefined"
1952
+ },
1953
+ {
1954
+ "name": "play",
1955
+ "tags": [],
1956
+ "docs": "When `true`, native starts audible playback after seeking to `position`.\nWhen `false` (paused video exit), native must not start playback.\niOS defaults to `false` when omitted; Android defaults to `true` for legacy callers.",
1924
1957
  "complexTypes": [],
1925
1958
  "type": "boolean | undefined"
1926
1959
  }
@@ -2,49 +2,161 @@ import { PluginListenerHandle } from '@capacitor/core';
2
2
  import { AudioPlayerOptions, AudioTrack, PlaylistItemOptions, PlaylistStatusChangeCallback } from './interfaces';
3
3
  export interface PlaylistPlugin {
4
4
  /**
5
- * Listen for screen reader state change (on/off)
5
+ * Subscribe to native playback status events (track changes, position, errors, etc.).
6
+ *
7
+ * @param eventName Must be `'status'`.
8
+ * @param listenerFunc Callback receiving `{ action, status }` where `status.msgType` is a `RmxAudioStatusMessage`.
6
9
  */
7
10
  addListener(eventName: 'status', listenerFunc: PlaylistStatusChangeCallback): Promise<PluginListenerHandle>;
11
+ /**
12
+ * Configure plugin behaviour (verbose logging, stream pause handling, notification icon).
13
+ * Can be called at any time; not required before playback.
14
+ */
8
15
  setOptions(options: AudioPlayerOptions): Promise<void>;
16
+ /**
17
+ * Initialise the native player, register status callbacks, and arm lock-screen / notification controls.
18
+ * Call once before playback (e.g. on app start).
19
+ */
9
20
  initialize(): Promise<void>;
21
+ /**
22
+ * Tear down native resources (audio session, media service, observers).
23
+ * Call when the app no longer needs background audio (e.g. on logout).
24
+ */
10
25
  release(): Promise<void>;
26
+ /**
27
+ * Replace the entire playlist. Clears all previous items.
28
+ * Use `options.retainPosition` to keep the current track and playback position.
29
+ */
11
30
  setPlaylistItems(options: PlaylistOptions): Promise<void>;
31
+ /**
32
+ * Append a single track to the end of the playlist.
33
+ */
12
34
  addItem(options: AddItemOptions): Promise<void>;
35
+ /**
36
+ * Append multiple tracks to the end of the playlist.
37
+ * Raises one `RMXSTATUS_ITEM_ADDED` event per track.
38
+ */
13
39
  addAllItems(options: AddAllItemOptions): Promise<void>;
40
+ /**
41
+ * Remove a track by index (preferred) or id.
42
+ * If the removed track is currently playing, the next track starts automatically.
43
+ */
14
44
  removeItem(options: RemoveItemOptions): Promise<void>;
45
+ /**
46
+ * Remove multiple tracks in a single batch.
47
+ * If the currently playing track is removed, the next available track starts automatically.
48
+ */
15
49
  removeItems(options: RemoveItemsOptions): Promise<void>;
50
+ /**
51
+ * Remove all tracks from the playlist. Raises `RMXSTATUS_PLAYLIST_CLEARED` and `RMXSTATUS_STOPPED`.
52
+ */
16
53
  clearAllItems(): Promise<void>;
54
+ /**
55
+ * Return a snapshot of the current playlist items.
56
+ */
17
57
  getPlaylist(): Promise<GetPlaylistResult>;
58
+ /**
59
+ * Start or resume playback of the current track.
60
+ * No-op if the playlist is empty.
61
+ */
18
62
  play(): Promise<void>;
63
+ /**
64
+ * Pause playback of the current track.
65
+ */
19
66
  pause(): Promise<void>;
67
+ /**
68
+ * Skip to the next track. At the end of the playlist, wraps to the beginning when loop is enabled.
69
+ */
20
70
  skipForward(): Promise<void>;
71
+ /**
72
+ * Skip to the previous track. No-op when already at the first track.
73
+ */
21
74
  skipBack(): Promise<void>;
75
+ /**
76
+ * Seek to a position (seconds) in the currently playing track.
77
+ * If the position exceeds track length, playback advances to the next track.
78
+ */
22
79
  seekTo(options: SeekToOptions): Promise<void>;
80
+ /**
81
+ * Jump to the track at the given 0-based index and start playback.
82
+ */
23
83
  playTrackByIndex(options: PlayByIndexOptions): Promise<void>;
84
+ /**
85
+ * Jump to the track with the given id and start playback.
86
+ */
24
87
  playTrackById(options: PlayByIdOptions): Promise<void>;
88
+ /**
89
+ * Select the track at the given index without necessarily starting playback.
90
+ */
25
91
  selectTrackByIndex(options: SelectByIndexOptions): Promise<void>;
92
+ /**
93
+ * Select the track with the given id without necessarily starting playback.
94
+ */
26
95
  selectTrackById(options: SelectByIdOptions): Promise<void>;
96
+ /**
97
+ * Set media stream volume. Float in range [0, 1].
98
+ * Hardware volume controls still apply on top of this value.
99
+ */
27
100
  setPlaybackVolume(options: SetPlaybackVolumeOptions): Promise<void>;
101
+ /**
102
+ * When true, the playlist loops back to the first track after the last track completes.
103
+ */
28
104
  setLoop(options: SetLoopOptions): Promise<void>;
105
+ /**
106
+ * Set playback speed. Float value; 0 pauses, 1 is normal speed.
107
+ */
29
108
  setPlaybackRate(options: SetPlaybackRateOptions): Promise<void>;
30
109
  /**
31
- * Epic 45 — release native audio session / focus so the video player can own playback.
32
- * Call immediately before CapacitorVideoPlayer.initPlayer (Critical Rule 2).
110
+ * Release native audio session / focus so a video player can own playback.
111
+ *
112
+ * **Android:** pauses current track, abandons audio focus, stores head position. Does not stop the foreground media service.
113
+ * **iOS:** pauses, captures head position, deactivates `AVAudioSession` with `notifyOthersOnDeactivation`.
114
+ * **Web:** pauses HTMLAudioElement and stores `currentTime`.
115
+ *
116
+ * Call immediately before native video starts (e.g. your video plugin's init method).
33
117
  */
34
118
  prepareForVideoHandoff(): Promise<void>;
35
119
  /**
36
- * Epic 45 optional resume hook after video exits (full behaviour in Stories 45.3/45.4).
120
+ * Re-arm native audio after video ends or, on Android, prewarm the media service before video starts.
121
+ *
122
+ * **Without `prewarm` (typical exit path):**
123
+ * - Android: when `play` is true (default), re-acquires focus and resumes at `position`. When `resumed` is `true`, JS should skip redundant `seekTo`/`play`. When `play` is false, clears handoff retain and returns `{ resumed: false }` so JS can seek without playing.
124
+ * - iOS: restores pinned track, reactivates `AVAudioSession`, seeks to `position`, and when `play` is true starts playback (seek-then-play). Returns `{ resumed: true }` when native handled the handoff.
125
+ * - Web: stores position only (no native session); returns `{ resumed: false }`.
126
+ *
127
+ * **With `prewarm: true` (Android, before video):** starts `MediaService` in foreground at `position` but stays silent — no audio focus, no audible playback. Always returns `{ resumed: false }`.
37
128
  */
38
- resumeAfterVideoHandoff(options: ResumeAfterVideoHandoffOptions): Promise<void>;
129
+ resumeAfterVideoHandoff(options: ResumeAfterVideoHandoffOptions): Promise<ResumeAfterVideoHandoffResult>;
39
130
  /**
40
- * Epic 45 — last audio head captured during prepare (seconds).
131
+ * Return the audio head position (seconds) captured during the most recent `prepareForVideoHandoff`
132
+ * or passed to `resumeAfterVideoHandoff`.
41
133
  */
42
134
  getLastKnownPosition(): Promise<GetLastKnownPositionResult>;
43
135
  }
44
136
  export interface ResumeAfterVideoHandoffOptions {
137
+ /** Resume position in seconds (video exit head or saved audio position). */
45
138
  position: number;
46
- /** Android: keep MediaService FGS alive (with WIU) while native video plays. */
139
+ /**
140
+ * **Android only.** When `true`, promote `MediaService` to foreground and prepare at `position`
141
+ * without requesting audio focus or playing audio. Use immediately after `prepareForVideoHandoff`
142
+ * and before native video starts, while the app is still foregrounded.
143
+ * Ignored on iOS (no-op). Not applicable on web.
144
+ */
47
145
  prewarm?: boolean;
146
+ /**
147
+ * When `true`, native starts audible playback after seeking to `position`.
148
+ * When `false` (paused video exit), native must not start playback.
149
+ * iOS defaults to `false` when omitted; Android defaults to `true` for legacy callers.
150
+ */
151
+ play?: boolean;
152
+ }
153
+ export interface ResumeAfterVideoHandoffResult {
154
+ /**
155
+ * `true` when native already handled seek (and play when requested) in place.
156
+ * When `true`, JS should skip redundant `seekTo` / `play` to avoid a stutter.
157
+ * `false` on web, prewarm, paused Android handoff, and Android last-resort `beginPlayback`.
158
+ */
159
+ resumed: boolean;
48
160
  }
49
161
  export interface GetLastKnownPositionResult {
50
162
  position: number;
package/dist/esm/web.d.ts CHANGED
@@ -38,7 +38,9 @@ export declare class PlaylistWeb extends WebPlugin implements PlaylistPlugin {
38
38
  prepareForVideoHandoff(): Promise<void>;
39
39
  resumeAfterVideoHandoff(options: {
40
40
  position: number;
41
- }): Promise<void>;
41
+ }): Promise<{
42
+ resumed: boolean;
43
+ }>;
42
44
  getLastKnownPosition(): Promise<{
43
45
  position: number;
44
46
  }>;
package/dist/esm/web.js CHANGED
@@ -214,7 +214,7 @@ export class PlaylistWeb extends WebPlugin {
214
214
  }
215
215
  async resumeAfterVideoHandoff(options) {
216
216
  this.lastKnownHandoffPosition = options.position;
217
- return Promise.resolve();
217
+ return Promise.resolve({ resumed: false });
218
218
  }
219
219
  async getLastKnownPosition() {
220
220
  return Promise.resolve({ position: this.lastKnownHandoffPosition });