react-native-audio-browser 0.2.0-next.4 → 0.2.0-next.6

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.
Files changed (58) hide show
  1. package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +11 -11
  2. package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +111 -58
  3. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +9 -5
  4. package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +218 -252
  5. package/android/src/main/java/com/audiobrowser/player/Player.kt +19 -8
  6. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +100 -56
  7. package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
  8. package/lib/commonjs/features/player/options.js +45 -15
  9. package/lib/commonjs/features/player/options.js.map +1 -1
  10. package/lib/commonjs/features/player/setup.js +13 -6
  11. package/lib/commonjs/features/player/setup.js.map +1 -1
  12. package/lib/module/features/player/options.js +45 -15
  13. package/lib/module/features/player/options.js.map +1 -1
  14. package/lib/module/features/player/setup.js +13 -6
  15. package/lib/module/features/player/setup.js.map +1 -1
  16. package/lib/typescript/src/features/player/options.d.ts +86 -36
  17. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  18. package/lib/typescript/src/features/player/setup.d.ts +10 -3
  19. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  20. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +1 -1
  21. package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -10
  22. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +5 -5
  23. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +9 -9
  24. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +5 -5
  25. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +5 -5
  26. package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -10
  27. package/nitrogen/generated/android/c++/JOptions.hpp +5 -5
  28. package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +19 -19
  29. package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
  30. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
  31. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -3
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -3
  34. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → RemoteButton.kt} +3 -3
  35. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
  36. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
  37. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +36 -51
  38. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +6 -6
  39. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +6 -6
  40. package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -12
  41. package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -12
  42. package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +6 -6
  43. package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
  44. package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
  45. package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -8
  46. package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -8
  47. package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +19 -19
  48. package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
  49. package/package.json +1 -1
  50. package/src/features/player/options.ts +97 -37
  51. package/src/features/player/setup.test.ts +12 -4
  52. package/src/features/player/setup.ts +14 -7
  53. package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
  54. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
  55. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
  56. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
  57. package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
  58. package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +0 -102
@@ -168,8 +168,10 @@ class Player(internal val context: Context) {
168
168
  mediaSessionCallback.updateMediaSession(
169
169
  mediaSession,
170
170
  options.capabilities,
171
- options.notificationButtons,
171
+ options.remoteButtonLayout,
172
172
  searchAvailable,
173
+ options.forwardJumpInterval,
174
+ options.backwardJumpInterval,
173
175
  )
174
176
  }
175
177
  }
