react-native-queue-player 1.2.0 → 2.0.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.
- package/QueuePlayer.podspec +14 -9
- package/README.md +3 -3
- package/android/consumer-rules.pro +1 -1
- package/android/src/main/cpp/airplay2_control.c +4 -4
- package/android/src/main/cpp/airplay2_control.h +4 -4
- package/android/src/main/cpp/airplay2_jni.cpp +3 -19
- package/android/src/main/cpp/airplay2_pair.c +7 -7
- package/android/src/main/cpp/airplay2_pair.h +2 -2
- package/android/src/main/cpp/airplay2_rtsp.c +8 -8
- package/android/src/main/cpp/airplay2_session.c +2 -2
- package/android/src/main/cpp/airplay_jni.cpp +8 -69
- package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +7 -16
- package/android/src/main/java/com/margelo/nitro/queueplayer/CacheMimeTypes.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/CastManager.kt +2 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/CellularTransportMonitor.kt +77 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +187 -52
- package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +27 -7
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerEngine.kt +33 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngine.kt +31 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/FFTProcessorTee.kt +45 -30
- package/android/src/main/java/com/margelo/nitro/queueplayer/FifoCacheEvictor.kt +111 -15
- package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +60 -33
- package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/IEqualizerEngine.kt +3 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCache.kt +111 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +78 -30
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaResponseCheck.kt +156 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaValidatingDataSource.kt +165 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MimeCapturingDataSource.kt +18 -9
- package/android/src/main/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractor.kt +48 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/PitchCorrection.kt +3 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +71 -76
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackErrorMapping.kt +13 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachine.kt +2 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +68 -46
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +6 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +5 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +11 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessor.kt +26 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainData.kt +22 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainExtractor.kt +38 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainGain.kt +17 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/SuppliedReplayGain.kt +66 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +985 -276
- package/android/src/main/java/com/margelo/nitro/queueplayer/Visualizer.kt +4 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackend.kt +4 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSession.kt +5 -4
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2JNI.kt +0 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Session.kt +4 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackend.kt +16 -9
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt +6 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI.kt +1 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MulticastLockHolder.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServer.kt +11 -35
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaServerHandle.kt +0 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaTokenRegistry.kt +0 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitor.kt +32 -33
- package/android/src/test/java/androidx/media3/session/{MediaSessionControllerRequestTestSeam.kt → MediaSessionControllerRequestTestSupport.kt} +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +12 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineFocusLossTest.kt +115 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EngineEndSignalOrderTest.kt +70 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +17 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +17 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +15 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FifoCacheEvictorTest.kt +135 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +1 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +36 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheTest.kt +84 -18
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterCancelTest.kt +85 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterTest.kt +112 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaResponseCheckTest.kt +163 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaValidatingDataSourceTest.kt +189 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +47 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +53 -29
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +8 -8
- package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +33 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessorTest.kt +128 -5
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainExtractorTest.kt +209 -14
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainGainTest.kt +150 -10
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainMergeTest.kt +258 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/RobolectricServiceBindHelper.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowDynamicsProcessingRejectingEnable.kt +19 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizer.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizerRejectingBandWrites.kt +27 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/SuppliedReplayGainTest.kt +61 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +4 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastCommandRoutingTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +122 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +291 -27
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +277 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +85 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +11 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +90 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +8 -26
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +3 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +0 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/FakeRemotePlayer.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitorTest.kt +173 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/ShadowConnectivityManagerRecordingRequests.kt +27 -0
- package/app.plugin.js +17 -1
- package/ios/AVPlayerItemQueueItemId.swift +26 -12
- package/ios/AVQueueBuilder.swift +120 -85
- package/ios/ArtworkLoader.swift +0 -7
- package/ios/ArtworkResolver.swift +22 -4
- package/ios/AssetReplayGainReader.swift +118 -0
- package/ios/AudioSession.swift +67 -16
- package/ios/AudioTapProvider.swift +284 -149
- package/ios/CarPlayBridge.swift +0 -12
- package/ios/CarPlayCoordinator.swift +18 -2
- package/ios/CarPlaySceneDelegate.swift +2 -2
- package/ios/Cast/AirPlayRouteState.swift +9 -8
- package/ios/Cast/Core/CastEventBridge.swift +66 -26
- package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
- package/ios/Cast/Core/CastSession.swift +2 -2
- package/ios/Cast/Core/CastTransportRouter.swift +21 -9
- package/ios/Cast/Core/LocalMediaServer.swift +41 -52
- package/ios/Cast/Core/LocalNetworkPermissionProbe.swift +32 -11
- package/ios/Cast/Core/MediaServerHandle.swift +0 -4
- package/ios/Cast/Core/MediaTokenRegistry.swift +0 -6
- package/ios/CastManager.swift +1 -1
- package/ios/CrossfadeEngine.swift +672 -226
- package/ios/EQTap.swift +20 -17
- package/ios/Equalizer.swift +1 -1
- package/ios/FLACStreamInfo.swift +65 -0
- package/ios/GaplessEngine.swift +57 -30
- package/ios/InputGuards.swift +35 -5
- package/ios/LookaheadCache.swift +229 -58
- package/ios/LookaheadCachePrefetcher.swift +48 -36
- package/ios/MediaResponseCheck.swift +122 -0
- package/ios/MediaServer/MediaHTTPConnection.swift +691 -0
- package/ios/NetworkRecoveryPolicy.swift +32 -0
- package/ios/NowPlayingFormatExtractor.swift +25 -18
- package/ios/NowPlayingInfo.swift +84 -48
- package/ios/OutputRouteMonitor.swift +16 -3
- package/ios/PendingBrowseRequests.swift +1 -1
- package/ios/PlaceholderArtwork.swift +9 -5
- package/ios/PlaybackEngine.swift +165 -76
- package/ios/PlaybackErrorMapping.swift +3 -3
- package/ios/PlaybackModeStateMachine.swift +2 -2
- package/ios/PlaybackNetworkMonitor.swift +73 -0
- package/ios/PlayerStateDerivation.swift +6 -6
- package/ios/QueueMutationArithmetic.swift +25 -0
- package/ios/QueueSkipArithmetic.swift +5 -3
- package/ios/QueueWindowArithmetic.swift +152 -0
- package/ios/ReadThroughServer.swift +283 -0
- package/ios/RemoteCommands.swift +44 -4
- package/ios/ReplayGainData.swift +22 -3
- package/ios/ReplayGainExtractor.swift +70 -27
- package/ios/Siri/VoiceDonation.swift +14 -9
- package/ios/StreamingBitrateProbe.swift +22 -68
- package/ios/SuppliedReplayGain.swift +83 -0
- package/ios/Tests/AVQueueBuilderTests.swift +158 -221
- package/ios/Tests/ActiveItemEngineStub.swift +44 -0
- package/ios/Tests/AssetReplayGainReaderTests.swift +228 -0
- package/ios/Tests/AudioTapProviderDispatchTargetTests.swift +158 -0
- package/ios/Tests/AudioTapProviderReplayGainTests.swift +122 -10
- package/ios/Tests/BitrateReresolveBudgetTests.swift +71 -0
- package/ios/Tests/CastMediaItemTranslationTests.swift +72 -0
- package/ios/Tests/CastNowPlayingControllerTests.swift +1 -1
- package/ios/Tests/CrossfadeAdvanceWithoutFadeTests.swift +120 -0
- package/ios/Tests/CrossfadeEngineIncomingMixTests.swift +122 -0
- package/ios/Tests/CrossfadeEngineTests.swift +164 -0
- package/ios/Tests/CrossfadeMixReapplyTests.swift +66 -0
- package/ios/Tests/CrossfadePlaybackIntentTests.swift +81 -0
- package/ios/Tests/EngineSwapTests.swift +91 -0
- package/ios/Tests/EqualizerAudioMixProviderTests.swift +46 -0
- package/ios/Tests/EqualizerHybridTests.swift +5 -2
- package/ios/Tests/FLACStreamInfoTests.swift +88 -0
- package/ios/Tests/GaplessEngineLifecycleTests.swift +4 -4
- package/ios/Tests/InputGuardsTests.swift +41 -0
- package/ios/Tests/InterruptionIntentTests.swift +77 -0
- package/ios/Tests/LookaheadCacheCellularAccessTests.swift +185 -0
- package/ios/Tests/LookaheadCachePrefetcherTests.swift +53 -30
- package/ios/Tests/LookaheadCacheRuntimeConfigTests.swift +160 -1
- package/ios/Tests/LookaheadCacheTests.swift +44 -18
- package/ios/Tests/MediaHTTPConnectionTests.swift +79 -0
- package/ios/Tests/MediaResponseCheckTests.swift +128 -0
- package/ios/Tests/MutationDeferralTests.swift +220 -0
- package/ios/Tests/NowPlayingInfoTests.swift +86 -99
- package/ios/Tests/NowPlayingSnapshotTests.swift +127 -0
- package/ios/Tests/OriginRestartStitcherTests.swift +60 -0
- package/ios/Tests/PlaybackNetworkMonitorTests.swift +95 -0
- package/ios/Tests/PlayerFixtures.swift +48 -0
- package/ios/Tests/PlayerStateDerivationTests.swift +10 -10
- package/ios/Tests/QueueMutationGenerationTests.swift +141 -0
- package/ios/Tests/QueueRebuildPrefixTests.swift +317 -0
- package/ios/Tests/QueueStateTests.swift +3 -1
- package/ios/Tests/QueueWindowArithmeticTests.swift +56 -0
- package/ios/Tests/QueueWindowSliceTests.swift +162 -0
- package/ios/Tests/ReadThroughRoutingLifecycleTests.swift +63 -0
- package/ios/Tests/ReadThroughServerTests.swift +345 -0
- package/ios/Tests/RemoteCommandsTests.swift +28 -0
- package/ios/Tests/ReplayGainExtractorTests.swift +216 -6
- package/ios/Tests/ReplayGainMergeTests.swift +230 -0
- package/ios/Tests/RetryRecoveryTests.swift +418 -0
- package/ios/Tests/SkipCapabilityTests.swift +233 -8
- package/ios/Tests/SkipIndexTests.swift +32 -2
- package/ios/Tests/SleepTimerPauseIntentTests.swift +43 -0
- package/ios/Tests/StallRecoveryTests.swift +97 -0
- package/ios/Tests/StreamingBitrateProbeTests.swift +17 -20
- package/ios/Tests/TopUpWindowGateTests.swift +441 -0
- package/ios/Tests/TrackPlayer+TestHops.swift +14 -0
- package/ios/Tests/TrackPlayerCallOrderTests.swift +93 -0
- package/ios/Tests/TrackPlayerConfigureTeardownTests.swift +86 -0
- package/ios/Tests/TrackPlayerEndVerdictTests.swift +206 -0
- package/ios/Tests/TrackPlayerSeekTests.swift +137 -0
- package/ios/Tests/TrackPlayerThreadingTests.swift +127 -0
- package/ios/Tests/TrackSourceClassifierTests.swift +33 -7
- package/ios/Tests/VoiceVocabularyOptInTests.swift +38 -0
- package/ios/TrackPlayer+Automotive.swift +101 -0
- package/ios/TrackPlayer+Cache.swift +232 -0
- package/ios/TrackPlayer+Config.swift +291 -0
- package/ios/TrackPlayer+EngineDelegate.swift +209 -0
- package/ios/TrackPlayer+EventsAPI.swift +111 -0
- package/ios/TrackPlayer+EventsDispatch.swift +1042 -0
- package/ios/TrackPlayer+EventsWiring.swift +173 -0
- package/ios/TrackPlayer+Lifecycle.swift +982 -0
- package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
- package/ios/TrackPlayer+Queue.swift +951 -0
- package/ios/TrackPlayer+Recovery.swift +213 -0
- package/ios/TrackPlayer+Skip.swift +465 -0
- package/ios/TrackPlayer+SleepTimer.swift +175 -0
- package/ios/TrackPlayer+State.swift +108 -0
- package/ios/TrackPlayer+Threading.swift +135 -0
- package/ios/TrackPlayer+Transport.swift +272 -0
- package/ios/TrackPlayer+Window.swift +227 -0
- package/ios/TrackPlayer.swift +331 -4619
- package/ios/Visualizer.swift +6 -5
- package/ios/tests-harness/Podfile +1 -1
- package/ios/tests-harness/TestHost.xcodeproj/project.pbxproj +19 -11
- package/ios/tests-harness/scripts/seed-xcodeproj.rb +2 -2
- package/lib/module/hooks/useActiveTrack.js +29 -22
- package/lib/module/hooks/useActiveTrack.js.map +1 -1
- package/lib/module/hooks/useCast.js +8 -26
- package/lib/module/hooks/useCast.js.map +1 -1
- package/lib/module/hooks/useEqualizer.js +19 -12
- package/lib/module/hooks/useEqualizer.js.map +1 -1
- package/lib/module/hooks/useLookaheadCache.js +3 -7
- package/lib/module/hooks/useLookaheadCache.js.map +1 -1
- package/lib/module/hooks/useQueue.js +66 -19
- package/lib/module/hooks/useQueue.js.map +1 -1
- package/lib/module/index.js +6 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/queueDelta.js +41 -0
- package/lib/module/queueDelta.js.map +1 -0
- package/lib/module/types.js +28 -5
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/TrackPlayer.nitro.d.ts +53 -28
- package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
- package/lib/typescript/hooks/useActiveTrack.d.ts +5 -7
- package/lib/typescript/hooks/useActiveTrack.d.ts.map +1 -1
- package/lib/typescript/hooks/useCast.d.ts +6 -1
- package/lib/typescript/hooks/useCast.d.ts.map +1 -1
- package/lib/typescript/hooks/useEqualizer.d.ts +2 -1
- package/lib/typescript/hooks/useEqualizer.d.ts.map +1 -1
- package/lib/typescript/hooks/useLookaheadCache.d.ts.map +1 -1
- package/lib/typescript/hooks/useQueue.d.ts +4 -4
- package/lib/typescript/hooks/useQueue.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/queueDelta.d.ts +10 -0
- package/lib/typescript/queueDelta.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +168 -12
- package/lib/typescript/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_QueueChangeDelta_double_QueueChangeReason.hpp +85 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +45 -19
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +3 -3
- package/nitrogen/generated/android/c++/JLookaheadCacheConfig.hpp +8 -4
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
- package/nitrogen/generated/android/c++/JQueueChangeDelta.hpp +128 -0
- package/nitrogen/generated/android/c++/JTrackItem.hpp +19 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_BrowseItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______MediaSearchRequest.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_AudioRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_BufferState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CacheStatus.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastDiscoveryState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastLocalNetworkPermissionEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastSessionDiedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlaybackError.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerProgress.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerState_StateChangeReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/{Func_void_std__vector_TrackItem__double_QueueChangeReason.kt → Func_void_QueueChangeDelta_double_QueueChangeReason.kt} +14 -16
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_ServiceReadyReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SkipCapability.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SleepTimerState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerErrorReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerFrame.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_bool.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_double_double.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_CastReceiver_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_EqualizerBand_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridCastManagerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridEqualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +6 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridVisualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/LookaheadCacheConfig.kt +9 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeDelta.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackItem.kt +24 -4
- package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -2
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +16 -16
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +80 -65
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +6 -3
- package/nitrogen/generated/ios/swift/Func_void_CacheStatus.swift +5 -5
- package/nitrogen/generated/ios/swift/Func_void_QueueChangeDelta_double_QueueChangeReason.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -3
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +32 -24
- package/nitrogen/generated/ios/swift/LookaheadCacheConfig.swift +20 -2
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
- package/nitrogen/generated/ios/swift/QueueChangeDelta.swift +82 -0
- package/nitrogen/generated/ios/swift/TrackItem.swift +73 -1
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +6 -3
- package/nitrogen/generated/shared/c++/LookaheadCacheConfig.hpp +7 -3
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
- package/nitrogen/generated/shared/c++/QueueChangeDelta.hpp +113 -0
- package/nitrogen/generated/shared/c++/TrackItem.hpp +18 -2
- package/package.json +8 -8
- package/src/TrackPlayer.nitro.ts +53 -27
- package/src/hooks/useActiveTrack.ts +29 -22
- package/src/hooks/useCast.ts +14 -15
- package/src/hooks/useEqualizer.ts +19 -12
- package/src/hooks/useLookaheadCache.ts +3 -7
- package/src/hooks/useQueue.ts +66 -17
- package/src/index.ts +13 -3
- package/src/queueDelta.ts +41 -0
- package/src/types.ts +169 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/PendingIntentBuffer.kt +0 -82
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt +0 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/PendingIntentBufferTest.kt +0 -192
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +0 -135
- package/android/src/test/java/com/margelo/nitro/queueplayer/SmokeTest.kt +0 -27
- package/ios/Cast/Core/LocalAddressMonitor.swift +0 -110
- package/ios/Cast/Core/MediaHTTPConnection.swift +0 -349
- package/ios/MetadataReader.swift +0 -555
- package/ios/Tests/CrossfadeEngineStubTests.swift +0 -43
- package/ios/Tests/MetadataReaderTests.swift +0 -603
- package/ios/Tests/PlaybackEngineProtocolTests.swift +0 -92
- package/ios/Tests/VoiceDonationTests.swift +0 -26
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +0 -101
- package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +0 -46
- /package/ios/{Cast/Core → MediaServer}/MimeTypes.swift +0 -0
|
@@ -45,7 +45,9 @@ class TrackPlayerQueueTest {
|
|
|
45
45
|
private fun track(id: String) = TrackItem(
|
|
46
46
|
id = id, url = "https://example.com/$id.m4a",
|
|
47
47
|
title = null, artist = null, album = null,
|
|
48
|
-
duration = null, artworkUrl = null, extras = null
|
|
48
|
+
duration = null, artworkUrl = null, extras = null,
|
|
49
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
50
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null
|
|
49
51
|
)
|
|
50
52
|
|
|
51
53
|
private lateinit var serviceHandle: PlaybackServiceTestHandle
|
|
@@ -55,7 +57,7 @@ class TrackPlayerQueueTest {
|
|
|
55
57
|
player = TrackPlayer()
|
|
56
58
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
57
59
|
player.configureInternal(
|
|
58
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
60
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
59
61
|
)
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -128,13 +130,15 @@ class TrackPlayerQueueTest {
|
|
|
128
130
|
// --- getQueue / getCurrentTrackIndex
|
|
129
131
|
|
|
130
132
|
@Test
|
|
131
|
-
fun `
|
|
133
|
+
fun `setQueueInternal stores the tracks in order`() {
|
|
134
|
+
// `getQueue()` itself returns a Nitro Promise, which cannot be resolved
|
|
135
|
+
// from Robolectric; it reads this list on main, and the demo cycle covers
|
|
136
|
+
// the bridged call.
|
|
132
137
|
val tracks = (1..3).map { track("t$it") }
|
|
133
138
|
player.setQueueInternal(tracks)
|
|
134
139
|
|
|
135
|
-
|
|
136
|
-
assertEquals(
|
|
137
|
-
assertEquals("t1", result[0].id)
|
|
140
|
+
assertEquals(3, player.tracks.size)
|
|
141
|
+
assertEquals("t1", player.tracks[0].id)
|
|
138
142
|
}
|
|
139
143
|
|
|
140
144
|
@Test
|
|
@@ -275,7 +279,7 @@ class TrackPlayerQueueTest {
|
|
|
275
279
|
fun `skipToIndex after setQueue issues the ExoPlayer seek`() {
|
|
276
280
|
// After setQueue the MediaItem timeline has 3 entries, so the
|
|
277
281
|
// `target < mediaItemCount` guard in skipToIndexInternal no
|
|
278
|
-
// longer short-circuits — confirms the skip → seek
|
|
282
|
+
// longer short-circuits — confirms the skip → seek path
|
|
279
283
|
// survives once the timeline is populated.
|
|
280
284
|
player.setQueueInternal((1..3).map { track("t$it") })
|
|
281
285
|
|
|
@@ -41,7 +41,8 @@ class TrackPlayerReadersTest {
|
|
|
41
41
|
PlayerConfig(
|
|
42
42
|
httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null,
|
|
43
43
|
networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null,
|
|
44
|
-
|
|
44
|
+
voiceVocabularyDonationEnabled = null,
|
|
45
|
+
visualizationEnabled = null, clampSeekToBuffered = null,
|
|
45
46
|
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
46
47
|
),
|
|
47
48
|
context()
|
package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt
CHANGED
|
@@ -40,7 +40,9 @@ class TrackPlayerSkipCapabilityTest {
|
|
|
40
40
|
private fun track(id: String) = TrackItem(
|
|
41
41
|
id = id, url = "https://example.com/$id.m4a",
|
|
42
42
|
title = null, artist = null, album = null,
|
|
43
|
-
duration = null, artworkUrl = null, extras = null
|
|
43
|
+
duration = null, artworkUrl = null, extras = null,
|
|
44
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
45
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null
|
|
44
46
|
)
|
|
45
47
|
|
|
46
48
|
/** Drain main-looper posts so the auto-fire-on-subscribe lands. */
|
|
@@ -53,7 +55,7 @@ class TrackPlayerSkipCapabilityTest {
|
|
|
53
55
|
player = TrackPlayer()
|
|
54
56
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
55
57
|
player.configureInternal(
|
|
56
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
58
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
57
59
|
)
|
|
58
60
|
}
|
|
59
61
|
|
|
@@ -318,7 +320,7 @@ class TrackPlayerSkipCapabilityTest {
|
|
|
318
320
|
player = TrackPlayer()
|
|
319
321
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
320
322
|
player.configureInternal(
|
|
321
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
323
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
322
324
|
)
|
|
323
325
|
}
|
|
324
326
|
|
|
@@ -326,10 +328,25 @@ class TrackPlayerSkipCapabilityTest {
|
|
|
326
328
|
|
|
327
329
|
private fun configureRestart() {
|
|
328
330
|
player.configureInternal(
|
|
329
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = SkipToPreviousBehavior.RESTART_OR_PREVIOUS), context()
|
|
331
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = SkipToPreviousBehavior.RESTART_OR_PREVIOUS), context()
|
|
330
332
|
)
|
|
331
333
|
}
|
|
332
334
|
|
|
335
|
+
@Test
|
|
336
|
+
fun `restart mode — addToQueueInternal into empty queue keeps Previous false (cur stays -1)`() {
|
|
337
|
+
configureRestart()
|
|
338
|
+
val emissions = mutableListOf<SkipCapability>()
|
|
339
|
+
player.onSkipCapabilityChange { cap -> emissions.add(cap) }
|
|
340
|
+
flushMain()
|
|
341
|
+
emissions.clear()
|
|
342
|
+
|
|
343
|
+
player.addToQueueInternal((1..3).map { track("t$it") }, insertBefore = null)
|
|
344
|
+
// No loaded track: nothing to restart and nothing before it, so the
|
|
345
|
+
// lock-screen Previous control stays disabled until skipToIndex.
|
|
346
|
+
assertEquals(false, player.getCanSkipPrevious())
|
|
347
|
+
assertEquals(emptyList<SkipCapability>(), emissions)
|
|
348
|
+
}
|
|
349
|
+
|
|
333
350
|
@Test
|
|
334
351
|
fun `restart mode forces canSkipPrevious true at index 0`() {
|
|
335
352
|
configureRestart()
|
|
@@ -360,4 +377,73 @@ class TrackPlayerSkipCapabilityTest {
|
|
|
360
377
|
emissions.last().canSkipPrevious
|
|
361
378
|
)
|
|
362
379
|
}
|
|
380
|
+
|
|
381
|
+
// --- cast index writes keep the capability current
|
|
382
|
+
|
|
383
|
+
@Test
|
|
384
|
+
fun `emitRemoteTrackChange to the last index disables Next`() {
|
|
385
|
+
player.setQueueInternal((1..3).map { track("t$it") })
|
|
386
|
+
assertEquals(true, player.getCanSkipNext())
|
|
387
|
+
|
|
388
|
+
// The receiver advanced to the last track; the lock-screen Next
|
|
389
|
+
// control must grey out exactly as it does for a local advance.
|
|
390
|
+
player.emitRemoteTrackChange(2)
|
|
391
|
+
assertEquals(2, player.currentTrackIndex)
|
|
392
|
+
assertEquals(false, player.getCanSkipNext())
|
|
393
|
+
assertEquals(true, player.getCanSkipPrevious())
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
@Test
|
|
397
|
+
fun `emitRemoteTrackChange emits the capability change`() {
|
|
398
|
+
player.setQueueInternal((1..3).map { track("t$it") })
|
|
399
|
+
val emissions = mutableListOf<SkipCapability>()
|
|
400
|
+
player.onSkipCapabilityChange { cap -> emissions.add(cap) }
|
|
401
|
+
flushMain()
|
|
402
|
+
emissions.clear()
|
|
403
|
+
|
|
404
|
+
player.emitRemoteTrackChange(2)
|
|
405
|
+
flushMain()
|
|
406
|
+
assertEquals(
|
|
407
|
+
listOf(SkipCapability(canSkipPrevious = true, canSkipNext = false)),
|
|
408
|
+
emissions
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
@Test
|
|
413
|
+
fun `emitRemoteTrackChange classifies the new track's source`() {
|
|
414
|
+
// A local file at index 2, streaming tracks elsewhere: the source the
|
|
415
|
+
// JS side reads must follow the index the receiver moved to.
|
|
416
|
+
val local = TrackItem(
|
|
417
|
+
id = "local", url = "file:///tmp/local.m4a",
|
|
418
|
+
title = null, artist = null, album = null,
|
|
419
|
+
duration = null, artworkUrl = null, extras = null,
|
|
420
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
421
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null
|
|
422
|
+
)
|
|
423
|
+
player.setQueueInternal(listOf(track("t1"), track("t2"), local))
|
|
424
|
+
assertEquals(TrackSource.STREAMING, player.getCurrentTrackSource())
|
|
425
|
+
|
|
426
|
+
player.emitRemoteTrackChange(2)
|
|
427
|
+
assertEquals(TrackSource.LOCAL, player.getCurrentTrackSource())
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
@Test
|
|
431
|
+
fun `resumeLocalAfterCast lands on the handback index with its capability and source`() {
|
|
432
|
+
val local = TrackItem(
|
|
433
|
+
id = "local", url = "file:///tmp/local.m4a",
|
|
434
|
+
title = null, artist = null, album = null,
|
|
435
|
+
duration = null, artworkUrl = null, extras = null,
|
|
436
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
437
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null
|
|
438
|
+
)
|
|
439
|
+
player.setQueueInternal(listOf(track("t1"), track("t2"), local))
|
|
440
|
+
assertEquals(true, player.getCanSkipNext())
|
|
441
|
+
assertEquals(TrackSource.STREAMING, player.getCurrentTrackSource())
|
|
442
|
+
|
|
443
|
+
player.resumeLocalAfterCast(trackIndex = 2, positionMs = 0L)
|
|
444
|
+
flushMain()
|
|
445
|
+
assertEquals(2, player.currentTrackIndex)
|
|
446
|
+
assertEquals(false, player.getCanSkipNext())
|
|
447
|
+
assertEquals(TrackSource.LOCAL, player.getCurrentTrackSource())
|
|
448
|
+
}
|
|
363
449
|
}
|
|
@@ -15,9 +15,8 @@ import org.robolectric.annotation.Config
|
|
|
15
15
|
* [TrackPlayer]. `QueueSkipArithmeticTest` covers the raw
|
|
16
16
|
* arithmetic; these tests cover the adapter layer:
|
|
17
17
|
*
|
|
18
|
-
* - `computeNextIndex`
|
|
19
|
-
*
|
|
20
|
-
* delegate to [QueueSkipArithmetic].
|
|
18
|
+
* - `computeNextIndex` — reads `repeatModeState` + `currentTrackIndex`
|
|
19
|
+
* + `tracks.size` and delegates to [QueueSkipArithmetic].
|
|
21
20
|
* - `skipToNextInternal` / `skipToPreviousInternal` /
|
|
22
21
|
* `skipToIndexInternal` — advance `currentTrackIndex` when the
|
|
23
22
|
* arithmetic returns a target, no-op when it returns null. Under
|
|
@@ -41,7 +40,9 @@ class TrackPlayerSkipTest {
|
|
|
41
40
|
private fun track(id: String) = TrackItem(
|
|
42
41
|
id = id, url = "https://example.com/$id.m4a",
|
|
43
42
|
title = null, artist = null, album = null,
|
|
44
|
-
duration = null, artworkUrl = null, extras = null
|
|
43
|
+
duration = null, artworkUrl = null, extras = null,
|
|
44
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
45
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null
|
|
45
46
|
)
|
|
46
47
|
|
|
47
48
|
private lateinit var serviceHandle: PlaybackServiceTestHandle
|
|
@@ -51,7 +52,7 @@ class TrackPlayerSkipTest {
|
|
|
51
52
|
player = TrackPlayer()
|
|
52
53
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
53
54
|
player.configureInternal(
|
|
54
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
55
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
55
56
|
)
|
|
56
57
|
player.tracks = (1..5).map { track("t$it") }
|
|
57
58
|
player.currentTrackIndex = 2 // mid-queue default for most cases
|
|
@@ -63,7 +64,7 @@ class TrackPlayerSkipTest {
|
|
|
63
64
|
serviceHandle.destroy()
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
// --- computeNextIndex
|
|
67
|
+
// --- computeNextIndex (adapter)
|
|
67
68
|
|
|
68
69
|
@Test
|
|
69
70
|
fun `computeNextIndex reads repeatModeState`() {
|
|
@@ -77,18 +78,6 @@ class TrackPlayerSkipTest {
|
|
|
77
78
|
assertEquals(3, player.computeNextIndex())
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
@Test
|
|
81
|
-
fun `computePreviousIndex reads repeatModeState`() {
|
|
82
|
-
player.repeatModeState = RepeatMode.OFF
|
|
83
|
-
assertEquals(1, player.computePreviousIndex())
|
|
84
|
-
|
|
85
|
-
player.repeatModeState = RepeatMode.TRACK
|
|
86
|
-
assertEquals(1, player.computePreviousIndex())
|
|
87
|
-
|
|
88
|
-
player.repeatModeState = RepeatMode.QUEUE
|
|
89
|
-
assertEquals(1, player.computePreviousIndex())
|
|
90
|
-
}
|
|
91
|
-
|
|
92
81
|
@Test
|
|
93
82
|
fun `computeNextIndex returns null at end under REPEAT OFF`() {
|
|
94
83
|
player.repeatModeState = RepeatMode.OFF
|
|
@@ -103,13 +92,6 @@ class TrackPlayerSkipTest {
|
|
|
103
92
|
assertEquals(0, player.computeNextIndex())
|
|
104
93
|
}
|
|
105
94
|
|
|
106
|
-
@Test
|
|
107
|
-
fun `computePreviousIndex wraps to last under REPEAT QUEUE at index 0`() {
|
|
108
|
-
player.repeatModeState = RepeatMode.QUEUE
|
|
109
|
-
player.currentTrackIndex = 0
|
|
110
|
-
assertEquals(4, player.computePreviousIndex())
|
|
111
|
-
}
|
|
112
|
-
|
|
113
95
|
// --- skipToNextInternal / skipToPreviousInternal
|
|
114
96
|
|
|
115
97
|
@Test
|
|
@@ -293,7 +275,7 @@ class TrackPlayerSkipTest {
|
|
|
293
275
|
|
|
294
276
|
private fun configureSkipBehavior(behavior: SkipToPreviousBehavior?) {
|
|
295
277
|
player.configureInternal(
|
|
296
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = behavior), context()
|
|
278
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = behavior), context()
|
|
297
279
|
)
|
|
298
280
|
player.tracks = (1..5).map { track("t$it") }
|
|
299
281
|
}
|
|
@@ -50,7 +50,7 @@ class TrackPlayerTransportTest {
|
|
|
50
50
|
player = TrackPlayer()
|
|
51
51
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
52
52
|
player.configureInternal(
|
|
53
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
53
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
54
54
|
)
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -80,7 +80,7 @@ class TrackPlayerTransportTest {
|
|
|
80
80
|
// currentTrackIndex while the ExoPlayer is still IDLE.
|
|
81
81
|
// iOS parity: paused-with-queue returns PAUSED, not NONE.
|
|
82
82
|
player.tracks = listOf(
|
|
83
|
-
TrackItem("t1", "https://example.com/1.m4a", null, null, null, null, null, null)
|
|
83
|
+
TrackItem("t1", "https://example.com/1.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
84
84
|
)
|
|
85
85
|
player.currentTrackIndex = 0
|
|
86
86
|
|
|
@@ -104,7 +104,7 @@ class TrackPlayerTransportTest {
|
|
|
104
104
|
// on eventually-seekable content still lands. The reset-to-start protection
|
|
105
105
|
// applies once the item is loaded (STATE_READY) and confirmed unseekable.
|
|
106
106
|
player.setQueueInternal(
|
|
107
|
-
(1..3).map { TrackItem("t$it", "https://example.com/$it.m4a", null, null, null, null, null, null) }
|
|
107
|
+
(1..3).map { TrackItem("t$it", "https://example.com/$it.m4a", null, null, null, null, null, null, null, null, null, null) }
|
|
108
108
|
)
|
|
109
109
|
player.currentTrackIndex = 0
|
|
110
110
|
|
|
@@ -162,9 +162,6 @@ private class FakePlaybackEngineWithSinks : PlaybackEngine {
|
|
|
162
162
|
|
|
163
163
|
override val mediaSessionPlayer: androidx.media3.exoplayer.ExoPlayer
|
|
164
164
|
get() = error("not used in test")
|
|
165
|
-
override suspend fun prepare(
|
|
166
|
-
mediaItems: List<androidx.media3.common.MediaItem>, startIndex: Int, startPositionMs: Long,
|
|
167
|
-
) = error("not used in test")
|
|
168
165
|
override fun release() = error("not used in test")
|
|
169
166
|
override fun setMediaItems(items: List<androidx.media3.common.MediaItem>, startIndex: Int, startPositionMs: Long) = error("not used in test")
|
|
170
167
|
override fun addMediaItems(items: List<androidx.media3.common.MediaItem>, insertBefore: Int) = error("not used in test")
|
|
@@ -197,6 +194,5 @@ private class FakePlaybackEngineWithSinks : PlaybackEngine {
|
|
|
197
194
|
override fun detachAudioBufferSink(sink: TeeAudioProcessor.AudioBufferSink) {
|
|
198
195
|
if (attachedSinks.remove(sink)) detachCount++
|
|
199
196
|
}
|
|
200
|
-
override suspend fun handoff(to: PlaybackEngine, atTrackBoundary: Boolean) = error("not used in test")
|
|
201
197
|
override var delegate: PlaybackEngineDelegate? = null
|
|
202
198
|
}
|
|
@@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
|
|
12
12
|
* Test double for [CastSession.RemotePlayer]. Transport calls record their
|
|
13
13
|
* name in [transportCalls]; the StateFlows are driveable via the public
|
|
14
14
|
* backing fields, or via [reportState] to mirror the real session's
|
|
15
|
-
* [hasReportedState]
|
|
15
|
+
* [hasReportedState] flag; [mediaSessionPlayer] is injectable so bind tests
|
|
16
16
|
* can assert the MediaSession swaps to it.
|
|
17
17
|
*/
|
|
18
18
|
internal class FakeRemotePlayer(
|
|
@@ -44,7 +44,7 @@ internal class FakeRemotePlayer(
|
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* Drive the receiver state the way a live session does: a non-idle report
|
|
47
|
-
*
|
|
47
|
+
* sets [hasReportedState]. Set [backingState] directly to stage a state
|
|
48
48
|
* the receiver has not reported yet.
|
|
49
49
|
*/
|
|
50
50
|
fun reportState(state: RemotePlaybackState) {
|
package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitorTest.kt
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
package com.margelo.nitro.queueplayer.cast.net
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.net.ConnectivityManager
|
|
5
|
+
import android.net.LinkAddress
|
|
6
|
+
import android.net.LinkProperties
|
|
7
|
+
import android.net.Network
|
|
8
|
+
import android.net.NetworkCapabilities
|
|
9
|
+
import androidx.test.core.app.ApplicationProvider
|
|
10
|
+
import org.junit.Assert.assertEquals
|
|
11
|
+
import org.junit.Assert.assertFalse
|
|
12
|
+
import org.junit.Assert.assertNull
|
|
13
|
+
import org.junit.Assert.assertTrue
|
|
14
|
+
import org.junit.Test
|
|
15
|
+
import org.junit.runner.RunWith
|
|
16
|
+
import org.robolectric.RobolectricTestRunner
|
|
17
|
+
import org.robolectric.Shadows.shadowOf
|
|
18
|
+
import org.robolectric.annotation.Config
|
|
19
|
+
import org.robolectric.shadow.api.Shadow
|
|
20
|
+
import org.robolectric.shadows.ShadowNetwork
|
|
21
|
+
import org.robolectric.shadows.ShadowNetworkCapabilities
|
|
22
|
+
import org.robolectric.util.ReflectionHelpers
|
|
23
|
+
import org.robolectric.util.ReflectionHelpers.ClassParameter
|
|
24
|
+
import java.net.InetAddress
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `currentAddress` is the Wi-Fi network's IPv4 address: the callback is
|
|
28
|
+
* registered for Wi-Fi networks only, the `start()` seed uses the active
|
|
29
|
+
* network only when it is Wi-Fi and not a VPN, and the address follows
|
|
30
|
+
* that network's link properties until it is lost.
|
|
31
|
+
*/
|
|
32
|
+
@RunWith(RobolectricTestRunner::class)
|
|
33
|
+
@Config(sdk = [34], shadows = [ShadowConnectivityManagerRecordingRequests::class])
|
|
34
|
+
class LocalAddressMonitorTest {
|
|
35
|
+
|
|
36
|
+
private val context: Context = ApplicationProvider.getApplicationContext()
|
|
37
|
+
private val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
|
38
|
+
private val shadowCm: ShadowConnectivityManagerRecordingRequests = Shadow.extract(cm)
|
|
39
|
+
|
|
40
|
+
// The shadow's default active network has netId 0; its transport is
|
|
41
|
+
// whatever capabilities the test puts on it. Any other netId is a
|
|
42
|
+
// network that exists but is not the active one.
|
|
43
|
+
private val activeNetwork: Network = ShadowNetwork.newInstance(0)
|
|
44
|
+
private val otherNetwork: Network = ShadowNetwork.newInstance(1)
|
|
45
|
+
|
|
46
|
+
private fun describe(network: Network, transports: List<Int>, ipv4: String, vpn: Boolean = false) {
|
|
47
|
+
val capabilities = ShadowNetworkCapabilities.newInstance()
|
|
48
|
+
transports.forEach { shadowOf(capabilities).addTransportType(it) }
|
|
49
|
+
if (vpn) shadowOf(capabilities).removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
|
50
|
+
shadowCm.setNetworkCapabilities(network, capabilities)
|
|
51
|
+
shadowCm.setLinkProperties(network, linkProperties(ipv4))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private fun linkProperties(vararg addresses: String): LinkProperties {
|
|
55
|
+
val linkAddresses = addresses.map { literal ->
|
|
56
|
+
val inet = InetAddress.getByName(literal)
|
|
57
|
+
ReflectionHelpers.callConstructor(
|
|
58
|
+
LinkAddress::class.java,
|
|
59
|
+
ClassParameter.from(InetAddress::class.java, inet),
|
|
60
|
+
ClassParameter.from(Int::class.javaPrimitiveType, if (inet.address.size == 4) 24 else 64),
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
return LinkProperties().apply { setLinkAddresses(linkAddresses) }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@Test
|
|
67
|
+
fun `the callback is registered for Wi-Fi networks only`() {
|
|
68
|
+
LocalAddressMonitor(context).start()
|
|
69
|
+
|
|
70
|
+
val request = shadowCm.requests.single()
|
|
71
|
+
assertTrue(request.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
|
|
72
|
+
assertFalse(request.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR))
|
|
73
|
+
assertTrue(request.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Test
|
|
77
|
+
fun `start seeds the address from a Wi-Fi active network`() {
|
|
78
|
+
describe(activeNetwork, listOf(NetworkCapabilities.TRANSPORT_WIFI), "192.168.1.10")
|
|
79
|
+
|
|
80
|
+
val monitor = LocalAddressMonitor(context).also { it.start() }
|
|
81
|
+
|
|
82
|
+
assertEquals("192.168.1.10", monitor.currentAddress)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@Test
|
|
86
|
+
fun `start does not seed the address from a cellular active network`() {
|
|
87
|
+
describe(otherNetwork, listOf(NetworkCapabilities.TRANSPORT_WIFI), "192.168.1.10")
|
|
88
|
+
describe(activeNetwork, listOf(NetworkCapabilities.TRANSPORT_CELLULAR), "10.0.0.5")
|
|
89
|
+
|
|
90
|
+
val monitor = LocalAddressMonitor(context).also { it.start() }
|
|
91
|
+
|
|
92
|
+
assertNull("a cellular address is unreachable from a LAN receiver", monitor.currentAddress)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@Test
|
|
96
|
+
fun `start does not seed the address from a VPN over Wi-Fi`() {
|
|
97
|
+
describe(
|
|
98
|
+
activeNetwork,
|
|
99
|
+
listOf(NetworkCapabilities.TRANSPORT_VPN, NetworkCapabilities.TRANSPORT_WIFI),
|
|
100
|
+
"10.8.0.2",
|
|
101
|
+
vpn = true,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
val monitor = LocalAddressMonitor(context).also { it.start() }
|
|
105
|
+
|
|
106
|
+
assertNull("a tunnel address is unreachable from a LAN receiver", monitor.currentAddress)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@Test
|
|
110
|
+
fun `start registers the callback and seeds nothing when there is no active network`() {
|
|
111
|
+
shadowCm.setDefaultNetworkActive(false)
|
|
112
|
+
|
|
113
|
+
val monitor = LocalAddressMonitor(context).also { it.start() }
|
|
114
|
+
|
|
115
|
+
assertEquals(1, shadowCm.networkCallbacks.size)
|
|
116
|
+
assertNull(monitor.currentAddress)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@Test
|
|
120
|
+
fun `a matched network's link properties set the address and its loss clears it`() {
|
|
121
|
+
shadowCm.setDefaultNetworkActive(false)
|
|
122
|
+
val monitor = LocalAddressMonitor(context).also { it.start() }
|
|
123
|
+
val callback = shadowCm.networkCallbacks.single()
|
|
124
|
+
|
|
125
|
+
callback.onLinkPropertiesChanged(otherNetwork, linkProperties("192.168.1.20"))
|
|
126
|
+
assertEquals("192.168.1.20", monitor.currentAddress)
|
|
127
|
+
|
|
128
|
+
callback.onLost(otherNetwork)
|
|
129
|
+
assertNull(monitor.currentAddress)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** A Wi-Fi handover reports the new network's link before the old network
|
|
133
|
+
* is lost; the loss of the old one must not clear the new address. */
|
|
134
|
+
@Test
|
|
135
|
+
fun `losing a network other than the one that supplied the address keeps it`() {
|
|
136
|
+
shadowCm.setDefaultNetworkActive(false)
|
|
137
|
+
val monitor = LocalAddressMonitor(context).also { it.start() }
|
|
138
|
+
val callback = shadowCm.networkCallbacks.single()
|
|
139
|
+
val oldWifi = otherNetwork
|
|
140
|
+
val newWifi = ShadowNetwork.newInstance(2)
|
|
141
|
+
|
|
142
|
+
callback.onLinkPropertiesChanged(oldWifi, linkProperties("192.168.1.20"))
|
|
143
|
+
callback.onLinkPropertiesChanged(newWifi, linkProperties("192.168.2.30"))
|
|
144
|
+
callback.onLost(oldWifi)
|
|
145
|
+
assertEquals("192.168.2.30", monitor.currentAddress)
|
|
146
|
+
|
|
147
|
+
callback.onLost(newWifi)
|
|
148
|
+
assertNull(monitor.currentAddress)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@Test
|
|
152
|
+
fun `the address is the first non-loopback IPv4 among the link addresses`() {
|
|
153
|
+
shadowCm.setDefaultNetworkActive(false)
|
|
154
|
+
val monitor = LocalAddressMonitor(context).also { it.start() }
|
|
155
|
+
val callback = shadowCm.networkCallbacks.single()
|
|
156
|
+
|
|
157
|
+
callback.onLinkPropertiesChanged(otherNetwork, linkProperties("fe80::1", "127.0.0.1", "192.168.1.20", "192.168.1.21"))
|
|
158
|
+
assertEquals("192.168.1.20", monitor.currentAddress)
|
|
159
|
+
|
|
160
|
+
callback.onLinkPropertiesChanged(otherNetwork, linkProperties("fe80::1"))
|
|
161
|
+
assertNull(monitor.currentAddress)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@Test
|
|
165
|
+
fun `stop unregisters the callback`() {
|
|
166
|
+
val monitor = LocalAddressMonitor(context).also { it.start() }
|
|
167
|
+
assertEquals(1, shadowCm.networkCallbacks.size)
|
|
168
|
+
|
|
169
|
+
monitor.stop()
|
|
170
|
+
|
|
171
|
+
assertTrue(shadowCm.networkCallbacks.isEmpty())
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.margelo.nitro.queueplayer.cast.net
|
|
2
|
+
|
|
3
|
+
import android.net.ConnectivityManager
|
|
4
|
+
import android.net.NetworkRequest
|
|
5
|
+
import android.os.Handler
|
|
6
|
+
import org.robolectric.annotation.Implements
|
|
7
|
+
import org.robolectric.shadows.ShadowConnectivityManager
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A [ShadowConnectivityManager] that keeps the [NetworkRequest] passed to
|
|
11
|
+
* `registerNetworkCallback`; the stock shadow discards it and stores only
|
|
12
|
+
* the callback.
|
|
13
|
+
*/
|
|
14
|
+
@Implements(ConnectivityManager::class)
|
|
15
|
+
class ShadowConnectivityManagerRecordingRequests : ShadowConnectivityManager() {
|
|
16
|
+
|
|
17
|
+
val requests = mutableListOf<NetworkRequest>()
|
|
18
|
+
|
|
19
|
+
override fun registerNetworkCallback(
|
|
20
|
+
request: NetworkRequest,
|
|
21
|
+
networkCallback: ConnectivityManager.NetworkCallback,
|
|
22
|
+
handler: Handler?,
|
|
23
|
+
) {
|
|
24
|
+
requests.add(request)
|
|
25
|
+
super.registerNetworkCallback(request, networkCallback, handler)
|
|
26
|
+
}
|
|
27
|
+
}
|
package/app.plugin.js
CHANGED
|
@@ -331,6 +331,13 @@ const withChromecastSubspecPodfile = (config) =>
|
|
|
331
331
|
},
|
|
332
332
|
]);
|
|
333
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Bonjour type the local-network permission probe advertises and browses.
|
|
336
|
+
* Mirrors `LocalNetworkPermissionProbe.serviceType`; the two must agree or the
|
|
337
|
+
* probe is blocked with nothing to indicate why.
|
|
338
|
+
*/
|
|
339
|
+
const LOCAL_NETWORK_PROBE_SERVICE_TYPE = '_rnqp-lnp._tcp';
|
|
340
|
+
|
|
334
341
|
const withChromecastInfoPlist = (config, receiverAppId) =>
|
|
335
342
|
withInfoPlist(config, (cfg) => {
|
|
336
343
|
const appId = (receiverAppId || 'CC1AD845').toUpperCase();
|
|
@@ -344,7 +351,16 @@ const withChromecastInfoPlist = (config, receiverAppId) =>
|
|
|
344
351
|
const existingBonjour = Array.isArray(cfg.modResults.NSBonjourServices)
|
|
345
352
|
? cfg.modResults.NSBonjourServices
|
|
346
353
|
: [];
|
|
347
|
-
|
|
354
|
+
// The permission probe browses its own service type. iOS refuses to browse
|
|
355
|
+
// a type that is not declared here, so without this entry the probe can
|
|
356
|
+
// never succeed and every consumer reads `undetermined` forever.
|
|
357
|
+
// Must match `LocalNetworkPermissionProbe.serviceType` in
|
|
358
|
+
// ios/Cast/Core/LocalNetworkPermissionProbe.swift.
|
|
359
|
+
const additions = [
|
|
360
|
+
'_googlecast._tcp',
|
|
361
|
+
`_${appId}._googlecast._tcp`,
|
|
362
|
+
LOCAL_NETWORK_PROBE_SERVICE_TYPE,
|
|
363
|
+
];
|
|
348
364
|
for (const s of additions) {
|
|
349
365
|
if (!existingBonjour.includes(s)) existingBonjour.push(s);
|
|
350
366
|
}
|
|
@@ -26,14 +26,18 @@ import AVFoundation
|
|
|
26
26
|
/// standard idiom — no `.m` / `.h` source files introduced.
|
|
27
27
|
private var queueItemIdKey: UInt8 = 0
|
|
28
28
|
private var sourceURLKey: UInt8 = 0
|
|
29
|
+
private var routedThroughReadThroughServerKey: UInt8 = 0
|
|
29
30
|
|
|
31
|
+
/// `nonisolated` throughout: the SDK marks `AVPlayerItem` main-actor isolated
|
|
32
|
+
/// because `asset` is not Sendable, but these stamps are written once at
|
|
33
|
+
/// construction and read on the player queue.
|
|
30
34
|
extension AVPlayerItem {
|
|
31
35
|
/// Lib-internal identity for this AVPlayerItem. Set once at
|
|
32
36
|
/// construction by `AVQueueBuilder.makeItem`; never mutated
|
|
33
37
|
/// after. nil only for AVPlayerItems that did not flow through
|
|
34
38
|
/// the lib's queue construction path (e.g. built directly via
|
|
35
39
|
/// `makeItemRaw`).
|
|
36
|
-
var queueItemId: String? {
|
|
40
|
+
nonisolated var queueItemId: String? {
|
|
37
41
|
get { objc_getAssociatedObject(self, &queueItemIdKey) as? String }
|
|
38
42
|
set {
|
|
39
43
|
objc_setAssociatedObject(
|
|
@@ -41,21 +45,31 @@ extension AVPlayerItem {
|
|
|
41
45
|
}
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
/// The exact `track.url` string this item was built from
|
|
45
|
-
/// verbatim
|
|
46
|
-
///
|
|
47
|
-
///
|
|
48
|
-
///
|
|
49
|
-
///
|
|
50
|
-
///
|
|
51
|
-
|
|
52
|
-
/// remote-built item for its local cache file once it finishes
|
|
53
|
-
/// downloading.
|
|
54
|
-
var sourceURL: String? {
|
|
48
|
+
/// The exact `track.url` string this item was built from, stored
|
|
49
|
+
/// verbatim. A routed item's `AVURLAsset.url` is the read-through
|
|
50
|
+
/// server's `127.0.0.1` address and an unrouted one is a
|
|
51
|
+
/// parse/re-serialize round-trip of the original, so anything that
|
|
52
|
+
/// reads a container, codec or track identity out of a URL reads this:
|
|
53
|
+
/// `NowPlayingFormatExtractor`, `AssetReplayGainReader`, the error
|
|
54
|
+
/// `url` field, and the source diagnostic log.
|
|
55
|
+
nonisolated var sourceURL: String? {
|
|
55
56
|
get { objc_getAssociatedObject(self, &sourceURLKey) as? String }
|
|
56
57
|
set {
|
|
57
58
|
objc_setAssociatedObject(
|
|
58
59
|
self, &sourceURLKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
59
60
|
}
|
|
60
61
|
}
|
|
62
|
+
|
|
63
|
+
/// Whether this item's asset URL is the read-through server's loopback
|
|
64
|
+
/// address rather than `sourceURL` itself. Set once by
|
|
65
|
+
/// `AVQueueBuilder.makeItem`; the routing decision is made at build time
|
|
66
|
+
/// and an item is never re-pointed, so the stamp is what source
|
|
67
|
+
/// classification reads instead of guessing from the server's presence.
|
|
68
|
+
nonisolated var isRoutedThroughReadThroughServer: Bool {
|
|
69
|
+
get { (objc_getAssociatedObject(self, &routedThroughReadThroughServerKey) as? Bool) ?? false }
|
|
70
|
+
set {
|
|
71
|
+
objc_setAssociatedObject(
|
|
72
|
+
self, &routedThroughReadThroughServerKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
61
75
|
}
|