react-native-mp3-player 1.0.0
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/LICENSE +205 -0
- package/README.md +88 -0
- package/android/build.gradle +114 -0
- package/android/proguard-rules.txt +0 -0
- package/android/src/main/AndroidManifest.xml +27 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/HeadlessJsMediaService.kt +199 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/TrackPlayer.kt +30 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/extensions/AudioPlayerStateExt.kt +19 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/extensions/EnumExtensions.kt +5 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/extensions/NumberExt.kt +13 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/model/MetadataAdapter.kt +227 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/model/NowPlayingMetadata.kt +16 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/model/PlaybackMetadata.kt +203 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/model/State.kt +13 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/model/Track.kt +67 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/model/TrackAudioItem.kt +18 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/model/TrackMetadata.kt +38 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicEvents.kt +65 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt +775 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt +1251 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/utils/AppForegroundTracker.kt +35 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/utils/BundleUtils.kt +147 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/utils/CoilBitmapLoader.kt +64 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/utils/MediaItemBuilder.kt +41 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/utils/RejectionException.kt +11 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/event/EventHolder.kt +30 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/event/PlayerEventHolder.kt +124 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/AudioContentType.kt +10 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/AudioItem.kt +133 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/AudioItemTransitionReason.kt +33 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/AudioPlayerState.kt +30 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/BufferConfig.kt +8 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/CacheConfig.kt +17 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/Capability.kt +19 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/CustomButton.kt +19 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/FocusChangeData.kt +3 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/MediaSessionCallback.kt +17 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/PlayWhenReadyChangeData.kt +3 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/PlaybackEndedReason.kt +5 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/PlaybackError.kt +6 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/PlayerConfig.kt +36 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/PlayerOptions.kt +39 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/PositionChangedReason.kt +39 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/RepeatMode.kt +16 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/models/WakeMode.kt +7 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/AudioPlayer.kt +689 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/ForwardingPlayer.java +1124 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/QueuedAudioPlayer.kt +295 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/components/Buffer.kt +34 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/components/Cache.kt +47 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/components/FocusManager.kt +59 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/components/MediaFactory.kt +165 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/service/MusicService.kt +127 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/utils/Utils.kt +113 -0
- package/android/src/main/res/drawable/baseline_repeat_24.xml +5 -0
- package/android/src/main/res/drawable/baseline_repeat_one_24.xml +5 -0
- package/android/src/main/res/drawable/forward.xml +5 -0
- package/android/src/main/res/drawable/heart_24px.xml +5 -0
- package/android/src/main/res/drawable/hearte_24px.xml +5 -0
- package/android/src/main/res/drawable/ifl_24px.xml +5 -0
- package/android/src/main/res/drawable/rewind.xml +5 -0
- package/android/src/main/res/drawable/shuffle_24px.xml +5 -0
- package/android/src/main/res/values/strings.xml +5 -0
- package/android/src/main/res/xml/automotive_app_desc.xml +3 -0
- package/ios/Example/SwiftAudio/Assets.xcassets/22AMI.imageset/22AMillion.jpg +0 -0
- package/ios/Example/SwiftAudio/Assets.xcassets/22AMI.imageset/Contents.json +21 -0
- package/ios/Example/SwiftAudio/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/ios/Example/SwiftAudio/Assets.xcassets/AppIcon.appiconset/Contents.json +58 -0
- package/ios/Example/SwiftAudio/Assets.xcassets/Contents.json +6 -0
- package/ios/Example/SwiftAudio/Assets.xcassets/cover.imageset/Contents.json +21 -0
- package/ios/Example/SwiftAudio/Assets.xcassets/cover.imageset/cover.jpg +0 -0
- package/ios/Example/SwiftAudio/AudioController.swift +46 -0
- package/ios/Example/SwiftAudio/Extensions.swift +22 -0
- package/ios/Example/SwiftAudio/PlayerView.swift +172 -0
- package/ios/Example/SwiftAudio/PlayerViewModel.swift +120 -0
- package/ios/Example/SwiftAudio/Preview Content/Preview Assets.xcassets/Contents.json +6 -0
- package/ios/Example/SwiftAudio/QueueView.swift +65 -0
- package/ios/Example/SwiftAudio/SwiftAudio.entitlements +12 -0
- package/ios/Example/SwiftAudio/SwiftAudioApp.swift +17 -0
- package/ios/Example/SwiftAudio.xcodeproj/project.pbxproj +412 -0
- package/ios/Example/SwiftAudio.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/Example/SwiftAudio.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/ios/RNTrackPlayer/Models/Capabilities.swift +52 -0
- package/ios/RNTrackPlayer/Models/MediaURL.swift +38 -0
- package/ios/RNTrackPlayer/Models/MetadataAdapter.swift +147 -0
- package/ios/RNTrackPlayer/Models/PitchAlgorithms.swift +13 -0
- package/ios/RNTrackPlayer/Models/SessionCategories.swift +106 -0
- package/ios/RNTrackPlayer/Models/State.swift +26 -0
- package/ios/RNTrackPlayer/Models/Track.swift +140 -0
- package/ios/RNTrackPlayer/RNTrackPlayer.swift +941 -0
- package/ios/RNTrackPlayer/Support/RNTrackPlayer-Bridging-Header.h +7 -0
- package/ios/RNTrackPlayer/TrackPlayer.h +14 -0
- package/ios/RNTrackPlayer/TrackPlayer.mm +246 -0
- package/ios/RNTrackPlayer/Utils/EventType.swift +44 -0
- package/ios/RNTrackPlayer/Utils/Metadata.swift +60 -0
- package/ios/SwiftAudioEx/Package.swift +20 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AVPlayerWrapper/AVPlayerWrapper.swift +521 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AVPlayerWrapper/AVPlayerWrapperDelegate.swift +27 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AVPlayerWrapper/AVPlayerWrapperProtocol.swift +69 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AVPlayerWrapper/AVPlayerWrapperState.swift +43 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AudioItem.swift +158 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AudioPlayer.swift +459 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AudioPlayerError.swift +26 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AudioSessionController/AudioSession.swift +33 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AudioSessionController/AudioSessionController.swift +135 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/AudioTap.swift +99 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/Event.swift +155 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/NowPlayingInfoController/MediaItemProperty.swift +95 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/NowPlayingInfoController/NowPlayingInfoCenter.swift +17 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/NowPlayingInfoController/NowPlayingInfoController.swift +73 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/NowPlayingInfoController/NowPlayingInfoControllerProtocol.swift +26 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/NowPlayingInfoController/NowPlayingInfoKeyValue.swift +14 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/NowPlayingInfoController/NowPlayingInfoProperty.swift +234 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/Observer/AVPlayerItemNotificationObserver.swift +102 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/Observer/AVPlayerItemObserver.swift +136 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/Observer/AVPlayerObserver.swift +120 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/Observer/AVPlayerTimeObserver.swift +112 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/QueueManager.swift +356 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/QueuedAudioPlayer.swift +236 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/RemoteCommandController/RemoteCommand.swift +170 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/RemoteCommandController/RemoteCommandController.swift +206 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/RepeatMode.swift +15 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/TimeEventFrequency.swift +26 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/Utils/DispatchQueueType.swift +18 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/WaveformAudioTap.swift +159 -0
- package/lib/specs/NativeTrackPlayer.d.ts +129 -0
- package/lib/specs/NativeTrackPlayer.js +4 -0
- package/lib/src/constants/AndroidAudioContentType.d.ts +35 -0
- package/lib/src/constants/AndroidAudioContentType.js +36 -0
- package/lib/src/constants/AndroidAutoContentStyle.d.ts +10 -0
- package/lib/src/constants/AndroidAutoContentStyle.js +11 -0
- package/lib/src/constants/AppKilledPlaybackBehavior.d.ts +17 -0
- package/lib/src/constants/AppKilledPlaybackBehavior.js +18 -0
- package/lib/src/constants/Capability.d.ts +17 -0
- package/lib/src/constants/Capability.js +19 -0
- package/lib/src/constants/Event.d.ts +163 -0
- package/lib/src/constants/Event.js +164 -0
- package/lib/src/constants/IOSCategory.d.ts +36 -0
- package/lib/src/constants/IOSCategory.js +37 -0
- package/lib/src/constants/IOSCategoryMode.d.ts +47 -0
- package/lib/src/constants/IOSCategoryMode.js +48 -0
- package/lib/src/constants/IOSCategoryOptions.d.ts +44 -0
- package/lib/src/constants/IOSCategoryOptions.js +45 -0
- package/lib/src/constants/MediaItemPlayable.d.ts +4 -0
- package/lib/src/constants/MediaItemPlayable.js +5 -0
- package/lib/src/constants/PitchAlgorithm.d.ts +14 -0
- package/lib/src/constants/PitchAlgorithm.js +16 -0
- package/lib/src/constants/RatingType.d.ts +8 -0
- package/lib/src/constants/RatingType.js +10 -0
- package/lib/src/constants/RepeatMode.d.ts +8 -0
- package/lib/src/constants/RepeatMode.js +10 -0
- package/lib/src/constants/State.d.ts +34 -0
- package/lib/src/constants/State.js +35 -0
- package/lib/src/constants/TrackType.d.ts +6 -0
- package/lib/src/constants/TrackType.js +7 -0
- package/lib/src/constants/index.d.ts +14 -0
- package/lib/src/constants/index.js +14 -0
- package/lib/src/hooks/index.d.ts +6 -0
- package/lib/src/hooks/index.js +6 -0
- package/lib/src/hooks/useActiveTrack.d.ts +2 -0
- package/lib/src/hooks/useActiveTrack.js +28 -0
- package/lib/src/hooks/useAppIsInBackground.d.ts +1 -0
- package/lib/src/hooks/useAppIsInBackground.js +16 -0
- package/lib/src/hooks/useIsPlaying.d.ts +35 -0
- package/lib/src/hooks/useIsPlaying.js +50 -0
- package/lib/src/hooks/usePlayWhenReady.d.ts +1 -0
- package/lib/src/hooks/usePlayWhenReady.js +27 -0
- package/lib/src/hooks/usePlaybackState.d.ts +10 -0
- package/lib/src/hooks/usePlaybackState.js +35 -0
- package/lib/src/hooks/useProgress.d.ts +7 -0
- package/lib/src/hooks/useProgress.js +55 -0
- package/lib/src/hooks/useTrackPlayerEvents.d.ts +8 -0
- package/lib/src/hooks/useTrackPlayerEvents.js +30 -0
- package/lib/src/index.d.ts +5 -0
- package/lib/src/index.js +5 -0
- package/lib/src/interfaces/AndroidAutoBrowseTree.d.ts +5 -0
- package/lib/src/interfaces/AndroidAutoBrowseTree.js +1 -0
- package/lib/src/interfaces/AndroidOptions.d.ts +41 -0
- package/lib/src/interfaces/AndroidOptions.js +1 -0
- package/lib/src/interfaces/CustomButtons.d.ts +5 -0
- package/lib/src/interfaces/CustomButtons.js +1 -0
- package/lib/src/interfaces/FeedbackOptions.d.ts +6 -0
- package/lib/src/interfaces/FeedbackOptions.js +1 -0
- package/lib/src/interfaces/MediaItem.d.ts +18 -0
- package/lib/src/interfaces/MediaItem.js +1 -0
- package/lib/src/interfaces/MetadataOptions.d.ts +3 -0
- package/lib/src/interfaces/MetadataOptions.js +1 -0
- package/lib/src/interfaces/NowPlayingMetadata.d.ts +4 -0
- package/lib/src/interfaces/NowPlayingMetadata.js +1 -0
- package/lib/src/interfaces/PlaybackState.d.ts +8 -0
- package/lib/src/interfaces/PlaybackState.js +1 -0
- package/lib/src/interfaces/PlayerOptions.d.ts +127 -0
- package/lib/src/interfaces/PlayerOptions.js +1 -0
- package/lib/src/interfaces/Progress.d.ts +15 -0
- package/lib/src/interfaces/Progress.js +1 -0
- package/lib/src/interfaces/ResourceObject.d.ts +1 -0
- package/lib/src/interfaces/ResourceObject.js +1 -0
- package/lib/src/interfaces/ServiceHandler.d.ts +1 -0
- package/lib/src/interfaces/ServiceHandler.js +1 -0
- package/lib/src/interfaces/Track.d.ts +21 -0
- package/lib/src/interfaces/Track.js +1 -0
- package/lib/src/interfaces/TrackMetadataBase.d.ts +28 -0
- package/lib/src/interfaces/TrackMetadataBase.js +1 -0
- package/lib/src/interfaces/UpdateOptions.d.ts +52 -0
- package/lib/src/interfaces/UpdateOptions.js +1 -0
- package/lib/src/interfaces/events/AudioMetadataReceivedEvent.d.ts +33 -0
- package/lib/src/interfaces/events/AudioMetadataReceivedEvent.js +1 -0
- package/lib/src/interfaces/events/ControllerConnectedEvent.d.ts +8 -0
- package/lib/src/interfaces/events/ControllerConnectedEvent.js +1 -0
- package/lib/src/interfaces/events/EventPayloadByEvent.d.ts +73 -0
- package/lib/src/interfaces/events/EventPayloadByEvent.js +1 -0
- package/lib/src/interfaces/events/PlaybackActiveTrackChangedEvent.d.ts +24 -0
- package/lib/src/interfaces/events/PlaybackActiveTrackChangedEvent.js +1 -0
- package/lib/src/interfaces/events/PlaybackAnimatedVolumeChangedEvent.d.ts +4 -0
- package/lib/src/interfaces/events/PlaybackAnimatedVolumeChangedEvent.js +1 -0
- package/lib/src/interfaces/events/PlaybackErrorEvent.d.ts +6 -0
- package/lib/src/interfaces/events/PlaybackErrorEvent.js +1 -0
- package/lib/src/interfaces/events/PlaybackMetadataReceivedEvent.d.ts +16 -0
- package/lib/src/interfaces/events/PlaybackMetadataReceivedEvent.js +1 -0
- package/lib/src/interfaces/events/PlaybackPlayWhenReadyChangedEvent.d.ts +4 -0
- package/lib/src/interfaces/events/PlaybackPlayWhenReadyChangedEvent.js +1 -0
- package/lib/src/interfaces/events/PlaybackProgressUpdatedEvent.d.ts +4 -0
- package/lib/src/interfaces/events/PlaybackProgressUpdatedEvent.js +1 -0
- package/lib/src/interfaces/events/PlaybackQueueEndedEvent.d.ts +9 -0
- package/lib/src/interfaces/events/PlaybackQueueEndedEvent.js +1 -0
- package/lib/src/interfaces/events/PlaybackResumeEvent.d.ts +3 -0
- package/lib/src/interfaces/events/PlaybackResumeEvent.js +1 -0
- package/lib/src/interfaces/events/PlaybackTrackChangedEvent.d.ts +11 -0
- package/lib/src/interfaces/events/PlaybackTrackChangedEvent.js +1 -0
- package/lib/src/interfaces/events/PlayerErrorEvent.d.ts +6 -0
- package/lib/src/interfaces/events/PlayerErrorEvent.js +1 -0
- package/lib/src/interfaces/events/RemoteBrowseEvent.d.ts +4 -0
- package/lib/src/interfaces/events/RemoteBrowseEvent.js +1 -0
- package/lib/src/interfaces/events/RemoteCustomActionEvent.d.ts +7 -0
- package/lib/src/interfaces/events/RemoteCustomActionEvent.js +1 -0
- package/lib/src/interfaces/events/RemoteDuckEvent.d.ts +13 -0
- package/lib/src/interfaces/events/RemoteDuckEvent.js +1 -0
- package/lib/src/interfaces/events/RemoteJumpBackwardEvent.d.ts +8 -0
- package/lib/src/interfaces/events/RemoteJumpBackwardEvent.js +1 -0
- package/lib/src/interfaces/events/RemoteJumpForwardEvent.d.ts +8 -0
- package/lib/src/interfaces/events/RemoteJumpForwardEvent.js +1 -0
- package/lib/src/interfaces/events/RemotePlayIdEvent.d.ts +4 -0
- package/lib/src/interfaces/events/RemotePlayIdEvent.js +1 -0
- package/lib/src/interfaces/events/RemotePlaySearchEvent.d.ts +9 -0
- package/lib/src/interfaces/events/RemotePlaySearchEvent.js +1 -0
- package/lib/src/interfaces/events/RemoteSeekEvent.d.ts +4 -0
- package/lib/src/interfaces/events/RemoteSeekEvent.js +1 -0
- package/lib/src/interfaces/events/RemoteSetRatingEvent.d.ts +4 -0
- package/lib/src/interfaces/events/RemoteSetRatingEvent.js +1 -0
- package/lib/src/interfaces/events/RemoteSkipEvent.d.ts +3 -0
- package/lib/src/interfaces/events/RemoteSkipEvent.js +1 -0
- package/lib/src/interfaces/events/index.d.ts +18 -0
- package/lib/src/interfaces/events/index.js +18 -0
- package/lib/src/interfaces/index.d.ts +15 -0
- package/lib/src/interfaces/index.js +15 -0
- package/lib/src/resolveAssetSource.d.ts +2 -0
- package/lib/src/resolveAssetSource.js +3 -0
- package/lib/src/trackPlayer.d.ts +347 -0
- package/lib/src/trackPlayer.js +592 -0
- package/package.json +94 -0
- package/react-native-mp3-player.podspec +22 -0
- package/specs/NativeTrackPlayer.ts +148 -0
- package/src/constants/AndroidAudioContentType.ts +35 -0
- package/src/constants/AndroidAutoContentStyle.ts +10 -0
- package/src/constants/AppKilledPlaybackBehavior.ts +19 -0
- package/src/constants/Capability.ts +19 -0
- package/src/constants/Event.ts +164 -0
- package/src/constants/IOSCategory.ts +36 -0
- package/src/constants/IOSCategoryMode.ts +47 -0
- package/src/constants/IOSCategoryOptions.ts +44 -0
- package/src/constants/MediaItemPlayable.ts +4 -0
- package/src/constants/PitchAlgorithm.ts +16 -0
- package/src/constants/RatingType.ts +10 -0
- package/src/constants/RepeatMode.ts +10 -0
- package/src/constants/State.ts +34 -0
- package/src/constants/TrackType.ts +6 -0
- package/src/constants/index.ts +14 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/useActiveTrack.ts +36 -0
- package/src/hooks/useAppIsInBackground.ts +20 -0
- package/src/hooks/useIsPlaying.ts +56 -0
- package/src/hooks/usePlayWhenReady.ts +37 -0
- package/src/hooks/usePlaybackState.ts +45 -0
- package/src/hooks/useProgress.ts +64 -0
- package/src/hooks/useTrackPlayerEvents.ts +48 -0
- package/src/index.ts +7 -0
- package/src/interfaces/AndroidAutoBrowseTree.ts +6 -0
- package/src/interfaces/AndroidOptions.ts +48 -0
- package/src/interfaces/CustomButtons.ts +6 -0
- package/src/interfaces/FeedbackOptions.ts +7 -0
- package/src/interfaces/MediaItem.ts +19 -0
- package/src/interfaces/MetadataOptions.ts +4 -0
- package/src/interfaces/NowPlayingMetadata.ts +5 -0
- package/src/interfaces/PlaybackState.ts +11 -0
- package/src/interfaces/PlayerOptions.ts +133 -0
- package/src/interfaces/Progress.ts +15 -0
- package/src/interfaces/ResourceObject.ts +1 -0
- package/src/interfaces/ServiceHandler.ts +1 -0
- package/src/interfaces/Track.ts +23 -0
- package/src/interfaces/TrackMetadataBase.ts +29 -0
- package/src/interfaces/UpdateOptions.ts +59 -0
- package/src/interfaces/events/AudioMetadataReceivedEvent.ts +37 -0
- package/src/interfaces/events/ControllerConnectedEvent.ts +9 -0
- package/src/interfaces/events/EventPayloadByEvent.ts +76 -0
- package/src/interfaces/events/PlaybackActiveTrackChangedEvent.ts +29 -0
- package/src/interfaces/events/PlaybackAnimatedVolumeChangedEvent.ts +4 -0
- package/src/interfaces/events/PlaybackErrorEvent.ts +6 -0
- package/src/interfaces/events/PlaybackMetadataReceivedEvent.ts +16 -0
- package/src/interfaces/events/PlaybackPlayWhenReadyChangedEvent.ts +4 -0
- package/src/interfaces/events/PlaybackProgressUpdatedEvent.ts +5 -0
- package/src/interfaces/events/PlaybackQueueEndedEvent.ts +9 -0
- package/src/interfaces/events/PlaybackResumeEvent.ts +5 -0
- package/src/interfaces/events/PlaybackTrackChangedEvent.ts +11 -0
- package/src/interfaces/events/PlayerErrorEvent.ts +6 -0
- package/src/interfaces/events/RemoteBrowseEvent.ts +4 -0
- package/src/interfaces/events/RemoteCustomActionEvent.ts +7 -0
- package/src/interfaces/events/RemoteDuckEvent.ts +13 -0
- package/src/interfaces/events/RemoteJumpBackwardEvent.ts +8 -0
- package/src/interfaces/events/RemoteJumpForwardEvent.ts +8 -0
- package/src/interfaces/events/RemotePlayIdEvent.ts +4 -0
- package/src/interfaces/events/RemotePlaySearchEvent.ts +21 -0
- package/src/interfaces/events/RemoteSeekEvent.ts +4 -0
- package/src/interfaces/events/RemoteSetRatingEvent.ts +5 -0
- package/src/interfaces/events/RemoteSkipEvent.ts +3 -0
- package/src/interfaces/events/index.ts +18 -0
- package/src/interfaces/index.ts +15 -0
- package/src/resolveAssetSource.ts +3 -0
- package/src/trackPlayer.ts +768 -0
|
@@ -0,0 +1,1251 @@
|
|
|
1
|
+
package com.doublesymmetry.trackplayer.service
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.app.*
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.graphics.Bitmap
|
|
7
|
+
import android.os.Binder
|
|
8
|
+
import android.os.Build
|
|
9
|
+
import android.os.Bundle
|
|
10
|
+
import android.os.IBinder
|
|
11
|
+
import android.provider.Settings
|
|
12
|
+
import android.view.KeyEvent
|
|
13
|
+
import androidx.annotation.MainThread
|
|
14
|
+
import androidx.annotation.OptIn
|
|
15
|
+
import androidx.media.utils.MediaConstants
|
|
16
|
+
import androidx.media3.common.C
|
|
17
|
+
import androidx.media3.common.Player
|
|
18
|
+
import androidx.media3.common.util.UnstableApi
|
|
19
|
+
import androidx.media3.session.CacheBitmapLoader
|
|
20
|
+
import androidx.media3.session.LibraryResult
|
|
21
|
+
import androidx.media3.common.MediaItem
|
|
22
|
+
import androidx.media3.common.util.BitmapLoader
|
|
23
|
+
import androidx.media3.exoplayer.ExoPlayer
|
|
24
|
+
import androidx.media3.session.CommandButton
|
|
25
|
+
import androidx.media3.session.MediaSession
|
|
26
|
+
import androidx.media3.session.SessionCommand
|
|
27
|
+
import androidx.media3.session.SessionCommands
|
|
28
|
+
import androidx.media3.session.SessionResult
|
|
29
|
+
import com.lovegaoshi.kotlinaudio.models.*
|
|
30
|
+
import com.lovegaoshi.kotlinaudio.player.QueuedAudioPlayer
|
|
31
|
+
import com.doublesymmetry.trackplayer.HeadlessJsMediaService
|
|
32
|
+
import com.doublesymmetry.trackplayer.extensions.NumberExt.Companion.toMilliseconds
|
|
33
|
+
import com.doublesymmetry.trackplayer.extensions.NumberExt.Companion.toSeconds
|
|
34
|
+
import com.doublesymmetry.trackplayer.extensions.asLibState
|
|
35
|
+
import com.doublesymmetry.trackplayer.extensions.find
|
|
36
|
+
import com.doublesymmetry.trackplayer.model.MetadataAdapter
|
|
37
|
+
import com.doublesymmetry.trackplayer.model.PlaybackMetadata
|
|
38
|
+
import com.doublesymmetry.trackplayer.model.Track
|
|
39
|
+
import com.doublesymmetry.trackplayer.model.TrackAudioItem
|
|
40
|
+
import com.doublesymmetry.trackplayer.module.MusicEvents
|
|
41
|
+
import com.doublesymmetry.trackplayer.module.MusicEvents.Companion.METADATA_PAYLOAD_KEY
|
|
42
|
+
import com.doublesymmetry.trackplayer.R as TrackPlayerR
|
|
43
|
+
import com.doublesymmetry.trackplayer.utils.BundleUtils
|
|
44
|
+
import com.doublesymmetry.trackplayer.utils.BundleUtils.setRating
|
|
45
|
+
import com.doublesymmetry.trackplayer.utils.CoilBitmapLoader
|
|
46
|
+
import com.doublesymmetry.trackplayer.utils.buildMediaItem
|
|
47
|
+
import com.facebook.react.bridge.Arguments
|
|
48
|
+
import com.facebook.react.jstasks.HeadlessJsTaskConfig
|
|
49
|
+
import com.google.common.collect.ImmutableList
|
|
50
|
+
import com.google.common.util.concurrent.Futures
|
|
51
|
+
import com.google.common.util.concurrent.ListenableFuture
|
|
52
|
+
import kotlinx.coroutines.*
|
|
53
|
+
import kotlinx.coroutines.flow.flow
|
|
54
|
+
import timber.log.Timber
|
|
55
|
+
import java.util.concurrent.TimeUnit
|
|
56
|
+
import kotlin.system.exitProcess
|
|
57
|
+
import androidx.core.net.toUri
|
|
58
|
+
|
|
59
|
+
@OptIn(UnstableApi::class)
|
|
60
|
+
@MainThread
|
|
61
|
+
class MusicService : HeadlessJsMediaService() {
|
|
62
|
+
private lateinit var player: QueuedAudioPlayer
|
|
63
|
+
private val binder = MusicBinder()
|
|
64
|
+
private val scope = MainScope()
|
|
65
|
+
private lateinit var fakePlayer: ExoPlayer
|
|
66
|
+
private lateinit var mediaSession: MediaLibrarySession
|
|
67
|
+
private var progressUpdateJob: Job? = null
|
|
68
|
+
var mediaTree: Map<String, List<MediaItem>> = HashMap()
|
|
69
|
+
var mediaTreeStyle: List<Int> = listOf(
|
|
70
|
+
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM,
|
|
71
|
+
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
|
|
72
|
+
private var sessionCommands: SessionCommands? = null
|
|
73
|
+
private var playerCommands: Player.Commands? = null
|
|
74
|
+
private var customLayout: List<CommandButton> = listOf()
|
|
75
|
+
private var lastWake: Long = 0
|
|
76
|
+
var lastConnectedPackage: String = ""
|
|
77
|
+
|
|
78
|
+
fun crossFadePrepare(previous: Boolean = false, seekTo: Double = 0.0) {
|
|
79
|
+
player.crossFadePrepare(previous, seekTo)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fun switchExoPlayer(
|
|
83
|
+
fadeDuration: Long = 2500,
|
|
84
|
+
fadeInterval: Long = 20,
|
|
85
|
+
fadeToVolume: Float = 1f,
|
|
86
|
+
waitUntil: Long = 0
|
|
87
|
+
) {
|
|
88
|
+
player.switchExoPlayer(
|
|
89
|
+
fadeDuration = fadeDuration,
|
|
90
|
+
fadeInterval = fadeInterval,
|
|
91
|
+
fadeToVolume = fadeToVolume,
|
|
92
|
+
waitUntil = waitUntil,
|
|
93
|
+
playerOperation = {
|
|
94
|
+
player.play()
|
|
95
|
+
emitPlaybackTrackChangedEvents(null, null, 0.0)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fun acquireWakeLock() { acquireWakeLockNow(this) }
|
|
101
|
+
|
|
102
|
+
fun abandonWakeLock() { wakeLock?.release() }
|
|
103
|
+
|
|
104
|
+
fun getBitmapLoader(): BitmapLoader {
|
|
105
|
+
return mediaSession.bitmapLoader
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fun getCurrentBitmap(): ListenableFuture<Bitmap>? {
|
|
109
|
+
return player.exoPlayer.currentMediaItem?.mediaMetadata?.let {
|
|
110
|
+
mediaSession.bitmapLoader.loadBitmapFromMetadata(
|
|
111
|
+
it
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@ExperimentalCoroutinesApi
|
|
117
|
+
override fun onCreate() {
|
|
118
|
+
Timber.plant(Timber.DebugTree())
|
|
119
|
+
Timber.tag("APM").d("RNTP musicservice created.")
|
|
120
|
+
fakePlayer = ExoPlayer.Builder(this).build()
|
|
121
|
+
val openAppIntent = packageManager.getLaunchIntentForPackage(packageName)?.apply {
|
|
122
|
+
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
|
123
|
+
// Add the Uri data so apps can identify that it was a notification click
|
|
124
|
+
data = "trackplayer://notification.click".toUri()
|
|
125
|
+
action = Intent.ACTION_VIEW
|
|
126
|
+
}
|
|
127
|
+
// Unique session id per process to avoid "Session ID must be unique" crash after aggressive OS kill (e.g. Oppo/Xiaomi)
|
|
128
|
+
val sessionId = "rntp_media_session_${android.os.Process.myPid()}"
|
|
129
|
+
mediaSession = MediaLibrarySession.Builder(this, fakePlayer, APMMediaSessionCallback() )
|
|
130
|
+
.setId(sessionId)
|
|
131
|
+
.setBitmapLoader(CacheBitmapLoader(CoilBitmapLoader(this)))
|
|
132
|
+
// https://github.com/androidx/media/issues/1218
|
|
133
|
+
.setSessionActivity(PendingIntent.getActivity(this, 0, openAppIntent, getPendingIntentFlags()))
|
|
134
|
+
.build()
|
|
135
|
+
super.onCreate()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Use [appKilledPlaybackBehavior] instead.
|
|
140
|
+
*/
|
|
141
|
+
@Deprecated("This will be removed soon")
|
|
142
|
+
var stoppingAppPausesPlayback = true
|
|
143
|
+
private set
|
|
144
|
+
|
|
145
|
+
enum class AppKilledPlaybackBehavior(val string: String) {
|
|
146
|
+
CONTINUE_PLAYBACK("continue-playback"),
|
|
147
|
+
PAUSE_PLAYBACK("pause-playback"),
|
|
148
|
+
STOP_PLAYBACK_AND_REMOVE_NOTIFICATION("stop-playback-and-remove-notification")
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private var appKilledPlaybackBehavior = AppKilledPlaybackBehavior.STOP_PLAYBACK_AND_REMOVE_NOTIFICATION
|
|
152
|
+
private var stopForegroundGracePeriod: Int = DEFAULT_STOP_FOREGROUND_GRACE_PERIOD
|
|
153
|
+
|
|
154
|
+
val tracks: List<Track>
|
|
155
|
+
get() = player.items.map { (it as TrackAudioItem).track }
|
|
156
|
+
|
|
157
|
+
val currentTrack: Track
|
|
158
|
+
get() {
|
|
159
|
+
return try {
|
|
160
|
+
(player.currentItem as TrackAudioItem).track
|
|
161
|
+
} catch (e: Exception) {
|
|
162
|
+
Track(this, Bundle(), 0)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
val state
|
|
167
|
+
get() = player.playerState
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
val playbackError
|
|
171
|
+
get() = player.playbackError
|
|
172
|
+
|
|
173
|
+
val event
|
|
174
|
+
get() = player.playerEventHolder
|
|
175
|
+
|
|
176
|
+
var playWhenReady: Boolean
|
|
177
|
+
get() = player.playWhenReady
|
|
178
|
+
set(value) {
|
|
179
|
+
player.playWhenReady = value
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private var latestOptions: Bundle? = null
|
|
183
|
+
private var compactCapabilities: List<Capability> = emptyList()
|
|
184
|
+
private var commandStarted = false
|
|
185
|
+
|
|
186
|
+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
187
|
+
Timber.tag("APM").d("onStartCommand: ${intent?.action}, ${intent?.`package`}")
|
|
188
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
|
189
|
+
// HACK: this is not supposed to be here. I definitely screwed up. but Why?
|
|
190
|
+
onMediaKeyEvent(intent)
|
|
191
|
+
}
|
|
192
|
+
// HACK: Why is onPlay triggering onStartCommand??
|
|
193
|
+
if (!commandStarted) {
|
|
194
|
+
commandStarted = true
|
|
195
|
+
super.onStartCommand(intent, flags, startId)
|
|
196
|
+
}
|
|
197
|
+
return START_STICKY
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@MainThread
|
|
201
|
+
fun setupPlayer(playerOptions: Bundle?) {
|
|
202
|
+
if (this::player.isInitialized) {
|
|
203
|
+
print("Player was initialized. Prevent re-initializing again")
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
Timber.tag("APM").d("RNTP musicservice set up")
|
|
207
|
+
val mPlayerOptions = PlayerOptions(
|
|
208
|
+
crossfade = playerOptions?.getBoolean(CROSSFADE, false) ?: false,
|
|
209
|
+
cacheSize = playerOptions?.getDouble(MAX_CACHE_SIZE_KEY)?.toLong() ?: 0,
|
|
210
|
+
audioContentType = when(playerOptions?.getString(ANDROID_AUDIO_CONTENT_TYPE)) {
|
|
211
|
+
"music" -> C.AUDIO_CONTENT_TYPE_MUSIC
|
|
212
|
+
"speech" -> C.AUDIO_CONTENT_TYPE_SPEECH
|
|
213
|
+
"sonification" -> C.AUDIO_CONTENT_TYPE_SONIFICATION
|
|
214
|
+
"movie" -> C.AUDIO_CONTENT_TYPE_MOVIE
|
|
215
|
+
"unknown" -> C.AUDIO_CONTENT_TYPE_UNKNOWN
|
|
216
|
+
else -> C.AUDIO_CONTENT_TYPE_MUSIC
|
|
217
|
+
},
|
|
218
|
+
wakeMode = playerOptions?.getInt(WAKE_MODE, 0) ?: 0,
|
|
219
|
+
handleAudioBecomingNoisy = playerOptions?.getBoolean(HANDLE_NOISY, true) ?: true,
|
|
220
|
+
alwaysShowNext = playerOptions?.getBoolean(ALWAYS_SHOW_NEXT, true) ?: true,
|
|
221
|
+
handleAudioFocus = playerOptions?.getBoolean(AUTO_HANDLE_INTERRUPTIONS) ?: true,
|
|
222
|
+
bufferOptions = BufferOptions(
|
|
223
|
+
playerOptions?.getDouble(MIN_BUFFER_KEY)?.toMilliseconds()?.toInt(),
|
|
224
|
+
playerOptions?.getDouble(MAX_BUFFER_KEY)?.toMilliseconds()?.toInt(),
|
|
225
|
+
playerOptions?.getDouble(PLAY_BUFFER_KEY)?.toMilliseconds()?.toInt(),
|
|
226
|
+
playerOptions?.getDouble(BACK_BUFFER_KEY)?.toMilliseconds()?.toInt(),
|
|
227
|
+
),
|
|
228
|
+
|
|
229
|
+
skipSilence = playerOptions?.getBoolean(SKIP_SILENCE) ?: false
|
|
230
|
+
)
|
|
231
|
+
player = QueuedAudioPlayer(this@MusicService, mPlayerOptions)
|
|
232
|
+
fakePlayer.release()
|
|
233
|
+
mediaSession.player = player.player
|
|
234
|
+
observeEvents()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@MainThread
|
|
238
|
+
fun updateOptions(options: Bundle) {
|
|
239
|
+
latestOptions = options
|
|
240
|
+
val androidOptions = options.getBundle(ANDROID_OPTIONS_KEY)
|
|
241
|
+
|
|
242
|
+
if (androidOptions?.containsKey(PARSE_EMBEDDED_ARTWORK) == true) {
|
|
243
|
+
player.parseEmbeddedArtwork = androidOptions.getBoolean(PARSE_EMBEDDED_ARTWORK)
|
|
244
|
+
}
|
|
245
|
+
if (androidOptions?.containsKey(AUDIO_OFFLOAD_KEY) == true) {
|
|
246
|
+
player.setAudioOffload(androidOptions.getBoolean(AUDIO_OFFLOAD_KEY))
|
|
247
|
+
}
|
|
248
|
+
if (androidOptions?.containsKey(SKIP_SILENCE) == true) {
|
|
249
|
+
player.skipSilence = androidOptions.getBoolean(SKIP_SILENCE)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
appKilledPlaybackBehavior =
|
|
253
|
+
AppKilledPlaybackBehavior::string.find(androidOptions?.getString(APP_KILLED_PLAYBACK_BEHAVIOR_KEY)) ?:
|
|
254
|
+
AppKilledPlaybackBehavior.CONTINUE_PLAYBACK
|
|
255
|
+
|
|
256
|
+
BundleUtils.getIntOrNull(androidOptions, STOP_FOREGROUND_GRACE_PERIOD_KEY)?.let { stopForegroundGracePeriod = it }
|
|
257
|
+
|
|
258
|
+
// TODO: This handles a deprecated flag. Should be removed soon.
|
|
259
|
+
options.getBoolean(STOPPING_APP_PAUSES_PLAYBACK_KEY).let {
|
|
260
|
+
stoppingAppPausesPlayback = options.getBoolean(STOPPING_APP_PAUSES_PLAYBACK_KEY)
|
|
261
|
+
if (stoppingAppPausesPlayback) {
|
|
262
|
+
appKilledPlaybackBehavior = AppKilledPlaybackBehavior.PAUSE_PLAYBACK
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
player.alwaysPauseOnInterruption = androidOptions?.getBoolean(PAUSE_ON_INTERRUPTION_KEY) ?: false
|
|
267
|
+
player.shuffleMode = androidOptions?.getBoolean(SHUFFLE_KEY) ?: false
|
|
268
|
+
|
|
269
|
+
// setup progress update events if configured
|
|
270
|
+
progressUpdateJob?.cancel()
|
|
271
|
+
val updateInterval = BundleUtils.getDoubleOrNull(options, PROGRESS_UPDATE_EVENT_INTERVAL_KEY)
|
|
272
|
+
if (updateInterval != null && updateInterval > 0) {
|
|
273
|
+
progressUpdateJob = scope.launch {
|
|
274
|
+
progressUpdateEventFlow(updateInterval).collect { emit(MusicEvents.PLAYBACK_PROGRESS_UPDATED, it) }
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
val capabilities = options.getIntegerArrayList("capabilities")?.map { Capability.entries[it] } ?: emptyList()
|
|
279
|
+
var notificationCapabilities = options.getIntegerArrayList("notificationCapabilities")?.map { Capability.entries[it] } ?: emptyList()
|
|
280
|
+
compactCapabilities = options.getIntegerArrayList("compactCapabilities")?.map { Capability.entries[it] } ?: emptyList()
|
|
281
|
+
val customActions = options.getBundle(CUSTOM_ACTIONS_KEY)
|
|
282
|
+
val customActionsList = customActions?.getStringArrayList(CUSTOM_ACTIONS_LIST_KEY)
|
|
283
|
+
if (notificationCapabilities.isEmpty()) notificationCapabilities = capabilities
|
|
284
|
+
|
|
285
|
+
val playerCommandsBuilder = Player.Commands.Builder().addAll(
|
|
286
|
+
// HACK: without COMMAND_GET_CURRENT_MEDIA_ITEM, notification cannot be created
|
|
287
|
+
Player.COMMAND_GET_CURRENT_MEDIA_ITEM,
|
|
288
|
+
Player.COMMAND_GET_TRACKS,
|
|
289
|
+
Player.COMMAND_GET_TIMELINE,
|
|
290
|
+
Player.COMMAND_GET_METADATA,
|
|
291
|
+
Player.COMMAND_GET_AUDIO_ATTRIBUTES,
|
|
292
|
+
Player.COMMAND_GET_VOLUME,
|
|
293
|
+
Player.COMMAND_GET_DEVICE_VOLUME,
|
|
294
|
+
Player.COMMAND_GET_TEXT,
|
|
295
|
+
Player.COMMAND_SEEK_TO_MEDIA_ITEM,
|
|
296
|
+
Player.COMMAND_SET_MEDIA_ITEM,
|
|
297
|
+
Player.COMMAND_PREPARE,
|
|
298
|
+
Player.COMMAND_RELEASE,
|
|
299
|
+
Player.COMMAND_CHANGE_MEDIA_ITEMS,
|
|
300
|
+
)
|
|
301
|
+
notificationCapabilities.forEach {
|
|
302
|
+
when (it) {
|
|
303
|
+
Capability.PLAY, Capability.PAUSE -> {
|
|
304
|
+
playerCommandsBuilder.add(Player.COMMAND_PLAY_PAUSE)
|
|
305
|
+
}
|
|
306
|
+
Capability.STOP -> {
|
|
307
|
+
playerCommandsBuilder.add(Player.COMMAND_STOP)
|
|
308
|
+
}
|
|
309
|
+
Capability.SKIP_TO_NEXT -> {
|
|
310
|
+
playerCommandsBuilder.add(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
|
|
311
|
+
playerCommandsBuilder.add(Player.COMMAND_SEEK_TO_NEXT)
|
|
312
|
+
}
|
|
313
|
+
Capability.SKIP_TO_PREVIOUS -> {
|
|
314
|
+
playerCommandsBuilder.add(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
|
|
315
|
+
playerCommandsBuilder.add(Player.COMMAND_SEEK_TO_PREVIOUS)
|
|
316
|
+
}
|
|
317
|
+
Capability.JUMP_FORWARD -> {
|
|
318
|
+
playerCommandsBuilder.add(Player.COMMAND_SEEK_FORWARD)
|
|
319
|
+
}
|
|
320
|
+
Capability.JUMP_BACKWARD -> {
|
|
321
|
+
playerCommandsBuilder.add(Player.COMMAND_SEEK_BACK)
|
|
322
|
+
}
|
|
323
|
+
Capability.SEEK_TO -> {
|
|
324
|
+
playerCommandsBuilder.add(Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)
|
|
325
|
+
}
|
|
326
|
+
else -> { }
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
customLayout = customActionsList?.map {
|
|
330
|
+
v -> CustomButton(
|
|
331
|
+
displayName = v,
|
|
332
|
+
sessionCommand = v,
|
|
333
|
+
iconRes = BundleUtils.getCustomIcon(
|
|
334
|
+
this,
|
|
335
|
+
customActions,
|
|
336
|
+
v,
|
|
337
|
+
TrackPlayerR.drawable.ifl_24px
|
|
338
|
+
)
|
|
339
|
+
).commandButton
|
|
340
|
+
} ?: ImmutableList.of()
|
|
341
|
+
|
|
342
|
+
val sessionCommandsBuilder = MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
|
|
343
|
+
customLayout.forEach {
|
|
344
|
+
v ->
|
|
345
|
+
v.sessionCommand?.let { sessionCommandsBuilder.add(it) }
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
sessionCommands = sessionCommandsBuilder.build()
|
|
349
|
+
playerCommands = playerCommandsBuilder.build()
|
|
350
|
+
|
|
351
|
+
if (mediaSession.mediaNotificationControllerInfo != null) {
|
|
352
|
+
// https://github.com/androidx/media/blob/c35a9d62baec57118ea898e271ac66819399649b/demos/session_service/src/main/java/androidx/media3/demo/session/DemoMediaLibrarySessionCallback.kt#L107
|
|
353
|
+
mediaSession.setCustomLayout(
|
|
354
|
+
mediaSession.mediaNotificationControllerInfo!!,
|
|
355
|
+
customLayout
|
|
356
|
+
)
|
|
357
|
+
mediaSession.setAvailableCommands(
|
|
358
|
+
mediaSession.mediaNotificationControllerInfo!!,
|
|
359
|
+
sessionCommands!!,
|
|
360
|
+
playerCommands!!
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
@MainThread
|
|
366
|
+
private fun progressUpdateEventFlow(interval: Double) = flow {
|
|
367
|
+
while (true) {
|
|
368
|
+
if (player.isPlaying) {
|
|
369
|
+
val bundle = progressUpdateEvent()
|
|
370
|
+
emit(bundle)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
delay((interval * 1000).toLong())
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
@MainThread
|
|
378
|
+
private suspend fun progressUpdateEvent(): Bundle {
|
|
379
|
+
return withContext(Dispatchers.Main) {
|
|
380
|
+
Bundle().apply {
|
|
381
|
+
putDouble(POSITION_KEY, player.position.toSeconds())
|
|
382
|
+
putDouble(DURATION_KEY, player.duration.toSeconds())
|
|
383
|
+
putDouble(BUFFERED_POSITION_KEY, player.bufferedPosition.toSeconds())
|
|
384
|
+
putInt(TRACK_KEY, player.currentIndex)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
@MainThread
|
|
390
|
+
fun add(track: Track) {
|
|
391
|
+
add(listOf(track))
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
@MainThread
|
|
395
|
+
fun add(tracks: List<Track>) {
|
|
396
|
+
try {
|
|
397
|
+
Timber.tag("RNTP").d("MusicService.add: Converting ${tracks.size} tracks to AudioItems")
|
|
398
|
+
val items = tracks.map { it.toAudioItem() }
|
|
399
|
+
|
|
400
|
+
Timber.tag("RNTP").d("MusicService.add: Calling player.add with ${items.size} items")
|
|
401
|
+
player.add(items)
|
|
402
|
+
|
|
403
|
+
Timber.tag("RNTP").d("MusicService.add: Successfully added tracks to player")
|
|
404
|
+
} catch (exception: Exception) {
|
|
405
|
+
Timber.tag("RNTP").e(exception, "MusicService.add: Error adding tracks")
|
|
406
|
+
throw exception
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
@MainThread
|
|
411
|
+
fun add(tracks: List<Track>, atIndex: Int) {
|
|
412
|
+
val items = tracks.map { it.toAudioItem() }
|
|
413
|
+
player.add(items, atIndex)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
@MainThread
|
|
417
|
+
fun load(track: Track) {
|
|
418
|
+
player.load(track.toAudioItem())
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
@MainThread
|
|
422
|
+
fun move(fromIndex: Int, toIndex: Int) {
|
|
423
|
+
player.move(fromIndex, toIndex)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
@MainThread
|
|
427
|
+
fun remove(index: Int) {
|
|
428
|
+
remove(listOf(index))
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
@MainThread
|
|
432
|
+
fun remove(indexes: List<Int>) {
|
|
433
|
+
player.remove(indexes)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
@MainThread
|
|
437
|
+
fun clear() {
|
|
438
|
+
player.clear()
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
@MainThread
|
|
442
|
+
fun play() {
|
|
443
|
+
player.play()
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
@MainThread
|
|
447
|
+
fun pause() {
|
|
448
|
+
player.pause()
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
@MainThread
|
|
452
|
+
fun stop() {
|
|
453
|
+
player.stop()
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
@MainThread
|
|
457
|
+
fun removeUpcomingTracks() {
|
|
458
|
+
player.removeUpcomingItems()
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
@MainThread
|
|
462
|
+
fun removePreviousTracks() {
|
|
463
|
+
player.removePreviousItems()
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
@MainThread
|
|
467
|
+
fun skip(index: Int) {
|
|
468
|
+
player.jumpToItem(index)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
@MainThread
|
|
472
|
+
fun skipToNext() {
|
|
473
|
+
player.next()
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
@MainThread
|
|
477
|
+
fun skipToPrevious() {
|
|
478
|
+
player.previous()
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
@MainThread
|
|
482
|
+
fun seekTo(seconds: Float) {
|
|
483
|
+
player.seek((seconds * 1000).toLong(), TimeUnit.MILLISECONDS)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
@MainThread
|
|
487
|
+
fun seekBy(offset: Float) {
|
|
488
|
+
player.seekBy((offset.toLong()), TimeUnit.SECONDS)
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
@MainThread
|
|
492
|
+
fun retry() {
|
|
493
|
+
player.prepare()
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
@MainThread
|
|
497
|
+
fun getCurrentTrackIndex(): Int = player.currentIndex
|
|
498
|
+
|
|
499
|
+
@MainThread
|
|
500
|
+
fun getRate(): Float = player.playbackSpeed
|
|
501
|
+
|
|
502
|
+
@MainThread
|
|
503
|
+
fun setRate(value: Float) {
|
|
504
|
+
player.playbackSpeed = value
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
@MainThread
|
|
508
|
+
fun getPitch(): Float = player.playbackPitch
|
|
509
|
+
|
|
510
|
+
@MainThread
|
|
511
|
+
fun setPitch(value: Float) {
|
|
512
|
+
player.playbackPitch = value
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
@MainThread
|
|
516
|
+
fun getRepeatMode(): RepeatMode = player.repeatMode
|
|
517
|
+
|
|
518
|
+
@MainThread
|
|
519
|
+
fun setRepeatMode(value: RepeatMode) {
|
|
520
|
+
player.repeatMode = value
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
@MainThread
|
|
524
|
+
fun getVolume(): Float = player.volume
|
|
525
|
+
|
|
526
|
+
@MainThread
|
|
527
|
+
fun setVolume(value: Float) {
|
|
528
|
+
player.volume = value
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
@MainThread
|
|
532
|
+
fun setAnimatedVolume(value: Float, duration: Long = 500L, interval: Long = 20L, emitEventMsg: String = ""): Deferred<Unit> {
|
|
533
|
+
val eventMsgBundle = Bundle()
|
|
534
|
+
eventMsgBundle.putString(DATA_KEY, emitEventMsg)
|
|
535
|
+
return player.fadeVolume(value, duration, interval) {
|
|
536
|
+
emit(
|
|
537
|
+
MusicEvents.PLAYBACK_ANIMATED_VOLUME_CHANGED,
|
|
538
|
+
eventMsgBundle
|
|
539
|
+
)
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
fun fadeOutPause (duration: Long = 500L, interval: Long = 20L) {
|
|
544
|
+
player.fadeVolume(0f, duration, interval) {
|
|
545
|
+
player.pause()
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
fun fadeOutNext (duration: Long = 500L, interval: Long = 20L, toVolume: Float = 1f) {
|
|
550
|
+
player.fadeVolume(0f, duration, interval) {
|
|
551
|
+
player.next()
|
|
552
|
+
player.fadeVolume(toVolume, duration, interval)
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
fun fadeOutPrevious (duration: Long = 500L, interval: Long = 20L, toVolume: Float = 1f) {
|
|
557
|
+
player.fadeVolume(0f, duration, interval) {
|
|
558
|
+
player.previous()
|
|
559
|
+
player.fadeVolume(toVolume, duration, interval)
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
fun fadeOutJump (index: Int, duration: Long = 500L, interval: Long = 20L, toVolume: Float = 1f) {
|
|
564
|
+
player.fadeVolume(0f, duration, interval) {
|
|
565
|
+
player.jumpToItem(index)
|
|
566
|
+
player.fadeVolume(toVolume, duration, interval)
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
@MainThread
|
|
570
|
+
fun getDurationInSeconds(): Double = player.duration.toSeconds()
|
|
571
|
+
|
|
572
|
+
@MainThread
|
|
573
|
+
fun getPositionInSeconds(): Double = player.position.toSeconds()
|
|
574
|
+
|
|
575
|
+
@MainThread
|
|
576
|
+
fun getBufferedPositionInSeconds(): Double = player.bufferedPosition.toSeconds()
|
|
577
|
+
|
|
578
|
+
@MainThread
|
|
579
|
+
fun getPlayerStateBundle(state: AudioPlayerState): Bundle {
|
|
580
|
+
val bundle = Bundle()
|
|
581
|
+
bundle.putString(STATE_KEY, state.asLibState.state)
|
|
582
|
+
if (state == AudioPlayerState.ERROR) {
|
|
583
|
+
bundle.putBundle(ERROR_KEY, getPlaybackErrorBundle())
|
|
584
|
+
}
|
|
585
|
+
return bundle
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
@MainThread
|
|
589
|
+
fun updateMetadataForTrack(index: Int, track: Track) {
|
|
590
|
+
player.replaceItem(index, track.toAudioItem())
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
@MainThread
|
|
594
|
+
fun updateNowPlayingMetadata(track: Track) {
|
|
595
|
+
updateMetadataForTrack(player.currentIndex, track)
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
@MainThread
|
|
599
|
+
fun clearNotificationMetadata() {
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
private fun emitPlaybackTrackChangedEvents(
|
|
603
|
+
index: Int?,
|
|
604
|
+
previousIndex: Int?,
|
|
605
|
+
oldPosition: Double
|
|
606
|
+
) {
|
|
607
|
+
val a = Bundle()
|
|
608
|
+
a.putDouble(POSITION_KEY, oldPosition)
|
|
609
|
+
if (index != null) {
|
|
610
|
+
a.putInt(NEXT_TRACK_KEY, index)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (previousIndex != null) {
|
|
614
|
+
a.putInt(TRACK_KEY, previousIndex)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
emit(MusicEvents.PLAYBACK_TRACK_CHANGED, a)
|
|
618
|
+
|
|
619
|
+
val b = Bundle()
|
|
620
|
+
b.putDouble("lastPosition", oldPosition)
|
|
621
|
+
if (tracks.isNotEmpty()) {
|
|
622
|
+
b.putInt("index", player.currentIndex)
|
|
623
|
+
b.putBundle("track", tracks[player.currentIndex].originalItem)
|
|
624
|
+
if (previousIndex != null) {
|
|
625
|
+
b.putInt("lastIndex", previousIndex)
|
|
626
|
+
b.putBundle("lastTrack", tracks[previousIndex].originalItem)
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
emit(MusicEvents.PLAYBACK_ACTIVE_TRACK_CHANGED, b)
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
private fun emitQueueEndedEvent() {
|
|
633
|
+
val bundle = Bundle()
|
|
634
|
+
bundle.putInt(TRACK_KEY, player.currentIndex)
|
|
635
|
+
bundle.putDouble(POSITION_KEY, player.position.toSeconds())
|
|
636
|
+
emit(MusicEvents.PLAYBACK_QUEUE_ENDED, bundle)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
@Suppress("DEPRECATION")
|
|
640
|
+
fun isForegroundService(): Boolean {
|
|
641
|
+
val manager = baseContext.getSystemService(ACTIVITY_SERVICE) as ActivityManager
|
|
642
|
+
for (service in manager.getRunningServices(Int.MAX_VALUE)) {
|
|
643
|
+
if (MusicService::class.java.name == service.service.className) {
|
|
644
|
+
return service.foreground
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
Timber.e("isForegroundService found no matching service")
|
|
648
|
+
return false
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
@MainThread
|
|
652
|
+
private fun observeEvents() {
|
|
653
|
+
scope.launch {
|
|
654
|
+
event.stateChange.collect {
|
|
655
|
+
emit(MusicEvents.PLAYBACK_STATE, getPlayerStateBundle(it))
|
|
656
|
+
|
|
657
|
+
if (it == AudioPlayerState.ENDED && player.nextItem == null) {
|
|
658
|
+
emitQueueEndedEvent()
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
scope.launch {
|
|
664
|
+
event.audioItemTransition.collect {
|
|
665
|
+
if (it !is AudioItemTransitionReason.REPEAT) {
|
|
666
|
+
emitPlaybackTrackChangedEvents(
|
|
667
|
+
player.currentIndex,
|
|
668
|
+
player.previousIndex,
|
|
669
|
+
(it?.oldPosition ?: 0).toSeconds()
|
|
670
|
+
)
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
scope.launch {
|
|
676
|
+
event.onAudioFocusChanged.collect {
|
|
677
|
+
Bundle().apply {
|
|
678
|
+
putBoolean(IS_FOCUS_LOSS_PERMANENT_KEY, it.isFocusLostPermanently)
|
|
679
|
+
putBoolean(IS_PAUSED_KEY, it.isPaused)
|
|
680
|
+
emit(MusicEvents.BUTTON_DUCK, this)
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
scope.launch {
|
|
686
|
+
event.onPlayerActionTriggeredExternally.collect {
|
|
687
|
+
when (it) {
|
|
688
|
+
is MediaSessionCallback.RATING -> {
|
|
689
|
+
Bundle().apply {
|
|
690
|
+
setRating(this, "rating", it.rating)
|
|
691
|
+
emit(MusicEvents.BUTTON_SET_RATING, this)
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
is MediaSessionCallback.SEEK -> {
|
|
695
|
+
Bundle().apply {
|
|
696
|
+
putDouble("position", it.positionMs.toSeconds())
|
|
697
|
+
emit(MusicEvents.BUTTON_SEEK_TO, this)
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
MediaSessionCallback.PLAY -> emit(MusicEvents.BUTTON_PLAY)
|
|
701
|
+
MediaSessionCallback.PAUSE -> emit(MusicEvents.BUTTON_PAUSE)
|
|
702
|
+
MediaSessionCallback.NEXT -> emit(MusicEvents.BUTTON_SKIP_NEXT)
|
|
703
|
+
MediaSessionCallback.PREVIOUS -> emit(MusicEvents.BUTTON_SKIP_PREVIOUS)
|
|
704
|
+
MediaSessionCallback.STOP -> emit(MusicEvents.BUTTON_STOP)
|
|
705
|
+
MediaSessionCallback.FORWARD -> {
|
|
706
|
+
Bundle().apply {
|
|
707
|
+
val interval = latestOptions?.getDouble(FORWARD_JUMP_INTERVAL_KEY, DEFAULT_JUMP_INTERVAL) ?:
|
|
708
|
+
DEFAULT_JUMP_INTERVAL
|
|
709
|
+
putInt("interval", interval.toInt())
|
|
710
|
+
emit(MusicEvents.BUTTON_JUMP_FORWARD, this)
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
MediaSessionCallback.REWIND -> {
|
|
714
|
+
Bundle().apply {
|
|
715
|
+
val interval = latestOptions?.getDouble(BACKWARD_JUMP_INTERVAL_KEY, DEFAULT_JUMP_INTERVAL) ?:
|
|
716
|
+
DEFAULT_JUMP_INTERVAL
|
|
717
|
+
putInt("interval", interval.toInt())
|
|
718
|
+
emit(MusicEvents.BUTTON_JUMP_BACKWARD, this)
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
is MediaSessionCallback.CUSTOMACTION -> {
|
|
723
|
+
Bundle().apply {
|
|
724
|
+
putString("customAction", it.customAction)
|
|
725
|
+
emit(MusicEvents.BUTTON_CUSTOM_ACTION, this)
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
scope.launch {
|
|
733
|
+
event.onTimedMetadata.collect {
|
|
734
|
+
val data = MetadataAdapter.fromMetadata(it)
|
|
735
|
+
val bundle = Bundle().apply {
|
|
736
|
+
putParcelableArrayList(METADATA_PAYLOAD_KEY, ArrayList(data))
|
|
737
|
+
}
|
|
738
|
+
emit(MusicEvents.METADATA_TIMED_RECEIVED, bundle)
|
|
739
|
+
|
|
740
|
+
// TODO: Handle the different types of metadata and publish to new events
|
|
741
|
+
val metadata = PlaybackMetadata.fromId3Metadata(it)
|
|
742
|
+
?: PlaybackMetadata.fromIcy(it)
|
|
743
|
+
?: PlaybackMetadata.fromVorbisComment(it)
|
|
744
|
+
?: PlaybackMetadata.fromQuickTime(it)
|
|
745
|
+
|
|
746
|
+
if (metadata != null) {
|
|
747
|
+
Bundle().apply {
|
|
748
|
+
putString("source", metadata.source)
|
|
749
|
+
putString("title", metadata.title)
|
|
750
|
+
putString("url", metadata.url)
|
|
751
|
+
putString("artist", metadata.artist)
|
|
752
|
+
putString("album", metadata.album)
|
|
753
|
+
putString("date", metadata.date)
|
|
754
|
+
putString("genre", metadata.genre)
|
|
755
|
+
emit(MusicEvents.PLAYBACK_METADATA, this)
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
scope.launch {
|
|
762
|
+
event.onCommonMetadata.collect {
|
|
763
|
+
val data = MetadataAdapter.fromMediaMetadata(it)
|
|
764
|
+
val bundle = Bundle().apply {
|
|
765
|
+
putBundle(METADATA_PAYLOAD_KEY, data)
|
|
766
|
+
}
|
|
767
|
+
emit(MusicEvents.METADATA_COMMON_RECEIVED, bundle)
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
scope.launch {
|
|
772
|
+
event.playWhenReadyChange.collect {
|
|
773
|
+
Bundle().apply {
|
|
774
|
+
putBoolean("playWhenReady", it.playWhenReady)
|
|
775
|
+
emit(MusicEvents.PLAYBACK_PLAY_WHEN_READY_CHANGED, this)
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
scope.launch {
|
|
781
|
+
event.playbackError.collect {
|
|
782
|
+
emit(MusicEvents.PLAYBACK_ERROR, getPlaybackErrorBundle())
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
private fun getPlaybackErrorBundle(): Bundle {
|
|
788
|
+
val bundle = Bundle()
|
|
789
|
+
val error = playbackError
|
|
790
|
+
if (error?.message != null) {
|
|
791
|
+
bundle.putString("message", error.message)
|
|
792
|
+
}
|
|
793
|
+
if (error?.code != null) {
|
|
794
|
+
bundle.putString("code", "android-" + error.code)
|
|
795
|
+
}
|
|
796
|
+
return bundle
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
@SuppressLint("VisibleForTests")
|
|
800
|
+
@MainThread
|
|
801
|
+
fun emit(event: String, data: Bundle? = null) {
|
|
802
|
+
reactContext?.emitDeviceEvent(event, data?.let { Arguments.fromBundle(it) })
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
@SuppressLint("VisibleForTests")
|
|
806
|
+
@MainThread
|
|
807
|
+
private fun emitList(event: String, data: List<Bundle> = emptyList()) {
|
|
808
|
+
val payload = Arguments.createArray()
|
|
809
|
+
data.forEach { payload.pushMap(Arguments.fromBundle(it)) }
|
|
810
|
+
|
|
811
|
+
reactContext?.emitDeviceEvent(event, payload)
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
override fun getTaskConfig(intent: Intent?): HeadlessJsTaskConfig {
|
|
815
|
+
return HeadlessJsTaskConfig(TASK_KEY, Arguments.createMap(), 0, true)
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
@MainThread
|
|
819
|
+
override fun onBind(intent: Intent?): IBinder? {
|
|
820
|
+
val intentAction = intent?.action
|
|
821
|
+
Timber.tag("APM").d("onbind: $intentAction")
|
|
822
|
+
return if (intentAction != null) {
|
|
823
|
+
super.onBind(intent)
|
|
824
|
+
} else {
|
|
825
|
+
binder
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
override fun onUnbind(intent: Intent?): Boolean {
|
|
830
|
+
Timber.tag("APM").d("unbind: ${intent?.action}")
|
|
831
|
+
return super.onUnbind(intent)
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
override fun onUpdateNotification(session: MediaSession, startInForegroundRequired: Boolean) {
|
|
835
|
+
// https://github.com/androidx/media/issues/843#issuecomment-1860555950
|
|
836
|
+
super.onUpdateNotification(session, true)
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
@MainThread
|
|
840
|
+
override fun onTaskRemoved(rootIntent: Intent?) {
|
|
841
|
+
onUnbind(rootIntent)
|
|
842
|
+
Timber
|
|
843
|
+
.tag("APM")
|
|
844
|
+
.d("onTaskRemoved: ${::player.isInitialized}, $appKilledPlaybackBehavior")
|
|
845
|
+
if (!::player.isInitialized) {
|
|
846
|
+
mediaSession.release()
|
|
847
|
+
return
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
when (appKilledPlaybackBehavior) {
|
|
851
|
+
AppKilledPlaybackBehavior.PAUSE_PLAYBACK -> player.pause()
|
|
852
|
+
AppKilledPlaybackBehavior.STOP_PLAYBACK_AND_REMOVE_NOTIFICATION -> {
|
|
853
|
+
Timber.tag("APM").d("onTaskRemoved: Killing service")
|
|
854
|
+
mediaSession.release()
|
|
855
|
+
player.clear()
|
|
856
|
+
player.stop()
|
|
857
|
+
// HACK: the service first stops, then starts, then call onTaskRemove. Why system
|
|
858
|
+
// registers the service being restarted?
|
|
859
|
+
player.destroy()
|
|
860
|
+
scope.cancel()
|
|
861
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
862
|
+
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
863
|
+
} else {
|
|
864
|
+
@Suppress("DEPRECATION")
|
|
865
|
+
stopForeground(true)
|
|
866
|
+
}
|
|
867
|
+
onDestroy()
|
|
868
|
+
// https://github.com/androidx/media/issues/27#issuecomment-1456042326
|
|
869
|
+
stopSelf()
|
|
870
|
+
exitProcess(0)
|
|
871
|
+
}
|
|
872
|
+
else -> {}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
@SuppressLint("VisibleForTests")
|
|
877
|
+
private fun selfWake(clientPackageName: String): Boolean {
|
|
878
|
+
val reactActivity = reactContext?.currentActivity
|
|
879
|
+
if (
|
|
880
|
+
// HACK: validate reactActivity is present; if not, send wake intent
|
|
881
|
+
(reactActivity == null || reactActivity.isDestroyed)
|
|
882
|
+
&& Settings.canDrawOverlays(this)
|
|
883
|
+
) {
|
|
884
|
+
val currentTime = System.currentTimeMillis()
|
|
885
|
+
if (currentTime - lastWake < 100000) {
|
|
886
|
+
return false
|
|
887
|
+
}
|
|
888
|
+
lastWake = currentTime
|
|
889
|
+
val activityIntent = packageManager.getLaunchIntentForPackage(packageName)
|
|
890
|
+
activityIntent!!.data = "trackplayer://service-bound".toUri()
|
|
891
|
+
activityIntent.action = Intent.ACTION_VIEW
|
|
892
|
+
activityIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
893
|
+
var activityOptions = ActivityOptions.makeBasic()
|
|
894
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
895
|
+
activityOptions = activityOptions.setPendingIntentBackgroundActivityStartMode(
|
|
896
|
+
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
|
|
897
|
+
}
|
|
898
|
+
this.startActivity(activityIntent, activityOptions.toBundle())
|
|
899
|
+
return true
|
|
900
|
+
}
|
|
901
|
+
return false
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession {
|
|
905
|
+
Timber.tag("APM").d("onGetSession: ${controllerInfo.packageName}")
|
|
906
|
+
return mediaSession
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
fun notifyChildrenChanged() {
|
|
910
|
+
mediaSession.connectedControllers.forEach {
|
|
911
|
+
controller ->
|
|
912
|
+
mediaTree.forEach {
|
|
913
|
+
it -> mediaSession.notifyChildrenChanged(controller, it.key, it.value.size, null)
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
@MainThread
|
|
920
|
+
override fun onHeadlessJsTaskFinish(taskId: Int) {
|
|
921
|
+
// This is empty so ReactNative doesn't kill this service
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
@MainThread
|
|
925
|
+
override fun onDestroy() {
|
|
926
|
+
Timber.tag("APM").d("RNTP service is destroyed.")
|
|
927
|
+
if (::mediaSession.isInitialized) {
|
|
928
|
+
mediaSession.release()
|
|
929
|
+
}
|
|
930
|
+
if (::fakePlayer.isInitialized) {
|
|
931
|
+
fakePlayer.release()
|
|
932
|
+
}
|
|
933
|
+
if (::player.isInitialized) {
|
|
934
|
+
player.destroy()
|
|
935
|
+
}
|
|
936
|
+
progressUpdateJob?.cancel()
|
|
937
|
+
super.onDestroy()
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
fun onMediaKeyEvent(intent: Intent?): Boolean? {
|
|
941
|
+
val keyEvent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
942
|
+
intent?.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java)
|
|
943
|
+
} else {
|
|
944
|
+
intent?.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
if (keyEvent?.action == KeyEvent.ACTION_DOWN) {
|
|
948
|
+
return when (keyEvent.keyCode) {
|
|
949
|
+
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
|
|
950
|
+
emit(MusicEvents.BUTTON_PLAY_PAUSE)
|
|
951
|
+
true
|
|
952
|
+
}
|
|
953
|
+
KeyEvent.KEYCODE_MEDIA_STOP -> {
|
|
954
|
+
emit(MusicEvents.BUTTON_STOP)
|
|
955
|
+
true
|
|
956
|
+
}
|
|
957
|
+
KeyEvent.KEYCODE_MEDIA_PAUSE -> {
|
|
958
|
+
emit(MusicEvents.BUTTON_PAUSE)
|
|
959
|
+
true
|
|
960
|
+
}
|
|
961
|
+
KeyEvent.KEYCODE_MEDIA_PLAY -> {
|
|
962
|
+
emit(MusicEvents.BUTTON_PLAY)
|
|
963
|
+
true
|
|
964
|
+
}
|
|
965
|
+
KeyEvent.KEYCODE_MEDIA_NEXT -> {
|
|
966
|
+
emit(MusicEvents.BUTTON_SKIP_NEXT)
|
|
967
|
+
true
|
|
968
|
+
}
|
|
969
|
+
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> {
|
|
970
|
+
emit(MusicEvents.BUTTON_SKIP_PREVIOUS)
|
|
971
|
+
true
|
|
972
|
+
}
|
|
973
|
+
KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD, KeyEvent.KEYCODE_MEDIA_STEP_FORWARD -> {
|
|
974
|
+
emit(MusicEvents.BUTTON_JUMP_FORWARD)
|
|
975
|
+
true
|
|
976
|
+
}
|
|
977
|
+
KeyEvent.KEYCODE_MEDIA_REWIND, KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD, KeyEvent.KEYCODE_MEDIA_STEP_BACKWARD -> {
|
|
978
|
+
emit(MusicEvents.BUTTON_JUMP_BACKWARD)
|
|
979
|
+
true
|
|
980
|
+
}
|
|
981
|
+
else -> null
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return null
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
@MainThread
|
|
988
|
+
inner class MusicBinder : Binder() {
|
|
989
|
+
val service = this@MusicService
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
private inner class APMMediaSessionCallback: MediaLibrarySession.Callback {
|
|
993
|
+
// HACK: I'm sure most of the callbacks were not implemented correctly.
|
|
994
|
+
// ATM I only care that andorid auto still functions.
|
|
995
|
+
|
|
996
|
+
private val rootItem = buildMediaItem(title = "root", mediaId = AA_ROOT_KEY, isPlayable = false)
|
|
997
|
+
private val forYouItem = buildMediaItem(title = "For You", mediaId = AA_FOR_YOU_KEY, isPlayable = false)
|
|
998
|
+
|
|
999
|
+
override fun onDisconnected(
|
|
1000
|
+
session: MediaSession,
|
|
1001
|
+
controller: MediaSession.ControllerInfo
|
|
1002
|
+
) {
|
|
1003
|
+
emit(MusicEvents.CONNECTOR_DISCONNECTED, Bundle().apply {
|
|
1004
|
+
putString("package", controller.packageName)
|
|
1005
|
+
})
|
|
1006
|
+
super.onDisconnected(session, controller)
|
|
1007
|
+
}
|
|
1008
|
+
// Configure commands available to the controller in onConnect()
|
|
1009
|
+
@OptIn(UnstableApi::class)
|
|
1010
|
+
override fun onConnect(
|
|
1011
|
+
session: MediaSession,
|
|
1012
|
+
controller: MediaSession.ControllerInfo
|
|
1013
|
+
): MediaSession.ConnectionResult {
|
|
1014
|
+
Timber.tag("APM").d("connection via: ${controller.packageName}")
|
|
1015
|
+
val isMediaNotificationController = session.isMediaNotificationController(controller)
|
|
1016
|
+
val isAutomotiveController = session.isAutomotiveController(controller)
|
|
1017
|
+
val isAutoCompanionController = session.isAutoCompanionController(controller)
|
|
1018
|
+
emit(MusicEvents.CONNECTOR_CONNECTED, Bundle().apply {
|
|
1019
|
+
putString("package", controller.packageName)
|
|
1020
|
+
putBoolean("isMediaNotificationController", isMediaNotificationController)
|
|
1021
|
+
putBoolean("isAutomotiveController", isAutomotiveController)
|
|
1022
|
+
putBoolean("isAutoCompanionController", isAutoCompanionController)
|
|
1023
|
+
})
|
|
1024
|
+
if (controller.packageName in arrayOf(
|
|
1025
|
+
"com.android.systemui",
|
|
1026
|
+
// https://github.com/googlesamples/android-media-controller
|
|
1027
|
+
"com.example.android.mediacontroller",
|
|
1028
|
+
// Android Auto
|
|
1029
|
+
"com.google.android.projection.gearhead"
|
|
1030
|
+
)) {
|
|
1031
|
+
lastConnectedPackage = controller.packageName
|
|
1032
|
+
// HACK: attempt to wake up activity (for legacy APM). if not, start headless.
|
|
1033
|
+
if (!selfWake(controller.packageName)) {
|
|
1034
|
+
onStartCommand(null, 0, 0)
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
return if (
|
|
1038
|
+
isMediaNotificationController ||
|
|
1039
|
+
isAutomotiveController ||
|
|
1040
|
+
isAutoCompanionController
|
|
1041
|
+
) {
|
|
1042
|
+
MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
|
1043
|
+
.setCustomLayout(customLayout)
|
|
1044
|
+
.setAvailableSessionCommands(sessionCommands ?: MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS)
|
|
1045
|
+
.setAvailablePlayerCommands(playerCommands ?: MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS)
|
|
1046
|
+
.build()
|
|
1047
|
+
} else {
|
|
1048
|
+
super.onConnect(session, controller)
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
override fun onCustomCommand(
|
|
1053
|
+
session: MediaSession,
|
|
1054
|
+
controller: MediaSession.ControllerInfo,
|
|
1055
|
+
customCommand: SessionCommand,
|
|
1056
|
+
args: Bundle
|
|
1057
|
+
): ListenableFuture<SessionResult> {
|
|
1058
|
+
emit(MusicEvents.BUTTON_CUSTOM_ACTION, Bundle().apply { putString("customAction", customCommand.customAction) })
|
|
1059
|
+
return super.onCustomCommand(session, controller, customCommand, args)
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
override fun onGetLibraryRoot(
|
|
1063
|
+
session: MediaLibrarySession,
|
|
1064
|
+
browser: MediaSession.ControllerInfo,
|
|
1065
|
+
params: LibraryParams?
|
|
1066
|
+
): ListenableFuture<LibraryResult<MediaItem>> {
|
|
1067
|
+
val rootExtras = Bundle().apply {
|
|
1068
|
+
putBoolean("android.media.browse.CONTENT_STYLE_SUPPORTED", true)
|
|
1069
|
+
putInt("android.media.browse.CONTENT_STYLE_BROWSABLE_HINT", mediaTreeStyle[0])
|
|
1070
|
+
putInt("android.media.browse.CONTENT_STYLE_PLAYABLE_HINT", mediaTreeStyle[1])
|
|
1071
|
+
}
|
|
1072
|
+
val libraryParams = LibraryParams.Builder().setExtras(rootExtras).build()
|
|
1073
|
+
Timber.tag("APM").d("acquiring root: ${browser.packageName}")
|
|
1074
|
+
// https://github.com/androidx/media/issues/1731#issuecomment-2411109462
|
|
1075
|
+
val mRootItem = when (browser.packageName) {
|
|
1076
|
+
"com.google.android.googlequicksearchbox" -> {
|
|
1077
|
+
if (mediaTree[AA_FOR_YOU_KEY] == null) rootItem else forYouItem
|
|
1078
|
+
}
|
|
1079
|
+
else -> rootItem
|
|
1080
|
+
}
|
|
1081
|
+
return Futures.immediateFuture(LibraryResult.ofItem(mRootItem, libraryParams))
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
override fun onGetChildren(
|
|
1085
|
+
session: MediaLibrarySession,
|
|
1086
|
+
browser: MediaSession.ControllerInfo,
|
|
1087
|
+
parentId: String,
|
|
1088
|
+
page: Int,
|
|
1089
|
+
pageSize: Int,
|
|
1090
|
+
params: LibraryParams?
|
|
1091
|
+
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
|
1092
|
+
emit(MusicEvents.BUTTON_BROWSE, Bundle().apply { putString("mediaId", parentId) })
|
|
1093
|
+
return Futures.immediateFuture(LibraryResult.ofItemList(mediaTree[parentId] ?: listOf(), null))
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
override fun onGetItem(
|
|
1097
|
+
session: MediaLibrarySession,
|
|
1098
|
+
browser: MediaSession.ControllerInfo,
|
|
1099
|
+
mediaId: String
|
|
1100
|
+
): ListenableFuture<LibraryResult<MediaItem>> {
|
|
1101
|
+
Timber.tag("APM").d("acquiring item: ${browser.packageName}, $mediaId")
|
|
1102
|
+
// emit(MusicEvents.BUTTON_PLAY_FROM_ID, Bundle().apply { putString("id", mediaId) })
|
|
1103
|
+
return Futures.immediateFuture(LibraryResult.ofItem(rootItem, null))
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
override fun onSearch(
|
|
1107
|
+
session: MediaLibrarySession,
|
|
1108
|
+
browser: MediaSession.ControllerInfo,
|
|
1109
|
+
query: String,
|
|
1110
|
+
params: LibraryParams?
|
|
1111
|
+
): ListenableFuture<LibraryResult<Void>> {
|
|
1112
|
+
Timber.tag("APM").d("searching: ${browser.packageName}, $query")
|
|
1113
|
+
return super.onSearch(session, browser, query, params)
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
override fun onAddMediaItems(
|
|
1117
|
+
mediaSession: MediaSession,
|
|
1118
|
+
controller: MediaSession.ControllerInfo,
|
|
1119
|
+
mediaItems: MutableList<MediaItem>
|
|
1120
|
+
): ListenableFuture<MutableList<MediaItem>> {
|
|
1121
|
+
Timber.tag("APM")
|
|
1122
|
+
.d("addMediaItem: ${controller.packageName}, ${mediaItems[0].mediaId}, ${mediaItems.size}")
|
|
1123
|
+
return super.onAddMediaItems(mediaSession, controller, mediaItems)
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
override fun onSetMediaItems(
|
|
1127
|
+
mediaSession: MediaSession,
|
|
1128
|
+
controller: MediaSession.ControllerInfo,
|
|
1129
|
+
mediaItems: MutableList<MediaItem>,
|
|
1130
|
+
startIndex: Int,
|
|
1131
|
+
startPositionMs: Long
|
|
1132
|
+
): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
|
|
1133
|
+
Timber.tag("APM").d("setMediaItem: ${controller.packageName}, ${mediaItems[0].toBundle()}")
|
|
1134
|
+
if (mediaItems[0].requestMetadata.searchQuery == null) {
|
|
1135
|
+
emit(MusicEvents.BUTTON_PLAY_FROM_ID, Bundle().apply {
|
|
1136
|
+
putString("id", mediaItems[0].mediaId)
|
|
1137
|
+
})
|
|
1138
|
+
} else {
|
|
1139
|
+
emit(MusicEvents.BUTTON_PLAY_FROM_SEARCH, Bundle().apply {
|
|
1140
|
+
putString("query", mediaItems[0].requestMetadata.searchQuery)
|
|
1141
|
+
})
|
|
1142
|
+
}
|
|
1143
|
+
return super.onSetMediaItems(
|
|
1144
|
+
mediaSession,
|
|
1145
|
+
controller,
|
|
1146
|
+
mediaItems,
|
|
1147
|
+
startIndex,
|
|
1148
|
+
startPositionMs
|
|
1149
|
+
)
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
override fun onMediaButtonEvent(
|
|
1153
|
+
session: MediaSession,
|
|
1154
|
+
controllerInfo: MediaSession.ControllerInfo,
|
|
1155
|
+
intent: Intent
|
|
1156
|
+
): Boolean {
|
|
1157
|
+
return onMediaKeyEvent(intent) ?: super.onMediaButtonEvent(session, controllerInfo, intent)
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
override fun onGetSearchResult(
|
|
1161
|
+
session: MediaLibrarySession,
|
|
1162
|
+
browser: MediaSession.ControllerInfo,
|
|
1163
|
+
query: String,
|
|
1164
|
+
page: Int,
|
|
1165
|
+
pageSize: Int,
|
|
1166
|
+
params: LibraryParams?
|
|
1167
|
+
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
|
1168
|
+
Timber.tag("APM").d("searching2: ${browser.packageName}, $query")
|
|
1169
|
+
return super.onGetSearchResult(session, browser, query, page, pageSize, params)
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
override fun onPlaybackResumption(
|
|
1173
|
+
mediaSession: MediaSession,
|
|
1174
|
+
controller: MediaSession.ControllerInfo
|
|
1175
|
+
): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
|
|
1176
|
+
Timber.tag("APM").d("triggered onPlaybackResumption")
|
|
1177
|
+
try {
|
|
1178
|
+
this@MusicService.player
|
|
1179
|
+
emit(MusicEvents.PLAYBACK_RESUME, Bundle().apply {
|
|
1180
|
+
putString("package", controller.packageName)
|
|
1181
|
+
})
|
|
1182
|
+
} catch (e: Exception) {
|
|
1183
|
+
// player has not been initialized; forcefully trigger onStartCommand
|
|
1184
|
+
// TODO: emit event after the player is initialized?
|
|
1185
|
+
this@MusicService.onStartCommand(null, 0, 0)
|
|
1186
|
+
}
|
|
1187
|
+
return super.onPlaybackResumption(mediaSession, controller)
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
private fun getPendingIntentFlags(): Int {
|
|
1192
|
+
return PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
companion object {
|
|
1196
|
+
const val EMPTY_NOTIFICATION_ID = 1
|
|
1197
|
+
const val STATE_KEY = "state"
|
|
1198
|
+
const val ERROR_KEY = "error"
|
|
1199
|
+
const val EVENT_KEY = "event"
|
|
1200
|
+
const val DATA_KEY = "data"
|
|
1201
|
+
const val TRACK_KEY = "track"
|
|
1202
|
+
const val NEXT_TRACK_KEY = "nextTrack"
|
|
1203
|
+
const val POSITION_KEY = "position"
|
|
1204
|
+
const val DURATION_KEY = "duration"
|
|
1205
|
+
const val BUFFERED_POSITION_KEY = "buffered"
|
|
1206
|
+
|
|
1207
|
+
const val TASK_KEY = "TrackPlayer"
|
|
1208
|
+
|
|
1209
|
+
const val MIN_BUFFER_KEY = "minBuffer"
|
|
1210
|
+
const val MAX_BUFFER_KEY = "maxBuffer"
|
|
1211
|
+
const val PLAY_BUFFER_KEY = "playBuffer"
|
|
1212
|
+
const val BACK_BUFFER_KEY = "backBuffer"
|
|
1213
|
+
|
|
1214
|
+
const val FORWARD_JUMP_INTERVAL_KEY = "forwardJumpInterval"
|
|
1215
|
+
const val BACKWARD_JUMP_INTERVAL_KEY = "backwardJumpInterval"
|
|
1216
|
+
const val PROGRESS_UPDATE_EVENT_INTERVAL_KEY = "progressUpdateEventInterval"
|
|
1217
|
+
|
|
1218
|
+
const val MAX_CACHE_SIZE_KEY = "maxCacheSize"
|
|
1219
|
+
|
|
1220
|
+
const val ANDROID_OPTIONS_KEY = "android"
|
|
1221
|
+
|
|
1222
|
+
const val CUSTOM_ACTIONS_KEY = "customActions"
|
|
1223
|
+
const val CUSTOM_ACTIONS_LIST_KEY = "customActionsList"
|
|
1224
|
+
|
|
1225
|
+
const val STOPPING_APP_PAUSES_PLAYBACK_KEY = "stoppingAppPausesPlayback"
|
|
1226
|
+
const val APP_KILLED_PLAYBACK_BEHAVIOR_KEY = "appKilledPlaybackBehavior"
|
|
1227
|
+
const val AUDIO_OFFLOAD_KEY = "audioOffload"
|
|
1228
|
+
const val SHUFFLE_KEY = "shuffle"
|
|
1229
|
+
const val STOP_FOREGROUND_GRACE_PERIOD_KEY = "stopForegroundGracePeriod"
|
|
1230
|
+
const val PAUSE_ON_INTERRUPTION_KEY = "alwaysPauseOnInterruption"
|
|
1231
|
+
const val AUTO_UPDATE_METADATA = "autoUpdateMetadata"
|
|
1232
|
+
const val AUTO_HANDLE_INTERRUPTIONS = "autoHandleInterruptions"
|
|
1233
|
+
const val ANDROID_AUDIO_CONTENT_TYPE = "androidAudioContentType"
|
|
1234
|
+
const val IS_FOCUS_LOSS_PERMANENT_KEY = "permanent"
|
|
1235
|
+
const val IS_PAUSED_KEY = "paused"
|
|
1236
|
+
|
|
1237
|
+
const val PARSE_EMBEDDED_ARTWORK = "androidParseEmbeddedArtwork"
|
|
1238
|
+
const val HANDLE_NOISY = "androidHandleAudioBecomingNoisy"
|
|
1239
|
+
const val CROSSFADE = "crossfade"
|
|
1240
|
+
const val ALWAYS_SHOW_NEXT = "androidAlwaysShowNext"
|
|
1241
|
+
const val SKIP_SILENCE = "androidSkipSilence"
|
|
1242
|
+
const val WAKE_MODE = "androidWakeMode"
|
|
1243
|
+
|
|
1244
|
+
const val AA_FOR_YOU_KEY = "for-you"
|
|
1245
|
+
const val AA_ROOT_KEY = "/"
|
|
1246
|
+
|
|
1247
|
+
const val DEFAULT_JUMP_INTERVAL = 15.0
|
|
1248
|
+
const val DEFAULT_STOP_FOREGROUND_GRACE_PERIOD = 5
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|