react-native-nitro-player 1.4.4-alpha.0 → 1.5.0-alpha.1

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 (67) hide show
  1. package/NitroPlayer.podspec +6 -0
  2. package/README.md +115 -0
  3. package/android/build.gradle +8 -0
  4. package/android/src/main/AndroidManifest.xml +11 -0
  5. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridCast.kt +79 -0
  6. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/ExoPlayerCore.kt +10 -7
  7. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCast.kt +122 -0
  8. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +13 -0
  9. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerSetup.kt +7 -0
  10. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/CastSessionController.kt +160 -0
  11. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/NitroCastConfig.kt +23 -0
  12. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/NitroCastOptionsProvider.kt +30 -0
  13. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/NitroMediaItemConverter.kt +53 -0
  14. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/PlaybackService.kt +33 -0
  15. package/ios/HybridCast.swift +63 -0
  16. package/ios/core/CastSessionManager.swift +373 -0
  17. package/ios/core/TrackPlayerCast.swift +109 -0
  18. package/ios/core/TrackPlayerCore.swift +23 -2
  19. package/ios/core/TrackPlayerPlayback.swift +32 -0
  20. package/ios/core/TrackPlayerQueue.swift +8 -1
  21. package/ios/core/TrackPlayerQueueBuild.swift +21 -0
  22. package/lib/components/CastButton.d.ts +44 -0
  23. package/lib/components/CastButton.js +125 -0
  24. package/lib/hooks/castCallbackManager.d.ts +19 -0
  25. package/lib/hooks/castCallbackManager.js +43 -0
  26. package/lib/hooks/index.d.ts +3 -0
  27. package/lib/hooks/index.js +3 -0
  28. package/lib/hooks/useCastState.d.ts +27 -0
  29. package/lib/hooks/useCastState.js +49 -0
  30. package/lib/hooks/useIsCasting.d.ts +17 -0
  31. package/lib/hooks/useIsCasting.js +43 -0
  32. package/lib/index.d.ts +5 -0
  33. package/lib/index.js +4 -1
  34. package/lib/specs/Cast.nitro.d.ts +58 -0
  35. package/lib/specs/Cast.nitro.js +1 -0
  36. package/nitro.json +10 -0
  37. package/nitrogen/generated/android/NitroPlayer+autolinking.cmake +3 -1
  38. package/nitrogen/generated/android/NitroPlayerOnLoad.cpp +18 -0
  39. package/nitrogen/generated/android/c++/JCastState.hpp +64 -0
  40. package/nitrogen/generated/android/c++/JFunc_void_CastState_std__optional_std__variant_nitro__NullType__std__string__.hpp +83 -0
  41. package/nitrogen/generated/android/c++/JHybridCastSpec.cpp +115 -0
  42. package/nitrogen/generated/android/c++/JHybridCastSpec.hpp +69 -0
  43. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/CastState.kt +25 -0
  44. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_CastState_std__optional_std__variant_nitro__NullType__std__string__.kt +80 -0
  45. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridCastSpec.kt +85 -0
  46. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.cpp +38 -13
  47. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.hpp +176 -125
  48. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Umbrella.hpp +8 -0
  49. package/nitrogen/generated/ios/NitroPlayerAutolinking.mm +8 -0
  50. package/nitrogen/generated/ios/NitroPlayerAutolinking.swift +12 -0
  51. package/nitrogen/generated/ios/c++/HybridCastSpecSwift.cpp +11 -0
  52. package/nitrogen/generated/ios/c++/HybridCastSpecSwift.hpp +133 -0
  53. package/nitrogen/generated/ios/swift/CastState.swift +48 -0
  54. package/nitrogen/generated/ios/swift/Func_void_CastState_std__optional_std__variant_nitro__NullType__std__string__.swift +65 -0
  55. package/nitrogen/generated/ios/swift/HybridCastSpec.swift +61 -0
  56. package/nitrogen/generated/ios/swift/HybridCastSpec_cxx.swift +254 -0
  57. package/nitrogen/generated/shared/c++/CastState.hpp +84 -0
  58. package/nitrogen/generated/shared/c++/HybridCastSpec.cpp +27 -0
  59. package/nitrogen/generated/shared/c++/HybridCastSpec.hpp +75 -0
  60. package/package.json +3 -3
  61. package/src/components/CastButton.tsx +233 -0
  62. package/src/hooks/castCallbackManager.ts +55 -0
  63. package/src/hooks/index.ts +5 -0
  64. package/src/hooks/useCastState.ts +67 -0
  65. package/src/hooks/useIsCasting.ts +47 -0
  66. package/src/index.ts +9 -0
  67. package/src/specs/Cast.nitro.ts +72 -0
