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.
- package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +11 -11
- package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +111 -58
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +9 -5
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +218 -252
- package/android/src/main/java/com/audiobrowser/player/Player.kt +19 -8
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +100 -56
- package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
- package/lib/commonjs/features/player/options.js +45 -15
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +13 -6
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/module/features/player/options.js +45 -15
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +13 -6
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/typescript/src/features/player/options.d.ts +86 -36
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts +10 -3
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +1 -1
- package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -10
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +5 -5
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +9 -9
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +5 -5
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +5 -5
- package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -10
- package/nitrogen/generated/android/c++/JOptions.hpp +5 -5
- package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +19 -19
- package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → RemoteButton.kt} +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +36 -51
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +6 -6
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +6 -6
- package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -12
- package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -12
- package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +6 -6
- package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
- package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
- package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -8
- package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -8
- package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +19 -19
- package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
- package/package.json +1 -1
- package/src/features/player/options.ts +97 -37
- package/src/features/player/setup.test.ts +12 -4
- package/src/features/player/setup.ts +14 -7
- package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
- package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
- 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.
|
|
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
|
-
*
|
|
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
|
|
1219
|
-
previousOptions.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
* [
|
|
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:
|
|
49
|
-
): List<Pair<
|
|
50
|
-
|
|
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
|
|
58
|
-
assertEquals("$control", 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(
|
|
66
|
-
assertFalse(caps.isEnabled(
|
|
67
|
-
assertTrue(caps.isEnabled(
|
|
68
|
-
assertTrue(caps.isEnabled(
|
|
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(
|
|
74
|
-
assertTrue(capabilities(pause = false).isEnabled(
|
|
75
|
-
assertFalse(capabilities(play = false, pause = false).isEnabled(
|
|
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(
|
|
81
|
-
assertFalse(capabilities(favorite = false).isEnabled(
|
|
82
|
-
assertTrue(capabilities(favorite = true).isEnabled(
|
|
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:
|
|
90
|
+
// MARK: deriveButtonSlots — capability defaults
|
|
86
91
|
|
|
87
92
|
@Test
|
|
88
|
-
fun `default layout puts skip on the primary
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
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
|
-
|
|
108
|
-
|
|
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
|
|
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(
|
|
123
|
+
assertEquals(listOf(RemoteButton.SKIP_TO_NEXT to ButtonSlot.FORWARD), derived)
|
|
119
124
|
}
|
|
120
125
|
|
|
121
|
-
// MARK:
|
|
126
|
+
// MARK: deriveButtonSlots — explicit layout
|
|
122
127
|
|
|
123
128
|
@Test
|
|
124
|
-
fun `explicit layout maps every
|
|
125
|
-
val
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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 `
|
|
147
|
-
val layout =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
35
|
-
*
|
|
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
|
-
*
|
|
40
|
-
* Allows explicit control over which buttons appear in which slots.
|
|
40
|
+
* Where each button sits on Android.
|
|
41
41
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
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
|
|
50
|
-
*
|
|
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
|
|
57
|
-
*
|
|
80
|
+
* // Music-style: skip either side of play/pause
|
|
81
|
+
* remoteButtonLayout: {
|
|
58
82
|
* back: 'skip-to-previous',
|
|
59
83
|
* forward: 'skip-to-next',
|
|
60
|
-
*
|
|
61
|
-
*
|
|
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;;
|
|
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} — `
|
|
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, `
|
|
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
|
-
* `
|
|
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: {
|
|
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
|
-
|
|
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
|
-
|
|
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","
|
|
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
|
|
28
|
-
*
|
|
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
|
-
*
|
|
33
|
-
* Allows explicit control over which buttons appear in which slots.
|
|
33
|
+
* Where each button sits on Android.
|
|
34
34
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
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
|
|
43
|
-
*
|
|
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
|
|
50
|
-
*
|
|
73
|
+
* // Music-style: skip either side of play/pause
|
|
74
|
+
* remoteButtonLayout: {
|
|
51
75
|
* back: 'skip-to-previous',
|
|
52
76
|
* forward: 'skip-to-next',
|
|
53
|
-
*
|
|
54
|
-
*
|
|
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;;
|
|
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":[]}
|