@@ -834,8 +836,8 @@ class Player(internal val context: Context) {
834
836
  * A restart from a terminal error begins a new load of the same track: fresh retry budgets and
835
837
  * unproven playback, so the user's tap yields a visible first-connect retry window instead of a
836
838
  * single silent attempt — the same behavior as re-selecting the track (ADR 0004). Hooked off the
837
- * player error clearing: every recovery path (retry(), play(), the transport controls'
838
- * idle-error recovery) goes through exoPlayer.prepare(), which clears the error.
839
+ * player error clearing: every recovery path (retry(), play(), the transport controls' idle-error
840
+ * recovery) goes through exoPlayer.prepare(), which clears the error.
839
841
  */
840
842
  internal fun resetForNewLoad() {
841
843
  resetRetryTimer()
@@ -1215,8 +1217,8 @@ class Player(internal val context: Context) {
1215
1217
  val backwardJumpIntervalChanged =
1216
1218
  previousOptions.backwardJumpInterval != options.backwardJumpInterval
1217
1219
  val capabilitiesChanged = previousOptions.capabilities != options.capabilities
1218
- val notificationButtonsChanged =
1219
- previousOptions.notificationButtons != options.notificationButtons
1220
+ val remoteButtonLayoutChanged =
1221
+ !previousOptions.remoteButtonLayout.sameAs(options.remoteButtonLayout)
1220
1222
  val appKilledPlaybackBehaviorChanged =
1221
1223
  previousOptions.appKilledPlaybackBehavior != options.appKilledPlaybackBehavior
1222
1224
 
@@ -1226,7 +1228,7 @@ class Player(internal val context: Context) {
1226
1228
  forwardJumpIntervalChanged ||
1227
1229
  backwardJumpIntervalChanged ||
1228
1230
  capabilitiesChanged ||
1229
- notificationButtonsChanged ||
1231
+ remoteButtonLayoutChanged ||
1230
1232
  appKilledPlaybackBehaviorChanged
1231
1233
 
1232
1234
  // Apply only changed properties
@@ -1244,13 +1246,22 @@ class Player(internal val context: Context) {
1244
1246
  setProgressUpdateInterval(options.progressUpdateEventInterval)
1245
1247
  }
1246
1248
 
1247
- if (capabilitiesChanged || notificationButtonsChanged) {
1249
+ // The jump intervals pick the button icons (ICON_SKIP_BACK_15 and friends), so a changed
1250
+ // interval has to rebuild the layout even when capabilities and placement are untouched.
1251
+ if (
1252
+ capabilitiesChanged ||
1253
+ remoteButtonLayoutChanged ||
1254
+ forwardJumpIntervalChanged ||
1255
+ backwardJumpIntervalChanged
1256
+ ) {
1248
1257
  val searchAvailable = browser?.browserManager?.config?.hasSearch ?: false
1249
1258
  mediaSessionCallback.updateMediaSession(
1250
1259
  mediaSession,
1251
1260
  options.capabilities,
1252
- options.notificationButtons,
1261
+ options.remoteButtonLayout,
1253
1262
  searchAvailable,
1263
+ options.forwardJumpInterval,
1264
+ options.backwardJumpInterval,
1254
1265
  )
1255
1266
  }
1256
1267
 
@@ -1,8 +1,8 @@
1
1
  package com.audiobrowser.player
2
2
 
3
- import com.margelo.nitro.audiobrowser.NotificationButton
4
- import com.margelo.nitro.audiobrowser.NotificationButtonLayout
5
3
  import com.margelo.nitro.audiobrowser.PlayerCapabilities
4
+ import com.margelo.nitro.audiobrowser.RemoteButton
5
+ import com.margelo.nitro.audiobrowser.RemoteButtonLayout
6
6
  import com.margelo.nitro.audiobrowser.Variant_Boolean_FavoriteConfig
7
7
  import org.junit.Assert.assertEquals
8
8
  import org.junit.Assert.assertFalse
@@ -12,8 +12,7 @@ import org.junit.Test
12
12
  /**
13
13
  * Truth tables for the pure Capability decision core: [PlayerCapabilities.isEnabled] (one rule for
14
14
  * "available by default, only false disables; favorite is opt-in; play/pause is one control") and
15
- * [deriveNotificationSlots] (the single notification slot derivation that both the button layout
16
- * and the notification player commands consume).
15
+ * [deriveButtonSlots] (the single slot derivation every Media3 assembly consumes).
17
16
  */
18
17
  class CapabilityControlsTest {
19
18
 
@@ -43,117 +42,162 @@ class CapabilityControlsTest {
43
42
  playbackRate = null,
44
43
  )
45
44
 
45
+ private fun layout(
46
+ back: RemoteButton? = null,
47
+ forward: RemoteButton? = null,
48
+ overflow: Array<RemoteButton> = emptyArray(),
49
+ ) = RemoteButtonLayout(back = back, forward = forward, overflow = overflow)
50
+
46
51
  private fun slots(
47
52
  capabilities: PlayerCapabilities,
48
- layout: NotificationButtonLayout? = null,
49
- ): List<Pair<NotificationButton, NotificationSlot>> =
50
- deriveNotificationSlots(capabilities, layout).map { it.button to it.slot }
53
+ layout: RemoteButtonLayout? = null,
54
+ ): List<Pair<RemoteButton, ButtonSlot>> =
55
+ deriveButtonSlots(capabilities, layout).map { it.button to it.slot }
51
56
 
52
57
  // MARK: isEnabled
53
58
 
54
59
  @Test
55
60
  fun `everything but favorite is enabled by default`() {
56
61
  val defaults = capabilities()
57
- for (control in Control.entries) {
58
- assertEquals("$control", control != Control.FAVORITE, defaults.isEnabled(control))
62
+ for (control in Capability.entries) {
63
+ assertEquals("$control", control != Capability.FAVORITE, defaults.isEnabled(control))
59
64
  }
60
65
  }
61
66
 
62
67
  @Test
63
68
  fun `only an explicit false disables a control`() {
64
69
  val caps = capabilities(skipToNext = false, jumpBackward = false)
65
- assertFalse(caps.isEnabled(Control.SKIP_TO_NEXT))
66
- assertFalse(caps.isEnabled(Control.JUMP_BACKWARD))
67
- assertTrue(caps.isEnabled(Control.SKIP_TO_PREVIOUS))
68
- assertTrue(caps.isEnabled(Control.JUMP_FORWARD))
70
+ assertFalse(caps.isEnabled(Capability.SKIP_TO_NEXT))
71
+ assertFalse(caps.isEnabled(Capability.JUMP_BACKWARD))
72
+ assertTrue(caps.isEnabled(Capability.SKIP_TO_PREVIOUS))
73
+ assertTrue(caps.isEnabled(Capability.JUMP_FORWARD))
69
74
  }
70
75
 
71
76
  @Test
72
77
  fun `play pause is one control - it survives a single half being disabled`() {
73
- assertTrue(capabilities(play = false).isEnabled(Control.PLAY_PAUSE))
74
- assertTrue(capabilities(pause = false).isEnabled(Control.PLAY_PAUSE))
75
- assertFalse(capabilities(play = false, pause = false).isEnabled(Control.PLAY_PAUSE))
78
+ assertTrue(capabilities(play = false).isEnabled(Capability.PLAY_PAUSE))
79
+ assertTrue(capabilities(pause = false).isEnabled(Capability.PLAY_PAUSE))
80
+ assertFalse(capabilities(play = false, pause = false).isEnabled(Capability.PLAY_PAUSE))
76
81
  }
77
82
 
78
83
  @Test
79
84
  fun `favorite is opt-in`() {
80
- assertFalse(capabilities().isEnabled(Control.FAVORITE))
81
- assertFalse(capabilities(favorite = false).isEnabled(Control.FAVORITE))
82
- assertTrue(capabilities(favorite = true).isEnabled(Control.FAVORITE))
85
+ assertFalse(capabilities().isEnabled(Capability.FAVORITE))
86
+ assertFalse(capabilities(favorite = false).isEnabled(Capability.FAVORITE))
87
+ assertTrue(capabilities(favorite = true).isEnabled(Capability.FAVORITE))
83
88
  }
84
89
 
85
- // MARK: deriveNotificationSlots — capability defaults
90
+ // MARK: deriveButtonSlots — capability defaults
86
91
 
87
92
  @Test
88
- fun `default layout puts skip on the primary slots, jump on the secondary, favorite in overflow`() {
93
+ fun `default layout puts skip on the primary positions, jump and favorite in overflow`() {
89
94
  val derived = slots(capabilities(favorite = true))
90
95
  assertEquals(
91
96
  listOf(
92
- NotificationButton.SKIP_TO_PREVIOUS to NotificationSlot.BACK,
93
- NotificationButton.SKIP_TO_NEXT to NotificationSlot.FORWARD,
94
- NotificationButton.JUMP_BACKWARD to NotificationSlot.BACK_SECONDARY,
95
- NotificationButton.JUMP_FORWARD to NotificationSlot.FORWARD_SECONDARY,
96
- NotificationButton.FAVORITE to NotificationSlot.OVERFLOW,
97
+ RemoteButton.SKIP_TO_PREVIOUS to ButtonSlot.BACK,
98
+ RemoteButton.SKIP_TO_NEXT to ButtonSlot.FORWARD,
99
+ RemoteButton.JUMP_BACKWARD to ButtonSlot.OVERFLOW,
100
+ RemoteButton.JUMP_FORWARD to ButtonSlot.OVERFLOW,
101
+ RemoteButton.FAVORITE to ButtonSlot.OVERFLOW,
97
102
  ),
98
103
  derived,
99
104
  )
100
105
  }
101
106
 
102
107
  @Test
103
- fun `jump falls back to the primary slot when skip is disabled`() {
108
+ fun `jump falls back to a primary position when skip is disabled`() {
104
109
  val derived = slots(capabilities(skipToPrevious = false, skipToNext = false))
105
110
  assertEquals(
106
111
  listOf(
107
- NotificationButton.JUMP_BACKWARD to NotificationSlot.BACK,
108
- NotificationButton.JUMP_FORWARD to NotificationSlot.FORWARD,
112
+ RemoteButton.JUMP_BACKWARD to ButtonSlot.BACK,
113
+ RemoteButton.JUMP_FORWARD to ButtonSlot.FORWARD,
109
114
  ),
110
115
  derived,
111
116
  )
112
117
  }
113
118
 
114
119
  @Test
115
- fun `a fully disabled side leaves its slot empty`() {
120
+ fun `a fully disabled side leaves its position empty`() {
116
121
  val derived =
117
122
  slots(capabilities(skipToPrevious = false, jumpBackward = false, jumpForward = false))
118
- assertEquals(listOf(NotificationButton.SKIP_TO_NEXT to NotificationSlot.FORWARD), derived)
123
+ assertEquals(listOf(RemoteButton.SKIP_TO_NEXT to ButtonSlot.FORWARD), derived)
119
124
  }
120
125
 
121
- // MARK: deriveNotificationSlots — explicit layout
126
+ // MARK: deriveButtonSlots — explicit layout
122
127
 
123
128
  @Test
124
- fun `explicit layout maps every slot and keeps overflow order`() {
125
- val layout =
126
- NotificationButtonLayout(
127
- back = NotificationButton.JUMP_BACKWARD,
128
- forward = NotificationButton.JUMP_FORWARD,
129
- backSecondary = null,
130
- forwardSecondary = null,
131
- overflow = arrayOf(NotificationButton.SKIP_TO_PREVIOUS, NotificationButton.SKIP_TO_NEXT),
129
+ fun `an explicit layout maps every field and keeps overflow order`() {
130
+ val derived =
131
+ slots(
132
+ capabilities(),
133
+ layout(
134
+ back = RemoteButton.JUMP_BACKWARD,
135
+ forward = RemoteButton.JUMP_FORWARD,
136
+ overflow = arrayOf(RemoteButton.SKIP_TO_PREVIOUS, RemoteButton.SKIP_TO_NEXT),
137
+ ),
132
138
  )
133
- val derived = slots(capabilities(), layout)
134
139
  assertEquals(
135
140
  listOf(
136
- NotificationButton.JUMP_BACKWARD to NotificationSlot.BACK,
137
- NotificationButton.JUMP_FORWARD to NotificationSlot.FORWARD,
138
- NotificationButton.SKIP_TO_PREVIOUS to NotificationSlot.OVERFLOW,
139
- NotificationButton.SKIP_TO_NEXT to NotificationSlot.OVERFLOW,
141
+ RemoteButton.JUMP_BACKWARD to ButtonSlot.BACK,
142
+ RemoteButton.JUMP_FORWARD to ButtonSlot.FORWARD,
143
+ RemoteButton.SKIP_TO_PREVIOUS to ButtonSlot.OVERFLOW,
144
+ RemoteButton.SKIP_TO_NEXT to ButtonSlot.OVERFLOW,
140
145
  ),
141
146
  derived,
142
147
  )
143
148
  }
144
149
 
145
150
  @Test
146
- fun `explicit layout entries are dropped when their capability is disabled`() {
147
- val layout =
148
- NotificationButtonLayout(
149
- back = NotificationButton.SKIP_TO_PREVIOUS,
150
- forward = NotificationButton.SKIP_TO_NEXT,
151
- backSecondary = null,
152
- forwardSecondary = null,
153
- overflow = arrayOf(NotificationButton.FAVORITE),
151
+ fun `a null field leaves that position empty`() {
152
+ val derived = slots(capabilities(), layout(back = null, forward = RemoteButton.SKIP_TO_NEXT))
153
+ assertEquals(listOf(RemoteButton.SKIP_TO_NEXT to ButtonSlot.FORWARD), derived)
154
+ }
155
+
156
+ @Test
157
+ fun `a layout is never merged with the capability defaults`() {
158
+ // Nitro collapses an omitted and a null enum field to the same Kotlin null, so a per-field
159
+ // merge is not expressible: an empty position must stay empty rather than falling back to the
160
+ // capability default. Every capability here is enabled, so a merge would add skip and jump.
161
+ val derived =
162
+ slots(capabilities(favorite = true), layout(overflow = arrayOf(RemoteButton.FAVORITE)))
163
+ assertEquals(listOf(RemoteButton.FAVORITE to ButtonSlot.OVERFLOW), derived)
164
+ }
165
+
166
+ @Test
167
+ fun `a disabled entry drops out without promoting anything into its position`() {
168
+ // favorite unset (= disabled), skipToPrevious explicitly off. skip-to-next must stay on
169
+ // FORWARD rather than sliding into the vacated BACK position.
170
+ val derived =
171
+ slots(
172
+ capabilities(skipToPrevious = false),
173
+ layout(
174
+ back = RemoteButton.SKIP_TO_PREVIOUS,
175
+ forward = RemoteButton.SKIP_TO_NEXT,
176
+ overflow = arrayOf(RemoteButton.FAVORITE),
177
+ ),
154
178
  )
155
- // favorite unset (= disabled), skipToPrevious explicitly off
156
- val derived = slots(capabilities(skipToPrevious = false), layout)
157
- assertEquals(listOf(NotificationButton.SKIP_TO_NEXT to NotificationSlot.FORWARD), derived)
179
+ assertEquals(listOf(RemoteButton.SKIP_TO_NEXT to ButtonSlot.FORWARD), derived)
180
+ }
181
+
182
+ // MARK: sameAs
183
+
184
+ @Test
185
+ fun `sameAs compares overflow by content, not by array identity`() {
186
+ val a = layout(back = RemoteButton.JUMP_BACKWARD, overflow = arrayOf(RemoteButton.FAVORITE))
187
+ val b = layout(back = RemoteButton.JUMP_BACKWARD, overflow = arrayOf(RemoteButton.FAVORITE))
188
+ // Distinct Array instances — data-class `==` would call these different and republish the
189
+ // layout to Android Auto on every unrelated options update.
190
+ assertTrue(a.sameAs(b))
191
+ assertFalse(a.sameAs(layout(back = RemoteButton.JUMP_BACKWARD)))
192
+ assertFalse(
193
+ a.sameAs(layout(back = RemoteButton.JUMP_FORWARD, overflow = arrayOf(RemoteButton.FAVORITE)))
194
+ )
195
+ }
196
+
197
+ @Test
198
+ fun `sameAs treats null as its own layout`() {
199
+ assertTrue(null.sameAs(null))
200
+ assertFalse(layout(back = RemoteButton.SKIP_TO_NEXT).sameAs(null))
201
+ assertFalse(null.sameAs(layout(back = RemoteButton.SKIP_TO_NEXT)))
158
202
  }
159
203
  }
@@ -0,0 +1,45 @@
1
+ package com.audiobrowser.player
2
+
3
+ import androidx.media3.session.CommandButton
4
+ import org.junit.Assert.assertArrayEquals
5
+ import org.junit.Assert.assertTrue
6
+ import org.junit.Test
7
+
8
+ /**
9
+ * Guards the slot preference lists handed to Media3.
10
+ *
11
+ * The invariant that matters is the OVERFLOW fallback: every flattening keeps a button only if it
12
+ * won SLOT_BACK, won SLOT_FORWARD, or names SLOT_OVERFLOW. A slot that stops naming OVERFLOW
13
+ * silently deletes its button from the notification and the car the moment its primary position is
14
+ * unavailable, so the loop below asserts the property for every slot rather than only today's
15
+ * three.
16
+ */
17
+ class MediaSessionSlotMappingTest {
18
+
19
+ @Test
20
+ fun `primary slots are preferred with an overflow fallback`() {
21
+ assertArrayEquals(
22
+ intArrayOf(CommandButton.SLOT_BACK, CommandButton.SLOT_OVERFLOW),
23
+ media3SlotsFor(ButtonSlot.BACK),
24
+ )
25
+ assertArrayEquals(
26
+ intArrayOf(CommandButton.SLOT_FORWARD, CommandButton.SLOT_OVERFLOW),
27
+ media3SlotsFor(ButtonSlot.FORWARD),
28
+ )
29
+ }
30
+
31
+ @Test
32
+ fun `overflow does not repeat itself`() {
33
+ assertArrayEquals(intArrayOf(CommandButton.SLOT_OVERFLOW), media3SlotsFor(ButtonSlot.OVERFLOW))
34
+ }
35
+
36
+ @Test
37
+ fun `every slot names OVERFLOW so nothing can be dropped`() {
38
+ for (slot in ButtonSlot.entries) {
39
+ assertTrue(
40
+ "$slot must name SLOT_OVERFLOW or its button is dropped from the notification and the car",
41
+ media3SlotsFor(slot).contains(CommandButton.SLOT_OVERFLOW),
42
+ )
43
+ }
44
+ }
45
+ }
@@ -31,34 +31,64 @@ var _useNativeUpdatedValue = require("../../utils/useNativeUpdatedValue");
31
31
  */
32
32
 
33
33
  /**
34
- * Buttons that can be assigned to Android notification button slots.
35
- * These represent the interactive buttons users can tap in the notification.
34
+ * Buttons that can be placed in the Android button layout — the interactive
35
+ * controls a listener taps in the notification, on the Android Auto Now Playing
36
+ * screen, and in the Android 13+ system media controls.
36
37
  */
37
38
 
38
39
  /**
39
- * Configuration for notification button layout on Android.
40
- * Allows explicit control over which buttons appear in which slots.
40
+ * Where each button sits on Android.
41
41
  *
42
- * Slot behavior:
43
- * - **Omit a slot**: Derive from capabilities (smart default)
44
- * - **Set to null**: Explicitly empty slot
45
- * - **Set to button**: Show that button in that slot
42
+ * Android offers exactly three positions, and play/pause always occupies the
43
+ * centre — you cannot move it, and you cannot put two buttons on the same side
44
+ * of it:
45
+ *
46
+ * ```
47
+ * back │ ▶ play/pause │ forward overflow ⋯
48
+ * ```
49
+ *
50
+ * - **`back`** — the single position left of play/pause
51
+ * - **`forward`** — the single position right of play/pause
52
+ * - **`overflow`** — everything else, in priority order
53
+ *
54
+ * **A layout describes the whole arrangement.** All three fields are required —
55
+ * there is no per-field merge with the capability defaults, so what you write is
56
+ * exactly what renders. Use `undefined` for an empty position and `[]` for no
57
+ * overflow. To go back to the derived defaults, omit `remoteButtonLayout`
58
+ * entirely (or set it to `null`); that is the only switch.
59
+ *
60
+ * **Overflow order is priority, not coordinates.** Each surface renders as many
61
+ * buttons as it has room for, taking them from the front — a phone
62
+ * notification, the Android 13+ system media controls, and a car head unit all
63
+ * have different budgets. A head unit with a spare slot may promote the first
64
+ * overflow entry onto the main row, and a long list gets truncated by whichever
65
+ * surface is showing it. Put what matters most first.
66
+ *
67
+ * Placement never changes what a control *can* do: a button left out entirely
68
+ * still responds to a Bluetooth remote or headset, as long as its
69
+ * {@link PlayerCapabilities} entry is enabled.
46
70
  *
47
71
  * @example
48
72
  * ```typescript
49
- * // Podcast-style: jump buttons as primary
50
- * notificationButtons: {
73
+ * // Podcast-style: jump either side of play/pause
74
+ * remoteButtonLayout: {
51
75
  * back: 'jump-backward',
52
76
  * forward: 'jump-forward',
53
- * overflow: ['favorite']
77
+ * overflow: ['skip-to-previous', 'skip-to-next', 'favorite']
54
78
  * }
55
79
  *
56
- * // Music-style: skip as primary, jump as secondary
57
- * notificationButtons: {
80
+ * // Music-style: skip either side of play/pause
81
+ * remoteButtonLayout: {
58
82
  * back: 'skip-to-previous',
59
83
  * forward: 'skip-to-next',
60
- * backSecondary: 'jump-backward',
61
- * forwardSecondary: 'jump-forward'
84
+ * overflow: ['favorite']
85
+ * }
86
+ *
87
+ * // Live radio: forward only, nothing to the left
88
+ * remoteButtonLayout: {
89
+ * back: undefined,
90
+ * forward: 'skip-to-next',
91
+ * overflow: []
62
92
  * }
63
93
  * ```
64
94
  */
@@ -1 +1 @@
1
- {"version":3,"names":["_native","require","_NativeUpdatedValue","_useNativeUpdatedValue","MAX_CARPLAY_NOW_PLAYING_BUTTONS","validateIOSUpdateOptions","ios","buttons","carPlayNowPlayingButtons","length","console","warn","updateOptions","options","nativeBrowser","getOptions","onOptionsChanged","exports","NativeUpdatedValue","emitterize","cb","useOptions","useNativeUpdatedValue"],"sourceRoot":"../../../../src","sources":["features/player/options.ts"],"mappings":";;;;;;;;;;AAEA,IAAAA,OAAA,GAAAC,OAAA;AACA,IAAAC,mBAAA,GAAAD,OAAA;AACA,IAAAE,sBAAA,GAAAF,OAAA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AA+FA;AACA;AACA;AACA;;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAkCA;AACA;AACA;AACA;;AA4EA;AACA;AACA;AACA;;AAOA;AACA;AACA;AACA;;AA6BA;AACA;AACA;AACA;;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AA6CA;AACA;AACA;AACA;;AA8BA;;AAEA,MAAMG,+BAA+B,GAAG,CAAC;;AAEzC;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,wBAAwBA,CAACC,GAAsB,EAAQ;EACrE,MAAMC,OAAO,GAAGD,GAAG,EAAEE,wBAAwB;EAC7C,IAAID,OAAO,IAAIA,OAAO,CAACE,MAAM,GAAGL,+BAA+B,EAAE;IAC/DM,OAAO,CAACC,IAAI,CACV,gCAAgCJ,OAAO,CAACE,MAAM,uBAAuB,GACnE,6CAA6CL,+BAA+B,GAChF,CAAC;EACH;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASQ,aAAaA,CAACC,OAAsB,EAAQ;EAC1DR,wBAAwB,CAACQ,OAAO,CAACP,GAAG,CAAC;EACrCQ,qBAAa,CAACF,aAAa,CAACC,OAAO,CAAC;AACtC;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASE,UAAUA,CAAA,EAAY;EACpC,OAAOD,qBAAa,CAACC,UAAU,CAAC,CAAC;AACnC;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACO,MAAMC,gBAAgB,GAAAC,OAAA,CAAAD,gBAAA,GAAGE,sCAAkB,CAACC,UAAU,CAC1DC,EAAE,IAAMN,qBAAa,CAACE,gBAAgB,GAAGI,EAC5C,CAAC;;AAED;;AAEA;AACA;AACA;AACA;AACO,SAASC,UAAUA,CAAA,EAAY;EACpC,OAAO,IAAAC,4CAAqB,EAACP,UAAU,EAAEC,gBAAgB,CAAC;AAC5D","ignoreList":[]}
1
+ {"version":3,"names":["_native","require","_NativeUpdatedValue","_useNativeUpdatedValue","MAX_CARPLAY_NOW_PLAYING_BUTTONS","validateIOSUpdateOptions","ios","buttons","carPlayNowPlayingButtons","length","console","warn","updateOptions","options","nativeBrowser","getOptions","onOptionsChanged","exports","NativeUpdatedValue","emitterize","cb","useOptions","useNativeUpdatedValue"],"sourceRoot":"../../../../src","sources":["features/player/options.ts"],"mappings":";;;;;;;;;;AAEA,IAAAA,OAAA,GAAAC,OAAA;AACA,IAAAC,mBAAA,GAAAD,OAAA;AACA,IAAAE,sBAAA,GAAAF,OAAA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAgGA;AACA;AACA;AACA;AACA;;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AA4CA;AACA;AACA;AACA;;AA4EA;AACA;AACA;AACA;;AAOA;AACA;AACA;AACA;;AA6BA;AACA;AACA;AACA;;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAuDA;AACA;AACA;AACA;;AAwCA;;AAEA,MAAMG,+BAA+B,GAAG,CAAC;;AAEzC;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,wBAAwBA,CAACC,GAAsB,EAAQ;EACrE,MAAMC,OAAO,GAAGD,GAAG,EAAEE,wBAAwB;EAC7C,IAAID,OAAO,IAAIA,OAAO,CAACE,MAAM,GAAGL,+BAA+B,EAAE;IAC/DM,OAAO,CAACC,IAAI,CACV,gCAAgCJ,OAAO,CAACE,MAAM,uBAAuB,GACnE,6CAA6CL,+BAA+B,GAChF,CAAC;EACH;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASQ,aAAaA,CAACC,OAAsB,EAAQ;EAC1DR,wBAAwB,CAACQ,OAAO,CAACP,GAAG,CAAC;EACrCQ,qBAAa,CAACF,aAAa,CAACC,OAAO,CAAC;AACtC;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASE,UAAUA,CAAA,EAAY;EACpC,OAAOD,qBAAa,CAACC,UAAU,CAAC,CAAC;AACnC;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACO,MAAMC,gBAAgB,GAAAC,OAAA,CAAAD,gBAAA,GAAGE,sCAAkB,CAACC,UAAU,CAC1DC,EAAE,IAAMN,qBAAa,CAACE,gBAAgB,GAAGI,EAC5C,CAAC;;AAED;;AAEA;AACA;AACA;AACA;AACO,SAASC,UAAUA,CAAA,EAAY;EACpC,OAAO,IAAAC,4CAAqB,EAACP,UAAU,EAAEC,gBAAgB,CAAC;AAC5D","ignoreList":[]}
@@ -223,7 +223,7 @@ var _options = require("./options");
223
223
 
224
224
  /**
225
225
  * Android setup options: the engine-construction fields plus the Android runtime options
226
- * ({@link AndroidUpdateOptions} — `notificationButtons`, `skipSilence`, …), all expressible at
226
+ * ({@link AndroidUpdateOptions} — `remoteButtonLayout`, `skipSilence`, …), all expressible at
227
227
  * launch. The consumer never has to know which is which; `setupPlayer` routes each field to
228
228
  * where it's applied.
229
229
  */
@@ -290,7 +290,7 @@ function wrapNowPlayingFormatter(formatter) {
290
290
 
291
291
  /**
292
292
  * The object's entries whose values aren't `undefined`. `null` stays: it's meaningful on some
293
- * fields (`progressUpdateEventInterval: null` disables progress events, `notificationButtons:
293
+ * fields (`progressUpdateEventInterval: null` disables progress events, `remoteButtonLayout:
294
294
  * null` empties the layout), so only `undefined` means "not provided".
295
295
  */
296
296
  function definedFields(obj) {
@@ -311,7 +311,7 @@ function nonEmpty(key, value) {
311
311
  /**
312
312
  * Initializes the player with the specified options — the full launch description in one
313
313
  * declarative, atomic call. Runtime options (`capabilities`, jump intervals,
314
- * `notificationButtons`, …) and the initial `playWhenReady` / `repeatMode` are applied
314
+ * `remoteButtonLayout`, …) and the initial `playWhenReady` / `repeatMode` are applied
315
315
  * natively as part of setup itself, so there is no setup-then-command ordering for consumers
316
316
  * to know about, and no window where the player exists without its options.
317
317
  *
@@ -328,7 +328,14 @@ function nonEmpty(key, value) {
328
328
  * playWhenReady: true,
329
329
  * repeatMode: 'queue',
330
330
  * capabilities: { favorite: true },
331
- * android: { audioContentType: 'music', notificationButtons: { overflow: ['favorite'] } },
331
+ * android: {
332
+ * audioContentType: 'music',
333
+ * remoteButtonLayout: {
334
+ * back: 'jump-backward',
335
+ * forward: 'jump-forward',
336
+ * overflow: ['favorite']
337
+ * }
338
+ * },
332
339
  * ios: { category: 'playback' }
333
340
  * })
334
341
  * ```
@@ -351,7 +358,7 @@ async function setupPlayer(options = {}) {
351
358
  const {
352
359
  appKilledPlaybackBehavior,
353
360
  skipSilence,
354
- notificationButtons,
361
+ remoteButtonLayout,
355
362
  ...androidSetup
356
363
  } = android;
357
364
  const {
@@ -374,7 +381,7 @@ async function setupPlayer(options = {}) {
374
381
  ...nonEmpty('android', definedFields({
375
382
  appKilledPlaybackBehavior,
376
383
  skipSilence,
377
- notificationButtons
384
+ remoteButtonLayout
378
385
  })),
379
386
  ...nonEmpty('ios', iosUpdate)
380
387
  });
@@ -1 +1 @@
1
- {"version":3,"names":["_native","require","_options","resolveNowPlaying","value","autoUpdateNowPlayingMetadata","nowPlayingMetadataFormatter","wrapNowPlayingFormatter","undefined","formatter","params","definedFields","obj","out","key","nonEmpty","Object","keys","length","setupPlayer","options","nowPlaying","capabilities","forwardJumpInterval","backwardJumpInterval","progressUpdateEventInterval","android","ios","nativeSetup","appKilledPlaybackBehavior","skipSilence","notificationButtons","androidSetup","playbackRates","carPlayUpNextButton","carPlayNowPlayingButtons","iosSetup","iosUpdate","validateIOSUpdateOptions","updates","nativeBrowser"],"sourceRoot":"../../../../src","sources":["features/player/setup.ts"],"mappings":";;;;;;AAYA,IAAAA,OAAA,GAAAC,OAAA;AACA,IAAAC,QAAA,GAAAD,OAAA;AAbA;;AAeA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAsBA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAmBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AA2BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;;AA2GA;;AA4CA;AACA;AACA;AACA;AACA;;AAgCA;;AAEA;AACA;AACA;AACA;;AAMA;AACA;AAC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","ignoreList":[]}
1
+ {"version":3,"names":["_native","require","_options","resolveNowPlaying","value","autoUpdateNowPlayingMetadata","nowPlayingMetadataFormatter","wrapNowPlayingFormatter","undefined","formatter","params","definedFields","obj","out","key","nonEmpty","Object","keys","length","setupPlayer","options","nowPlaying","capabilities","forwardJumpInterval","backwardJumpInterval","progressUpdateEventInterval","android","ios","nativeSetup","appKilledPlaybackBehavior","skipSilence","remoteButtonLayout","androidSetup","playbackRates","carPlayUpNextButton","carPlayNowPlayingButtons","iosSetup","iosUpdate","validateIOSUpdateOptions","updates","nativeBrowser"],"sourceRoot":"../../../../src","sources":["features/player/setup.ts"],"mappings":";;;;;;AAYA,IAAAA,OAAA,GAAAC,OAAA;AACA,IAAAC,QAAA,GAAAD,OAAA;AAbA;;AAeA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAsBA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAmBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AA2BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;;AA2GA;;AA4CA;AACA;AACA;AACA;AACA;;AAgCA;;AAEA;AACA;AACA;AACA;;AAMA;AACA;AAC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","ignoreList":[]}
@@ -24,34 +24,64 @@ import { useNativeUpdatedValue } from '../../utils/useNativeUpdatedValue';
24
24
  */
25
25
 
26
26
  /**
27
- * Buttons that can be assigned to Android notification button slots.
28
- * These represent the interactive buttons users can tap in the notification.
27
+ * Buttons that can be placed in the Android button layout — the interactive
28
+ * controls a listener taps in the notification, on the Android Auto Now Playing
29
+ * screen, and in the Android 13+ system media controls.
29
30
  */
30
31
 
31
32
  /**
32
- * Configuration for notification button layout on Android.
33
- * Allows explicit control over which buttons appear in which slots.
33
+ * Where each button sits on Android.
34
34
  *
35
- * Slot behavior:
36
- * - **Omit a slot**: Derive from capabilities (smart default)
37
- * - **Set to null**: Explicitly empty slot
38
- * - **Set to button**: Show that button in that slot
35
+ * Android offers exactly three positions, and play/pause always occupies the
36
+ * centre — you cannot move it, and you cannot put two buttons on the same side
37
+ * of it:
38
+ *
39
+ * ```
40
+ * back │ ▶ play/pause │ forward overflow ⋯
41
+ * ```
42
+ *
43
+ * - **`back`** — the single position left of play/pause
44
+ * - **`forward`** — the single position right of play/pause
45
+ * - **`overflow`** — everything else, in priority order
46
+ *
47
+ * **A layout describes the whole arrangement.** All three fields are required —
48
+ * there is no per-field merge with the capability defaults, so what you write is
49
+ * exactly what renders. Use `undefined` for an empty position and `[]` for no
50
+ * overflow. To go back to the derived defaults, omit `remoteButtonLayout`
51
+ * entirely (or set it to `null`); that is the only switch.
52
+ *
53
+ * **Overflow order is priority, not coordinates.** Each surface renders as many
54
+ * buttons as it has room for, taking them from the front — a phone
55
+ * notification, the Android 13+ system media controls, and a car head unit all
56
+ * have different budgets. A head unit with a spare slot may promote the first
57
+ * overflow entry onto the main row, and a long list gets truncated by whichever
58
+ * surface is showing it. Put what matters most first.
59
+ *
60
+ * Placement never changes what a control *can* do: a button left out entirely
61
+ * still responds to a Bluetooth remote or headset, as long as its
62
+ * {@link PlayerCapabilities} entry is enabled.
39
63
  *
40
64
  * @example
41
65
  * ```typescript
42
- * // Podcast-style: jump buttons as primary
43
- * notificationButtons: {
66
+ * // Podcast-style: jump either side of play/pause
67
+ * remoteButtonLayout: {
44
68
  * back: 'jump-backward',
45
69
  * forward: 'jump-forward',
46
- * overflow: ['favorite']
70
+ * overflow: ['skip-to-previous', 'skip-to-next', 'favorite']
47
71
  * }
48
72
  *
49
- * // Music-style: skip as primary, jump as secondary
50
- * notificationButtons: {
73
+ * // Music-style: skip either side of play/pause
74
+ * remoteButtonLayout: {
51
75
  * back: 'skip-to-previous',
52
76
  * forward: 'skip-to-next',
53
- * backSecondary: 'jump-backward',
54
- * forwardSecondary: 'jump-forward'
77
+ * overflow: ['favorite']
78
+ * }
79
+ *
80
+ * // Live radio: forward only, nothing to the left
81
+ * remoteButtonLayout: {
82
+ * back: undefined,
83
+ * forward: 'skip-to-next',
84
+ * overflow: []
55
85
  * }
56
86
  * ```
57
87
  */
@@ -1 +1 @@
1
- {"version":3,"names":["nativeBrowser","NativeUpdatedValue","useNativeUpdatedValue","MAX_CARPLAY_NOW_PLAYING_BUTTONS","validateIOSUpdateOptions","ios","buttons","carPlayNowPlayingButtons","length","console","warn","updateOptions","options","getOptions","onOptionsChanged","emitterize","cb","useOptions"],"sourceRoot":"../../../../src","sources":["features/player/options.ts"],"mappings":";;AAEA,SAASA,aAAa,QAAQ,cAAc;AAC5C,SAASC,kBAAkB,QAAQ,gCAAgC;AACnE,SAASC,qBAAqB,QAAQ,mCAAmC;;AAEzE;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AA+FA;AACA;AACA;AACA;;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAkCA;AACA;AACA;AACA;;AA4EA;AACA;AACA;AACA;;AAOA;AACA;AACA;AACA;;AA6BA;AACA;AACA;AACA;;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AA6CA;AACA;AACA;AACA;;AA8BA;;AAEA,MAAMC,+BAA+B,GAAG,CAAC;;AAEzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,wBAAwBA,CAACC,GAAsB,EAAQ;EACrE,MAAMC,OAAO,GAAGD,GAAG,EAAEE,wBAAwB;EAC7C,IAAID,OAAO,IAAIA,OAAO,CAACE,MAAM,GAAGL,+BAA+B,EAAE;IAC/DM,OAAO,CAACC,IAAI,CACV,gCAAgCJ,OAAO,CAACE,MAAM,uBAAuB,GACnE,6CAA6CL,+BAA+B,GAChF,CAAC;EACH;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASQ,aAAaA,CAACC,OAAsB,EAAQ;EAC1DR,wBAAwB,CAACQ,OAAO,CAACP,GAAG,CAAC;EACrCL,aAAa,CAACW,aAAa,CAACC,OAAO,CAAC;AACtC;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAAA,EAAY;EACpC,OAAOb,aAAa,CAACa,UAAU,CAAC,CAAC;AACnC;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,gBAAgB,GAAGb,kBAAkB,CAACc,UAAU,CAC1DC,EAAE,IAAMhB,aAAa,CAACc,gBAAgB,GAAGE,EAC5C,CAAC;;AAED;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAAA,EAAY;EACpC,OAAOf,qBAAqB,CAACW,UAAU,EAAEC,gBAAgB,CAAC;AAC5D","ignoreList":[]}
1
+ {"version":3,"names":["nativeBrowser","NativeUpdatedValue","useNativeUpdatedValue","MAX_CARPLAY_NOW_PLAYING_BUTTONS","validateIOSUpdateOptions","ios","buttons","carPlayNowPlayingButtons","length","console","warn","updateOptions","options","getOptions","onOptionsChanged","emitterize","cb","useOptions"],"sourceRoot":"../../../../src","sources":["features/player/options.ts"],"mappings":";;AAEA,SAASA,aAAa,QAAQ,cAAc;AAC5C,SAASC,kBAAkB,QAAQ,gCAAgC;AACnE,SAASC,qBAAqB,QAAQ,mCAAmC;;AAEzE;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAgGA;AACA;AACA;AACA;AACA;;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AA4CA;AACA;AACA;AACA;;AA4EA;AACA;AACA;AACA;;AAOA;AACA;AACA;AACA;;AA6BA;AACA;AACA;AACA;;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAuDA;AACA;AACA;AACA;;AAwCA;;AAEA,MAAMC,+BAA+B,GAAG,CAAC;;AAEzC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,wBAAwBA,CAACC,GAAsB,EAAQ;EACrE,MAAMC,OAAO,GAAGD,GAAG,EAAEE,wBAAwB;EAC7C,IAAID,OAAO,IAAIA,OAAO,CAACE,MAAM,GAAGL,+BAA+B,EAAE;IAC/DM,OAAO,CAACC,IAAI,CACV,gCAAgCJ,OAAO,CAACE,MAAM,uBAAuB,GACnE,6CAA6CL,+BAA+B,GAChF,CAAC;EACH;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASQ,aAAaA,CAACC,OAAsB,EAAQ;EAC1DR,wBAAwB,CAACQ,OAAO,CAACP,GAAG,CAAC;EACrCL,aAAa,CAACW,aAAa,CAACC,OAAO,CAAC;AACtC;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAAA,EAAY;EACpC,OAAOb,aAAa,CAACa,UAAU,CAAC,CAAC;AACnC;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,gBAAgB,GAAGb,kBAAkB,CAACc,UAAU,CAC1DC,EAAE,IAAMhB,aAAa,CAACc,gBAAgB,GAAGE,EAC5C,CAAC;;AAED;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAAA,EAAY;EACpC,OAAOf,qBAAqB,CAACW,UAAU,EAAEC,gBAAgB,CAAC;AAC5D","ignoreList":[]}