@@ -0,0 +1,75 @@
1
+ ///
2
+ /// HybridCastSpec.hpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #pragma once
9
+
10
+ #if __has_include(<NitroModules/HybridObject.hpp>)
11
+ #include <NitroModules/HybridObject.hpp>
12
+ #else
13
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
14
+ #endif
15
+
16
+ // Forward declaration of `CastState` to properly resolve imports.
17
+ namespace margelo::nitro::nitroplayer { enum class CastState; }
18
+
19
+ #include <NitroModules/Promise.hpp>
20
+ #include <string>
21
+ #include <optional>
22
+ #include "CastState.hpp"
23
+ #include <NitroModules/Null.hpp>
24
+ #include <variant>
25
+ #include <functional>
26
+
27
+ namespace margelo::nitro::nitroplayer {
28
+
29
+ using namespace margelo::nitro;
30
+
31
+ /**
32
+ * An abstract base class for `Cast`
33
+ * Inherit this class to create instances of `HybridCastSpec` in C++.
34
+ * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual.
35
+ * @example
36
+ * ```cpp
37
+ * class HybridCast: public HybridCastSpec {
38
+ * public:
39
+ * HybridCast(...): HybridObject(TAG) { ... }
40
+ * // ...
41
+ * };
42
+ * ```
43
+ */
44
+ class HybridCastSpec: public virtual HybridObject {
45
+ public:
46
+ // Constructor
47
+ explicit HybridCastSpec(): HybridObject(TAG) { }
48
+
49
+ // Destructor
50
+ ~HybridCastSpec() override = default;
51
+
52
+ public:
53
+ // Properties
54
+
55
+
56
+ public:
57
+ // Methods
58
+ virtual std::shared_ptr<Promise<void>> configure(const std::optional<std::string>& receiverApplicationId) = 0;
59
+ virtual bool isCasting() = 0;
60
+ virtual CastState getCastState() = 0;
61
+ virtual std::variant<nitro::NullType, std::string> getCastDeviceName() = 0;
62
+ virtual void showCastPicker() = 0;
63
+ virtual std::shared_ptr<Promise<void>> endCastSession() = 0;
64
+ virtual void onCastStateChange(const std::function<void(CastState /* state */, const std::optional<std::variant<nitro::NullType, std::string>>& /* deviceName */)>& callback) = 0;
65
+
66
+ protected:
67
+ // Hybrid Setup
68
+ void loadHybridMethods() override;
69
+
70
+ protected:
71
+ // Tag for logging
72
+ static constexpr auto TAG = "Cast";
73
+ };
74
+
75
+ } // namespace margelo::nitro::nitroplayer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-player",
3
- "version": "1.4.4-alpha.0",
3
+ "version": "1.5.0-alpha.1",
4
4
  "description": "A powerful audio player library for React Native with playlist management, playback controls, and support for Android Auto and CarPlay",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",
@@ -57,7 +57,7 @@
57
57
  "registry": "https://registry.npmjs.org/"
58
58
  },
59
59
  "devDependencies": {
60
- "@react-native/eslint-config": "0.85.0-rc.6",
60
+ "@react-native/eslint-config": "0.86.0",
61
61
  "@release-it-plugins/workspaces": "^5.0.3",
62
62
  "@release-it/bumper": "^7.0.5",
63
63
  "@release-it/conventional-changelog": "^10.0.1",
@@ -68,7 +68,7 @@
68
68
  "nitrogen": "^0.35.2",
69
69
  "prettier": "^3.3.3",
70
70
  "react": "19.2.3",
71
- "react-native": "0.85.0-rc.6",
71
+ "react-native": "0.86.0",
72
72
  "react-native-nitro-modules": "^0.35.2",
73
73
  "release-it": "^19.0.4",
74
74
  "typescript": "^5.8.3"
@@ -0,0 +1,233 @@
1
+ import React from 'react'
2
+ import {
3
+ Pressable,
4
+ View,
5
+ type PressableProps,
6
+ type StyleProp,
7
+ type ViewStyle,
8
+ } from 'react-native'
9
+ import { Cast } from '../index'
10
+ import { useCastState } from '../hooks/useCastState'
11
+ import type { CastState } from '../specs/Cast.nitro'
12
+
13
+ export interface CastButtonProps extends Omit<PressableProps, 'children'> {
14
+ /** Icon size in points. Defaults to `24`. */
15
+ size?: number
16
+ /** Glyph color when not connected. Defaults to `#000000`. */
17
+ color?: string
18
+ /** Glyph color while connecting / connected. Defaults to `#1E88E5`. */
19
+ activeColor?: string
20
+ /** Style applied to the pressable container. */
21
+ style?: StyleProp<ViewStyle>
22
+ /**
23
+ * Hide the button entirely when no Cast devices are available on the network,
24
+ * matching standard platform Cast-button behaviour. Defaults to `true`.
25
+ */
26
+ hideWhenNoDevices?: boolean
27
+ /**
28
+ * Render a custom icon instead of the built-in Cast glyph. Receives the live
29
+ * connection state so you can theme it yourself.
30
+ */
31
+ renderIcon?: (info: {
32
+ state: CastState
33
+ isCasting: boolean
34
+ size: number
35
+ color: string
36
+ }) => React.ReactNode
37
+ }
38
+
39
+ /**
40
+ * A ready-to-use Google Cast button.
41
+ *
42
+ * Reflects the live connection state, hides itself when no devices are available
43
+ * (configurable), and presents the native Cast dialog on press. For full control
44
+ * over the icon, pass `renderIcon`; for custom press handling, pass `onPress`
45
+ * (the default opens the system Cast picker).
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * import { CastButton } from 'react-native-nitro-player'
50
+ *
51
+ * <CastButton size={28} activeColor="#1DB954" />
52
+ * ```
53
+ */
54
+ export function CastButton({
55
+ size = 24,
56
+ color = '#000000',
57
+ activeColor = '#1E88E5',
58
+ hideWhenNoDevices = true,
59
+ renderIcon,
60
+ onPress,
61
+ style,
62
+ ...rest
63
+ }: CastButtonProps): React.ReactElement | null {
64
+ const { state, isCasting } = useCastState()
65
+
66
+ if (hideWhenNoDevices && state === 'no_devices_available') {
67
+ return null
68
+ }
69
+
70
+ const active = state === 'connected' || state === 'connecting'
71
+ const glyphColor = active ? activeColor : color
72
+
73
+ const handlePress: PressableProps['onPress'] = (event) => {
74
+ if (onPress) {
75
+ onPress(event)
76
+ return
77
+ }
78
+ try {
79
+ Cast.showCastPicker()
80
+ } catch (error) {
81
+ console.error('[CastButton] Failed to present cast picker:', error)
82
+ }
83
+ }
84
+
85
+ return (
86
+ <Pressable
87
+ accessibilityRole="button"
88
+ accessibilityLabel={isCasting ? 'Casting' : 'Cast'}
89
+ accessibilityState={{ selected: isCasting }}
90
+ hitSlop={8}
91
+ onPress={handlePress}
92
+ style={style}
93
+ {...rest}
94
+ >
95
+ {renderIcon ? (
96
+ renderIcon({ state, isCasting, size, color: glyphColor })
97
+ ) : (
98
+ <CastGlyph size={size} color={glyphColor} />
99
+ )}
100
+ </Pressable>
101
+ )
102
+ }
103
+
104
+ /**
105
+ * Dependency-free rendition of the standard Cast glyph: a rounded "screen"
106
+ * rectangle (with its bottom-left corner open) and a broadcast signal — a dot
107
+ * and two concentric arcs — radiating from the bottom-left. Drawn purely with
108
+ * `View`s so the library stays dependency-free.
109
+ */
110
+ function CastGlyph({
111
+ size,
112
+ color,
113
+ }: {
114
+ size: number
115
+ color: string
116
+ }): React.ReactElement {
117
+ const stroke = Math.max(1.5, Math.round(size / 13))
118
+ const inset = Math.round(size * 0.14)
119
+ const dotSize = stroke * 1.5
120
+ // Origin of the broadcast signal — the screen's bottom-left corner.
121
+ const cx = inset + stroke / 2
122
+ const cy = size - inset - stroke / 2
123
+ // Leave the screen's bottom-left corner open so the signal reads clearly.
124
+ const gap = size * 0.46
125
+
126
+ return (
127
+ <View style={{ width: size, height: size }}>
128
+ {/* Screen: top + right + most of bottom + most of left, leaving the
129
+ bottom-left corner open (composed from edges for a clean cast look). */}
130
+ {/* Top edge */}
131
+ <View
132
+ style={{
133
+ position: 'absolute',
134
+ left: inset,
135
+ top: inset,
136
+ right: inset,
137
+ height: stroke,
138
+ backgroundColor: color,
139
+ borderRadius: stroke,
140
+ }}
141
+ />
142
+ {/* Right edge */}
143
+ <View
144
+ style={{
145
+ position: 'absolute',
146
+ top: inset,
147
+ bottom: inset,
148
+ right: inset,
149
+ width: stroke,
150
+ backgroundColor: color,
151
+ borderRadius: stroke,
152
+ }}
153
+ />
154
+ {/* Bottom edge (stops short of the bottom-left corner) */}
155
+ <View
156
+ style={{
157
+ position: 'absolute',
158
+ left: inset + gap,
159
+ bottom: inset,
160
+ right: inset,
161
+ height: stroke,
162
+ backgroundColor: color,
163
+ borderRadius: stroke,
164
+ }}
165
+ />
166
+ {/* Left edge (stops short of the bottom-left corner) */}
167
+ <View
168
+ style={{
169
+ position: 'absolute',
170
+ left: inset,
171
+ top: inset,
172
+ height: size - 2 * inset - gap,
173
+ width: stroke,
174
+ backgroundColor: color,
175
+ borderRadius: stroke,
176
+ }}
177
+ />
178
+ {/* Two concentric broadcast arcs radiating from the corner dot */}
179
+ <RippleArc cx={cx} cy={cy} radius={size * 0.2} stroke={stroke} color={color} />
180
+ <RippleArc cx={cx} cy={cy} radius={size * 0.34} stroke={stroke} color={color} />
181
+ {/* Broadcast dot */}
182
+ <View
183
+ style={{
184
+ position: 'absolute',
185
+ left: cx - dotSize / 2,
186
+ top: cy - dotSize / 2,
187
+ width: dotSize,
188
+ height: dotSize,
189
+ borderRadius: dotSize / 2,
190
+ backgroundColor: color,
191
+ }}
192
+ />
193
+ </View>
194
+ )
195
+ }
196
+
197
+ /**
198
+ * A quarter-circle arc (the up-right quadrant) centered at (cx, cy). Rendered as
199
+ * a circle with only its top border colored, rotated 45° so the visible cap
200
+ * faces up-and-to-the-right — i.e. radiating from the bottom-left dot.
201
+ */
202
+ function RippleArc({
203
+ cx,
204
+ cy,
205
+ radius,
206
+ stroke,
207
+ color,
208
+ }: {
209
+ cx: number
210
+ cy: number
211
+ radius: number
212
+ stroke: number
213
+ color: string
214
+ }): React.ReactElement {
215
+ return (
216
+ <View
217
+ style={{
218
+ position: 'absolute',
219
+ left: cx - radius,
220
+ top: cy - radius,
221
+ width: radius * 2,
222
+ height: radius * 2,
223
+ borderRadius: radius,
224
+ borderWidth: stroke,
225
+ borderTopColor: color,
226
+ borderRightColor: 'transparent',
227
+ borderBottomColor: 'transparent',
228
+ borderLeftColor: 'transparent',
229
+ transform: [{ rotate: '45deg' }],
230
+ }}
231
+ />
232
+ )
233
+ }
@@ -0,0 +1,55 @@
1
+ import { Cast } from '../index'
2
+ import type { CastState } from '../specs/Cast.nitro'
3
+
4
+ type CastStateCallback = (state: CastState, deviceName: string | null) => void
5
+
6
+ /**
7
+ * Internal subscription manager that allows multiple hooks/components to
8
+ * subscribe to the single native `Cast.onCastStateChange` callback without
9
+ * overwriting each other. Mirrors {@link callbackManager}.
10
+ */
11
+ class CastCallbackSubscriptionManager {
12
+ private castStateSubscribers = new Set<CastStateCallback>()
13
+ private isCastStateRegistered = false
14
+
15
+ /**
16
+ * Subscribe to Cast connection state changes.
17
+ * @returns Unsubscribe function
18
+ */
19
+ subscribeToCastState(callback: CastStateCallback): () => void {
20
+ this.castStateSubscribers.add(callback)
21
+ this.ensureCastStateRegistered()
22
+
23
+ return () => {
24
+ this.castStateSubscribers.delete(callback)
25
+ }
26
+ }
27
+
28
+ private ensureCastStateRegistered(): void {
29
+ if (this.isCastStateRegistered) return
30
+
31
+ try {
32
+ Cast.onCastStateChange((state, deviceName) => {
33
+ this.castStateSubscribers.forEach((subscriber) => {
34
+ try {
35
+ subscriber(state, deviceName)
36
+ } catch (error) {
37
+ console.error(
38
+ '[CastCallbackManager] Error in cast state subscriber:',
39
+ error
40
+ )
41
+ }
42
+ })
43
+ })
44
+ this.isCastStateRegistered = true
45
+ } catch (error) {
46
+ console.error(
47
+ '[CastCallbackManager] Failed to register cast state callback:',
48
+ error
49
+ )
50
+ }
51
+ }
52
+ }
53
+
54
+ // Export singleton instance
55
+ export const castCallbackManager = new CastCallbackSubscriptionManager()
@@ -12,6 +12,11 @@ export type { UsePlaylistResult } from './usePlaylist'
12
12
  export { useActualQueue } from './useActualQueue'
13
13
  export type { UseActualQueueResult } from './useActualQueue'
14
14
 
15
+ // Cast hooks
16
+ export { useIsCasting } from './useIsCasting'
17
+ export { useCastState } from './useCastState'
18
+ export type { UseCastStateResult } from './useCastState'
19
+
15
20
  // Download hooks
16
21
  export { useDownloadProgress } from './useDownloadProgress'
17
22
  export type {
@@ -0,0 +1,67 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import { Cast } from '../index'
3
+ import { castCallbackManager } from './castCallbackManager'
4
+ import type { CastState } from '../specs/Cast.nitro'
5
+
6
+ export interface UseCastStateResult {
7
+ /** The current Cast connection state. */
8
+ state: CastState
9
+ /** Friendly name of the connected device, or `null` when not connected. */
10
+ deviceName: string | null
11
+ /** Convenience flag, equivalent to `state === 'connected'`. */
12
+ isCasting: boolean
13
+ /** Whether the initial state has been read from native. */
14
+ isReady: boolean
15
+ }
16
+
17
+ /**
18
+ * Hook to subscribe to the full Google Cast connection state.
19
+ *
20
+ * Provides the connection {@link CastState}, the connected device name, and a
21
+ * convenience `isCasting` flag. For a simple boolean prefer {@link useIsCasting}.
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * function CastStatus() {
26
+ * const { state, deviceName, isCasting } = useCastState()
27
+ * if (!isCasting) return null
28
+ * return <Text>Casting to {deviceName} ({state})</Text>
29
+ * }
30
+ * ```
31
+ */
32
+ export function useCastState(): UseCastStateResult {
33
+ const [state, setState] = useState<CastState>('not_connected')
34
+ const [deviceName, setDeviceName] = useState<string | null>(null)
35
+ const [isReady, setIsReady] = useState(false)
36
+ const isMounted = useRef(true)
37
+
38
+ useEffect(() => {
39
+ isMounted.current = true
40
+
41
+ // Seed from current native state.
42
+ try {
43
+ setState(Cast.getCastState())
44
+ setDeviceName(Cast.getCastDeviceName())
45
+ } catch (error) {
46
+ console.error('[useCastState] Failed to read initial cast state:', error)
47
+ } finally {
48
+ setIsReady(true)
49
+ }
50
+
51
+ const unsubscribe = castCallbackManager.subscribeToCastState(
52
+ (newState, newDeviceName) => {
53
+ if (isMounted.current) {
54
+ setState(newState)
55
+ setDeviceName(newDeviceName)
56
+ }
57
+ }
58
+ )
59
+
60
+ return () => {
61
+ isMounted.current = false
62
+ unsubscribe()
63
+ }
64
+ }, [])
65
+
66
+ return { state, deviceName, isCasting: state === 'connected', isReady }
67
+ }
@@ -0,0 +1,47 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import { Cast } from '../index'
3
+ import { castCallbackManager } from './castCallbackManager'
4
+
5
+ /**
6
+ * Hook that returns whether playback is currently routed to a Google Cast device.
7
+ *
8
+ * Updates in real time as Cast sessions start and end. Initializes synchronously
9
+ * from the native player's current state.
10
+ *
11
+ * @returns `true` while connected to a Cast device, otherwise `false`.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * function NowPlaying() {
16
+ * const isCasting = useIsCasting()
17
+ * return <Text>{isCasting ? 'Casting' : 'Playing locally'}</Text>
18
+ * }
19
+ * ```
20
+ */
21
+ export function useIsCasting(): boolean {
22
+ const [isCasting, setIsCasting] = useState<boolean>(() => {
23
+ try {
24
+ return Cast.isCasting()
25
+ } catch {
26
+ return false
27
+ }
28
+ })
29
+ const isMounted = useRef(true)
30
+
31
+ useEffect(() => {
32
+ isMounted.current = true
33
+
34
+ const unsubscribe = castCallbackManager.subscribeToCastState((state) => {
35
+ if (isMounted.current) {
36
+ setIsCasting(state === 'connected')
37
+ }
38
+ })
39
+
40
+ return () => {
41
+ isMounted.current = false
42
+ unsubscribe()
43
+ }
44
+ }, [])
45
+
46
+ return isCasting
47
+ }
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ import type {
9
9
  import type { AndroidAutoMediaLibrary as AndroidAutoMediaLibraryType } from './specs/AndroidAutoMediaLibrary.nitro'
10
10
  import type { AudioDevices as AudioDevicesType } from './specs/AudioDevices.nitro'
11
11
  import type { AudioRoutePicker as AudioRoutePickerType } from './specs/AudioRoutePicker.nitro'
12
+ import type { Cast as CastType } from './specs/Cast.nitro'
12
13
  import type { DownloadManager as DownloadManagerType } from './specs/DownloadManager.nitro'
13
14
  import type { Equalizer as EqualizerType } from './specs/Equalizer.nitro'
14
15
 
@@ -37,6 +38,9 @@ export const AudioRoutePicker =
37
38
  ? NitroModules.createHybridObject<AudioRoutePickerType>('AudioRoutePicker')
38
39
  : null
39
40
 
41
+ // Google Cast (iOS + Android)
42
+ export const Cast = NitroModules.createHybridObject<CastType>('Cast')
43
+
40
44
  // Download Manager
41
45
  export const DownloadManager =
42
46
  NitroModules.createHybridObject<DownloadManagerType>('DownloadManager')
@@ -48,7 +52,12 @@ export const Equalizer =
48
52
  // Export hooks
49
53
  export * from './hooks'
50
54
 
55
+ // Export components
56
+ export { CastButton } from './components/CastButton'
57
+ export type { CastButtonProps } from './components/CastButton'
58
+
51
59
  // Export types
60
+ export type { CastState } from './specs/Cast.nitro'
52
61
  export * from './types/PlayerQueue'
53
62
  export * from './types/AndroidAutoMediaLibrary'
54
63
  export * from './types/DownloadTypes'
@@ -0,0 +1,72 @@
1
+ import type { HybridObject } from 'react-native-nitro-modules'
2
+
3
+ /**
4
+ * The current Google Cast connection state.
5
+ *
6
+ * - `no_devices_available`: No Cast devices are discoverable on the network.
7
+ * The Cast button is typically hidden in this state.
8
+ * - `not_connected`: Cast devices are available but none is connected.
9
+ * - `connecting`: A session is being established with a device.
10
+ * - `connected`: A session is active and playback is routed to the device.
11
+ */
12
+ export type CastState =
13
+ | 'no_devices_available'
14
+ | 'not_connected'
15
+ | 'connecting'
16
+ | 'connected'
17
+
18
+ /**
19
+ * Google Cast control surface.
20
+ *
21
+ * Casting is implemented as an internal playback backend swap inside the native
22
+ * player core: when a session is `connected`, every playback operation (play,
23
+ * pause, seek, skip, queue) is routed to the remote Cast device and local audio
24
+ * is suppressed automatically. The existing {@link TrackPlayer} API keeps
25
+ * working unchanged — this object only exposes the Cast-specific surface.
26
+ */
27
+ export interface Cast extends HybridObject<{
28
+ android: 'kotlin'
29
+ ios: 'swift'
30
+ }> {
31
+ /**
32
+ * Initialize the Cast framework. Must be called once early in app startup
33
+ * (before the first Cast interaction). Safe to call multiple times.
34
+ *
35
+ * @param receiverApplicationId Optional Web Receiver application ID registered
36
+ * in the Google Cast Developer Console. When omitted, the platform Default
37
+ * Media Receiver (`CC1AD845`) is used, which supports audio playback,
38
+ * metadata and queues out of the box.
39
+ */
40
+ configure(receiverApplicationId?: string): Promise<void>
41
+
42
+ /**
43
+ * Whether playback is currently routed to a Cast device.
44
+ * Equivalent to `getCastState() === 'connected'`.
45
+ */
46
+ isCasting(): boolean
47
+
48
+ /** The current Cast connection state. */
49
+ getCastState(): CastState
50
+
51
+ /** The friendly name of the connected device, or `null` when not connected. */
52
+ getCastDeviceName(): string | null
53
+
54
+ /**
55
+ * Present the native Cast dialog. Shows the device chooser when not connected,
56
+ * or the cast controls / disconnect dialog when already connected. Mirrors the
57
+ * behaviour of the standard platform Cast button.
58
+ */
59
+ showCastPicker(): void
60
+
61
+ /** End the current Cast session and resume local playback. No-op when not connected. */
62
+ endCastSession(): Promise<void>
63
+
64
+ /**
65
+ * Register a callback fired whenever the Cast connection state changes.
66
+ * The callback receives the new {@link CastState} and the connected device
67
+ * name (or `null`).
68
+ */
69
+ onCastStateChange(
70
+ callback: (state: CastState, deviceName: string | null) => void
71
+ ): void
72
+ }