react-native-queue-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 +201 -0
- package/QueuePlayer.podspec +114 -0
- package/README.md +194 -0
- package/android/CMakeLists.txt +41 -0
- package/android/build.gradle +257 -0
- package/android/src/androidTest/java/com/margelo/nitro/queueplayer/SignalsmithStretchNativeTest.kt +136 -0
- package/android/src/main/AndroidManifest.xml +173 -0
- package/android/src/main/cpp/CMakeLists.txt +221 -0
- package/android/src/main/cpp/THIRD_PARTY_LICENSES +128 -0
- package/android/src/main/cpp/THIRD_PARTY_VERSIONS.txt +32 -0
- package/android/src/main/cpp/airplay2_audio.c +537 -0
- package/android/src/main/cpp/airplay2_audio.h +66 -0
- package/android/src/main/cpp/airplay2_control.c +680 -0
- package/android/src/main/cpp/airplay2_control.h +101 -0
- package/android/src/main/cpp/airplay2_crypto.c +548 -0
- package/android/src/main/cpp/airplay2_crypto.h +213 -0
- package/android/src/main/cpp/airplay2_dmap.c +81 -0
- package/android/src/main/cpp/airplay2_dmap.h +53 -0
- package/android/src/main/cpp/airplay2_filelog.h +100 -0
- package/android/src/main/cpp/airplay2_jni.cpp +579 -0
- package/android/src/main/cpp/airplay2_ntp.c +203 -0
- package/android/src/main/cpp/airplay2_ntp.h +39 -0
- package/android/src/main/cpp/airplay2_pair.c +653 -0
- package/android/src/main/cpp/airplay2_pair.h +114 -0
- package/android/src/main/cpp/airplay2_plist.c +840 -0
- package/android/src/main/cpp/airplay2_plist.h +184 -0
- package/android/src/main/cpp/airplay2_ptp.c +509 -0
- package/android/src/main/cpp/airplay2_ptp.h +40 -0
- package/android/src/main/cpp/airplay2_rtsp.c +693 -0
- package/android/src/main/cpp/airplay2_rtsp.h +90 -0
- package/android/src/main/cpp/airplay2_session.c +196 -0
- package/android/src/main/cpp/airplay2_session.h +197 -0
- package/android/src/main/cpp/airplay2_tlv.c +135 -0
- package/android/src/main/cpp/airplay2_tlv.h +87 -0
- package/android/src/main/cpp/airplay_jni.cpp +589 -0
- package/android/src/main/cpp/build-openssl-android.sh +94 -0
- package/android/src/main/cpp/cpp-adapter.cpp +11 -0
- package/android/src/main/cpp/prebuilt/libsodium/arm64-v8a/lib/libsodium.a +0 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/core.h +28 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_aead_aegis128l.h +92 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_aead_aegis256.h +92 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_aead_aes256gcm.h +179 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_aead_chacha20poly1305.h +180 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_aead_xchacha20poly1305.h +100 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_auth.h +46 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_auth_hmacsha256.h +70 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_auth_hmacsha512.h +68 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_auth_hmacsha512256.h +65 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_box.h +177 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_box_curve25519xchacha20poly1305.h +164 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_box_curve25519xsalsa20poly1305.h +112 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_core_ed25519.h +104 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_core_hchacha20.h +36 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_core_hsalsa20.h +36 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_core_ristretto255.h +100 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_core_salsa20.h +36 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_core_salsa2012.h +36 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_core_salsa208.h +40 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_generichash.h +84 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_generichash_blake2b.h +122 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_hash.h +40 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_hash_sha256.h +60 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_hash_sha512.h +60 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_kdf.h +53 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_kdf_blake2b.h +44 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_kdf_hkdf_sha256.h +74 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_kdf_hkdf_sha512.h +75 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_kx.h +66 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_onetimeauth.h +65 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_onetimeauth_poly1305.h +72 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_pwhash.h +147 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_pwhash_argon2i.h +122 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_pwhash_argon2id.h +122 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_pwhash_scryptsalsa208sha256.h +120 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_scalarmult.h +46 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_scalarmult_curve25519.h +42 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_scalarmult_ed25519.h +51 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_scalarmult_ristretto255.h +43 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_secretbox.h +93 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_secretbox_xchacha20poly1305.h +70 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_secretbox_xsalsa20poly1305.h +69 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_secretstream_xchacha20poly1305.h +108 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_shorthash.h +41 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_shorthash_siphash24.h +50 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_sign.h +107 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_sign_ed25519.h +124 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_sign_edwards25519sha512batch.h +55 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_stream.h +59 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_stream_chacha20.h +106 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_stream_salsa20.h +61 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_stream_salsa2012.h +53 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_stream_salsa208.h +56 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_stream_xchacha20.h +61 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_stream_xsalsa20.h +61 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_verify_16.h +23 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_verify_32.h +23 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/crypto_verify_64.h +23 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/export.h +57 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/randombytes.h +72 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/randombytes_internal_random.h +22 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/randombytes_sysrandom.h +19 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/runtime.h +55 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/utils.h +179 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium/version.h +33 -0
- package/android/src/main/cpp/prebuilt/libsodium/include/sodium.h +75 -0
- package/android/src/main/cpp/prebuilt/libsodium/x86_64/lib/libsodium.a +0 -0
- package/android/src/main/cpp/prebuilt/openssl/arm64-v8a/lib/libcrypto.a +0 -0
- package/android/src/main/cpp/prebuilt/openssl/arm64-v8a/lib/libssl.a +0 -0
- package/android/src/main/cpp/prebuilt/openssl/x86_64/lib/libcrypto.a +0 -0
- package/android/src/main/cpp/prebuilt/openssl/x86_64/lib/libssl.a +0 -0
- package/android/src/main/cpp/stretch_jni.cpp +171 -0
- package/android/src/main/cpp/third_party/alac/addons/alac_wrapper.cpp +198 -0
- package/android/src/main/cpp/third_party/alac/addons/alac_wrapper.h +46 -0
- package/android/src/main/cpp/third_party/alac/codec/ALACAudioTypes.h +197 -0
- package/android/src/main/cpp/third_party/alac/codec/ALACBitUtilities.c +260 -0
- package/android/src/main/cpp/third_party/alac/codec/ALACBitUtilities.h +104 -0
- package/android/src/main/cpp/third_party/alac/codec/ALACDecoder.cpp +730 -0
- package/android/src/main/cpp/third_party/alac/codec/ALACDecoder.h +65 -0
- package/android/src/main/cpp/third_party/alac/codec/ALACEncoder.cpp +1425 -0
- package/android/src/main/cpp/third_party/alac/codec/ALACEncoder.h +92 -0
- package/android/src/main/cpp/third_party/alac/codec/EndianPortable.c +181 -0
- package/android/src/main/cpp/third_party/alac/codec/EndianPortable.h +59 -0
- package/android/src/main/cpp/third_party/alac/codec/ag_dec.c +362 -0
- package/android/src/main/cpp/third_party/alac/codec/ag_enc.c +370 -0
- package/android/src/main/cpp/third_party/alac/codec/aglib.h +81 -0
- package/android/src/main/cpp/third_party/alac/codec/dp_dec.c +381 -0
- package/android/src/main/cpp/third_party/alac/codec/dp_enc.c +386 -0
- package/android/src/main/cpp/third_party/alac/codec/dplib.h +61 -0
- package/android/src/main/cpp/third_party/alac/codec/matrix_dec.c +390 -0
- package/android/src/main/cpp/third_party/alac/codec/matrix_enc.c +342 -0
- package/android/src/main/cpp/third_party/alac/codec/matrixlib.h +80 -0
- package/android/src/main/cpp/third_party/crosstools/cross_log.c +95 -0
- package/android/src/main/cpp/third_party/crosstools/cross_log.h +25 -0
- package/android/src/main/cpp/third_party/crosstools/cross_net.c +1139 -0
- package/android/src/main/cpp/third_party/crosstools/cross_net.h +51 -0
- package/android/src/main/cpp/third_party/crosstools/cross_ssl.c +371 -0
- package/android/src/main/cpp/third_party/crosstools/cross_ssl.h +15 -0
- package/android/src/main/cpp/third_party/crosstools/cross_thread.c +134 -0
- package/android/src/main/cpp/third_party/crosstools/cross_thread.h +20 -0
- package/android/src/main/cpp/third_party/crosstools/cross_util.c +753 -0
- package/android/src/main/cpp/third_party/crosstools/cross_util.h +125 -0
- package/android/src/main/cpp/third_party/crosstools/platform.c +227 -0
- package/android/src/main/cpp/third_party/crosstools/platform.h +127 -0
- package/android/src/main/cpp/third_party/curve25519/include/curve25519_dh.h +53 -0
- package/android/src/main/cpp/third_party/curve25519/include/ed25519_signature.h +98 -0
- package/android/src/main/cpp/third_party/curve25519/include/external_calls.h +36 -0
- package/android/src/main/cpp/third_party/curve25519/source/BaseTypes.h +125 -0
- package/android/src/main/cpp/third_party/curve25519/source/base_folding8.h +1288 -0
- package/android/src/main/cpp/third_party/curve25519/source/curve25519_dh.c +208 -0
- package/android/src/main/cpp/third_party/curve25519/source/curve25519_mehdi.c +410 -0
- package/android/src/main/cpp/third_party/curve25519/source/curve25519_mehdi.h +175 -0
- package/android/src/main/cpp/third_party/curve25519/source/curve25519_order.c +155 -0
- package/android/src/main/cpp/third_party/curve25519/source/curve25519_utils.c +153 -0
- package/android/src/main/cpp/third_party/curve25519/source/custom_blind.c +27 -0
- package/android/src/main/cpp/third_party/curve25519/source/custom_blind.h +11 -0
- package/android/src/main/cpp/third_party/curve25519/source/ed25519_sign.c +419 -0
- package/android/src/main/cpp/third_party/curve25519/source/ed25519_signature.h +98 -0
- package/android/src/main/cpp/third_party/curve25519/source/ed25519_verify.c +313 -0
- package/android/src/main/cpp/third_party/curve25519/source/sha512.c +294 -0
- package/android/src/main/cpp/third_party/curve25519/source/sha512.h +93 -0
- package/android/src/main/cpp/third_party/dmap-parser/dmap_parser.c +545 -0
- package/android/src/main/cpp/third_party/dmap-parser/dmap_parser.h +90 -0
- package/android/src/main/cpp/third_party/libraop/aes.c +863 -0
- package/android/src/main/cpp/third_party/libraop/aes.h +18 -0
- package/android/src/main/cpp/third_party/libraop/aes_ctr.c +89 -0
- package/android/src/main/cpp/third_party/libraop/aes_ctr.h +59 -0
- package/android/src/main/cpp/third_party/libraop/alac.c +1135 -0
- package/android/src/main/cpp/third_party/libraop/alac.h +55 -0
- package/android/src/main/cpp/third_party/libraop/raop_client.c +1500 -0
- package/android/src/main/cpp/third_party/libraop/raop_client.h +178 -0
- package/android/src/main/cpp/third_party/libraop/rtsp_client.c +743 -0
- package/android/src/main/cpp/third_party/libraop/rtsp_client.h +50 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/__DECC_INCLUDE_EPILOGUE.H +22 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/__DECC_INCLUDE_PROLOGUE.H +26 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/aes.h +111 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/asn1.h +1134 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/asn1.h.in +967 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/asn1err.h +142 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/asn1t.h +946 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/asn1t.h.in +923 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/async.h +104 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/asyncerr.h +29 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/bio.h +1022 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/bio.h.in +999 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/bioerr.h +72 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/blowfish.h +78 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/bn.h +590 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/bnerr.h +47 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/buffer.h +62 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/buffererr.h +25 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/byteorder.h +339 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/camellia.h +117 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/cast.h +71 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/cmac.h +52 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/cmp.h +727 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/cmp.h.in +584 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/cmp_util.h +56 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/cmperr.h +134 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/cms.h +511 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/cms.h.in +413 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/cmserr.h +126 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/comp.h +98 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/comp.h.in +76 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/comperr.h +38 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/conf.h +214 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/conf.h.in +177 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/conf_api.h +46 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/conferr.h +52 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/configuration.h +212 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/configuration.h.in +75 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/conftypes.h +44 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/core.h +236 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/core_dispatch.h +1141 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/core_names.h +575 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/core_names.h.in +126 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/core_object.h +41 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/crmf.h +278 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/crmf.h.in +207 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/crmferr.h +57 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/crypto.h +583 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/crypto.h.in +560 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/cryptoerr.h +56 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/cryptoerr_legacy.h +1466 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ct.h +573 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ct.h.in +525 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/cterr.h +45 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/decoder.h +133 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/decodererr.h +28 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/des.h +211 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/dh.h +339 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/dherr.h +59 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/dsa.h +280 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/dsaerr.h +44 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/dtls1.h +57 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/e_os2.h +310 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/e_ostime.h +38 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ebcdic.h +39 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ec.h +1588 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ecdh.h +10 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ecdsa.h +10 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ecerr.h +104 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/encoder.h +124 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/encodererr.h +28 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/engine.h +833 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/engineerr.h +63 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/err.h +512 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/err.h.in +501 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ess.h +128 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ess.h.in +81 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/esserr.h +32 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/evp.h +2310 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/evperr.h +142 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/fips_names.h +50 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/fipskey.h +41 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/fipskey.h.in +40 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/hmac.h +62 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/hpke.h +169 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/http.h +119 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/httperr.h +56 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/idea.h +82 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/indicator.h +31 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/kdf.h +138 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/kdferr.h +16 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/lhash.h +398 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/lhash.h.in +373 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/macros.h +349 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/md2.h +56 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/md4.h +63 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/md5.h +62 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/mdc2.h +55 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ml_kem.h +31 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/modes.h +219 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/obj_mac.h +6636 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/objects.h +184 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/objectserr.h +28 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ocsp.h +483 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ocsp.h.in +387 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ocsperr.h +53 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/opensslconf.h +17 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/opensslv.h +114 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/opensslv.h.in +113 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ossl_typ.h +16 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/param_build.h +63 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/params.h +163 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/pem.h +547 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/pem2.h +19 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/pemerr.h +59 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/pkcs12.h +366 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/pkcs12.h.in +343 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/pkcs12err.h +46 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/pkcs7.h +430 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/pkcs7.h.in +359 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/pkcs7err.h +63 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/prov_ssl.h +38 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/proverr.h +169 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/provider.h +94 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/quic.h +75 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/rand.h +131 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/randerr.h +70 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/rc2.h +68 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/rc4.h +47 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/rc5.h +79 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ripemd.h +59 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/rsa.h +615 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/rsaerr.h +107 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/safestack.h +297 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/safestack.h.in +227 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/seed.h +113 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/self_test.h +112 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/sha.h +139 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/srp.h +285 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/srp.h.in +214 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/srtp.h +68 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ssl.h +2933 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ssl.h.in +2886 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ssl2.h +30 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ssl3.h +358 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/sslerr.h +382 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/sslerr_legacy.h +467 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/stack.h +90 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/store.h +377 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/storeerr.h +49 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/symhacks.h +39 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/thread.h +31 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/tls1.h +1220 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/trace.h +321 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ts.h +522 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/tserr.h +67 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/txt_db.h +63 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/types.h +248 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ui.h +407 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/ui.h.in +384 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/uierr.h +38 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/whrlpool.h +62 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/x509.h +1303 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/x509.h.in +1109 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/x509_acert.h +294 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/x509_acert.h.in +199 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/x509_vfy.h +903 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/x509_vfy.h.in +806 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/x509err.h +70 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/x509v3.h +1968 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/x509v3.h.in +1367 -0
- package/android/src/main/cpp/third_party/openssl/include/openssl/x509v3err.h +97 -0
- package/android/src/main/cpp/third_party/pair_ap/pair-internal.h +390 -0
- package/android/src/main/cpp/third_party/pair_ap/pair-tlv.c +221 -0
- package/android/src/main/cpp/third_party/pair_ap/pair-tlv.h +77 -0
- package/android/src/main/cpp/third_party/pair_ap/pair.c +806 -0
- package/android/src/main/cpp/third_party/pair_ap/pair.h +266 -0
- package/android/src/main/cpp/third_party/pair_ap/pair_fruit_stub.c +36 -0
- package/android/src/main/cpp/third_party/pair_ap/pair_homekit.c +3190 -0
- package/android/src/main/cpp/third_party/signalsmith/LICENSE-signalsmith-linear.txt +21 -0
- package/android/src/main/cpp/third_party/signalsmith/LICENSE-signalsmith-stretch.txt +21 -0
- package/android/src/main/cpp/third_party/signalsmith/THIRD_PARTY_VERSIONS.txt +9 -0
- package/android/src/main/cpp/third_party/signalsmith/include/signalsmith-linear/fft.h +1380 -0
- package/android/src/main/cpp/third_party/signalsmith/include/signalsmith-linear/linear.h +1075 -0
- package/android/src/main/cpp/third_party/signalsmith/include/signalsmith-linear/stft.h +643 -0
- package/android/src/main/cpp/third_party/signalsmith/include/signalsmith-stretch.h +1060 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +314 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/ArtworkContentProvider.kt +124 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/AudioFocusErrorMapping.kt +89 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/AudioPipelineRenderersFactory.kt +60 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/BrowseCallbackRegistry.kt +136 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/BrowseDataProvider.kt +128 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/BufferStateTranslator.kt +35 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/CacheMimeTypes.kt +79 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/CastManager.kt +629 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +1350 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeForwardingPlayer.kt +66 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/CustomPresetStore.kt +100 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +348 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerEngine.kt +222 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngine.kt +213 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerPresets.kt +45 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/FFTProcessorTee.kt +173 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/FifoCacheEvictor.kt +96 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +587 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +220 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/IEqualizerEngine.kt +97 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/ListenerRegistry.kt +59 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCache.kt +343 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +533 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaButtonDispatch.kt +88 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +281 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemMapper.kt +76 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MilestoneTracker.kt +66 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MimeCapturingDataSource.kt +64 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MultiplexingAudioBufferSink.kt +92 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/NotificationProvider.kt +37 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractor.kt +166 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/PendingBrowseRequests.kt +70 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/PendingIntentBuffer.kt +82 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChain.kt +69 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/PitchCorrection.kt +27 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaceholderArtwork.kt +65 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaceholderFallbackBitmapLoader.kt +75 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +336 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackErrorMapping.kt +175 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachine.kt +127 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +1716 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +755 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/ProgressEmissionGate.kt +70 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +131 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueuePlayerPackage.kt +22 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +113 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/RadixTwoFFT.kt +176 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/RemoteCommandDeduper.kt +81 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessor.kt +84 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainConfig.kt +34 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainData.kt +44 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainExtractor.kt +209 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainGain.kt +34 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/SearchResultCache.kt +72 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayer.kt +167 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/SignalsmithStretchAudioProcessor.kt +225 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/SignalsmithStretchNative.kt +93 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/SleepTimerCore.kt +125 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +4242 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/TransportStateTranslator.kt +70 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/UrlSchemes.kt +9 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/VisualizationGate.kt +35 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/Visualizer.kt +240 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/VisualizerTeeBuilder.kt +63 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/VoiceSearchExtrasParser.kt +103 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/AudioRouteState.kt +202 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/AuthRequiredException.kt +14 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackend.kt +41 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackends.kt +56 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastEventBridge.kt +122 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastMediaItem.kt +20 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSession.kt +193 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSinkRouter.kt +95 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastTransportRouter.kt +151 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/DiscoveryState.kt +53 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/MetadataAwareSession.kt +17 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/PlaybackStateRouter.kt +65 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/Receiver.kt +35 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2JNI.kt +129 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2MetadataSync.kt +189 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Session.kt +123 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Sink.kt +205 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackend.kt +585 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackendInitializer.kt +43 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayDiscovery.kt +511 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayDiscoveryLifecycleObserver.kt +57 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt +199 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayReceiverMapper.kt +42 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt +43 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlaySession.kt +101 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlaySink.kt +265 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayTxtParser.kt +203 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI.kt +106 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MulticastLockHolder.kt +75 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/CastMediaItemConverter.kt +67 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/CastVolumeForwardingPlayer.kt +156 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt +171 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastOptionsProvider.kt +51 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastReceiverMapper.kt +44 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastSession.kt +544 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/MediaInfoBuilder.kt +92 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/RemoteMediaCallbacks.kt +113 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServer.kt +160 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt +15 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaServerHandle.kt +44 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaTokenRegistry.kt +37 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MimeTypes.kt +27 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitor.kt +86 -0
- package/android/src/main/res/raw/rnqp_placeholder.jpg +0 -0
- package/android/src/main/res/values/cast_strings.xml +15 -0
- package/android/src/main/res/values/strings.xml +24 -0
- package/android/src/main/res/xml/automotive_app_desc.xml +15 -0
- package/android/src/test/java/androidx/media3/session/MediaSessionControllerRequestTestSeam.kt +27 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ArtworkContentProviderTest.kt +124 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/AudioFocusErrorMappingTest.kt +122 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/AudioPipelineRenderersFactoryTest.kt +57 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +264 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/BrowseCallbackRegistryTest.kt +104 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/BrowseDataProviderTest.kt +276 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/BufferStateTranslatorTest.kt +58 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CacheMimeTypesTest.kt +116 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +214 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineReplayGainTest.kt +180 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CustomPresetStoreTest.kt +128 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +223 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +274 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +214 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerPresetsTest.kt +89 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +185 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FifoCacheEvictorTest.kt +112 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ForwardingPlayerTestAccess.kt +21 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +65 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +154 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/HeadlessJsMediaServiceTest.kt +106 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheTest.kt +183 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterTest.kt +649 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaButtonDispatchTest.kt +192 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +389 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemMapperTest.kt +145 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/MilestoneTrackerTest.kt +81 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/MultiplexingAudioBufferSinkTest.kt +112 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/NotificationProviderTest.kt +86 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +106 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PendingBrowseRequestsTest.kt +82 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PendingIntentBufferTest.kt +192 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +147 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchCorrectionTest.kt +55 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaceholderFallbackBitmapLoaderTest.kt +187 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +134 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachineTest.kt +157 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceArtworkLoaderTest.kt +95 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackBrowseTest.kt +480 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +171 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +773 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCastBindTest.kt +100 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +315 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceSessionActivityTest.kt +121 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlayerCommandsForTest.kt +246 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ProgressEmissionGateTest.kt +113 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/QueueMutationArithmeticTest.kt +252 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +343 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/RadixTwoFFTTest.kt +109 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/RemoteCommandDeduperTest.kt +106 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessorTest.kt +141 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainConfigTest.kt +103 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainExtractorTest.kt +469 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainGainTest.kt +129 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/RobolectricServiceBindHelper.kt +83 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/SearchResultCacheTest.kt +128 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ServiceReadyReasonTest.kt +42 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +521 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/SleepTimerCoreTest.kt +125 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/SmokeTest.kt +27 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +305 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +179 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +497 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +209 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +417 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +473 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerPitchCorrectionTest.kt +61 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerPlaybackModeTest.kt +28 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +89 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +290 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +151 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReplayGainTest.kt +69 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +324 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +287 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSourceClassifierTest.kt +82 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +222 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TransportStateTranslatorTest.kt +287 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizationGateTest.kt +96 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +202 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/VoiceSearchExtrasParserTest.kt +211 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/CastSinkRouterTest.kt +120 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/FakeRemotePlayer.kt +62 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/PlaybackStateRouterTest.kt +43 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayDiscoveryFilterTest.kt +86 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayTxtParserClassificationTest.kt +34 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/chromecast/CastMediaItemConverterTest.kt +78 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/chromecast/CastVolumeForwardingPlayerTest.kt +176 -0
- package/app.plugin.js +466 -0
- package/ios/AVPlayerItemQueueItemId.swift +61 -0
- package/ios/AVQueueBuilder.swift +200 -0
- package/ios/Array+SafeSubscript.swift +6 -0
- package/ios/ArtworkLoader.swift +136 -0
- package/ios/ArtworkResolver.swift +87 -0
- package/ios/AudioCategoryMapping.swift +48 -0
- package/ios/AudioSession.swift +220 -0
- package/ios/AudioSessionError.swift +44 -0
- package/ios/AudioTapProvider.swift +647 -0
- package/ios/BiquadCoefficients.swift +80 -0
- package/ios/CarPlayBridge.swift +178 -0
- package/ios/CarPlayBrowseBuilder.swift +368 -0
- package/ios/CarPlayBrowseDataProvider.swift +162 -0
- package/ios/CarPlayCoordinator.swift +42 -0
- package/ios/CarPlaySceneDelegate.swift +199 -0
- package/ios/Cast/AirPlayRouteState.swift +281 -0
- package/ios/Cast/Chromecast/ChromecastDiscoveryAdapter.swift +27 -0
- package/ios/Cast/Chromecast/ChromecastReceiverMapper.swift +48 -0
- package/ios/Cast/Chromecast/ChromecastSession.swift +740 -0
- package/ios/Cast/Chromecast/ChromecastSessionAdapter.swift +114 -0
- package/ios/Cast/Chromecast/ChromecastSupport.swift +63 -0
- package/ios/Cast/Chromecast/MediaInfoBuilder.swift +104 -0
- package/ios/Cast/Chromecast/RemoteMediaObserver.swift +147 -0
- package/ios/Cast/Core/CastEventBridge.swift +210 -0
- package/ios/Cast/Core/CastMediaItem.swift +41 -0
- package/ios/Cast/Core/CastNowPlayingController.swift +310 -0
- package/ios/Cast/Core/CastReceiverState.swift +22 -0
- package/ios/Cast/Core/CastSession.swift +147 -0
- package/ios/Cast/Core/CastTransportRouter.swift +162 -0
- package/ios/Cast/Core/ChromecastBackend.swift +259 -0
- package/ios/Cast/Core/LocalAddressMonitor.swift +110 -0
- package/ios/Cast/Core/LocalMediaServer.swift +270 -0
- package/ios/Cast/Core/LocalNetworkPermissionProbe.swift +265 -0
- package/ios/Cast/Core/MediaHTTPConnection.swift +349 -0
- package/ios/Cast/Core/MediaServerHandle.swift +49 -0
- package/ios/Cast/Core/MediaTokenRegistry.swift +51 -0
- package/ios/Cast/Core/MimeTypes.swift +26 -0
- package/ios/Cast/Core/PlaybackStateRouter.swift +123 -0
- package/ios/CastManager.swift +493 -0
- package/ios/CrossfadeEngine.swift +1343 -0
- package/ios/CustomPresetStore.swift +78 -0
- package/ios/EQProcessor.swift +321 -0
- package/ios/EQTap.swift +241 -0
- package/ios/Equalizer.swift +321 -0
- package/ios/EqualizerPresets.swift +48 -0
- package/ios/FFTProcessor.swift +237 -0
- package/ios/GaplessEngine.swift +385 -0
- package/ios/InputGuards.swift +63 -0
- package/ios/InterruptionEventMapping.swift +51 -0
- package/ios/ListenerRegistry.swift +73 -0
- package/ios/LookaheadCache.swift +833 -0
- package/ios/LookaheadCacheEviction.swift +83 -0
- package/ios/LookaheadCachePrefetcher.swift +503 -0
- package/ios/MetadataReader.swift +555 -0
- package/ios/MilestoneTracker.swift +63 -0
- package/ios/NowPlayingFormatExtractor.swift +362 -0
- package/ios/NowPlayingInfo.swift +190 -0
- package/ios/OutputRouteMonitor.swift +125 -0
- package/ios/PendingBrowseRequests.swift +62 -0
- package/ios/PitchAlgorithm.swift +24 -0
- package/ios/PlaceholderArtwork.swift +46 -0
- package/ios/PlaybackEngine.swift +319 -0
- package/ios/PlaybackErrorMapping.swift +195 -0
- package/ios/PlaybackModeStateMachine.swift +159 -0
- package/ios/PlayerStateDerivation.swift +45 -0
- package/ios/ProgressEmissionGate.swift +54 -0
- package/ios/QueueMutationArithmetic.swift +129 -0
- package/ios/QueueSkipArithmetic.swift +109 -0
- package/ios/QueueState.swift +49 -0
- package/ios/RemoteCommands.swift +229 -0
- package/ios/ReplayGainData.swift +58 -0
- package/ios/ReplayGainExtractor.swift +82 -0
- package/ios/ReplayGainProcessor.swift +46 -0
- package/ios/Resources/RNQP-placeholder.jpg +0 -0
- package/ios/Resources/silence.m4a +0 -0
- package/ios/Siri/PendingIntentBuffer.swift +100 -0
- package/ios/Siri/PlayMediaIntentHandler.swift +283 -0
- package/ios/Siri/QueuePlayerMediaIntents.swift +45 -0
- package/ios/Siri/VoiceDonation.swift +50 -0
- package/ios/SleepTimerCore.swift +128 -0
- package/ios/StreamingBitrateProbe.swift +318 -0
- package/ios/Tests/AVQueueBuilderTests.swift +430 -0
- package/ios/Tests/ArtworkResolverTests.swift +540 -0
- package/ios/Tests/AudioSessionTests.swift +222 -0
- package/ios/Tests/AudioTapProviderReplayGainTests.swift +136 -0
- package/ios/Tests/AudioTapProviderVisualizationFlagTests.swift +104 -0
- package/ios/Tests/BiquadCoefficientsTests.swift +84 -0
- package/ios/Tests/CarPlayBridgeTests.swift +91 -0
- package/ios/Tests/CarPlayBrowseBuilderTests.swift +221 -0
- package/ios/Tests/CarPlayBrowseDataProviderTests.swift +218 -0
- package/ios/Tests/CastNowPlayingControllerTests.swift +204 -0
- package/ios/Tests/CrossfadeEngineStubTests.swift +43 -0
- package/ios/Tests/EQProcessorTests.swift +302 -0
- package/ios/Tests/EQTapTests.swift +49 -0
- package/ios/Tests/EqualizerAudioMixProviderTests.swift +110 -0
- package/ios/Tests/EqualizerHybridTests.swift +303 -0
- package/ios/Tests/EqualizerPresetsTests.swift +69 -0
- package/ios/Tests/FFTProcessorTests.swift +303 -0
- package/ios/Tests/Fixtures/silence-aac-128.m4a +0 -0
- package/ios/Tests/Fixtures/silence-cbr-128.mp3 +0 -0
- package/ios/Tests/Fixtures/silence-vbr.mp3 +0 -0
- package/ios/Tests/GaplessEngineLifecycleTests.swift +160 -0
- package/ios/Tests/InputGuardsTests.swift +75 -0
- package/ios/Tests/InterruptionEventMappingTests.swift +91 -0
- package/ios/Tests/LookaheadCacheEvictionTests.swift +217 -0
- package/ios/Tests/LookaheadCachePrefetcherTests.swift +630 -0
- package/ios/Tests/LookaheadCacheRuntimeConfigTests.swift +247 -0
- package/ios/Tests/LookaheadCacheTests.swift +717 -0
- package/ios/Tests/MediaHTTPConnectionTests.swift +163 -0
- package/ios/Tests/MetadataReaderTests.swift +605 -0
- package/ios/Tests/MilestoneTrackerTests.swift +76 -0
- package/ios/Tests/NowPlayingFormatExtractorTests.swift +158 -0
- package/ios/Tests/NowPlayingInfoTests.swift +292 -0
- package/ios/Tests/PendingBrowseRequestsTests.swift +69 -0
- package/ios/Tests/PendingIntentBufferTests.swift +164 -0
- package/ios/Tests/PlaybackEngineProtocolTests.swift +92 -0
- package/ios/Tests/PlaybackErrorMappingTests.swift +179 -0
- package/ios/Tests/PlaybackModeStateMachineTests.swift +40 -0
- package/ios/Tests/PlaybackStateRouterTests.swift +178 -0
- package/ios/Tests/PlayerStateDerivationTests.swift +69 -0
- package/ios/Tests/ProgressEmissionGateTests.swift +113 -0
- package/ios/Tests/QueueMutationArithmeticTests.swift +245 -0
- package/ios/Tests/RemoteCommandsTests.swift +147 -0
- package/ios/Tests/ReplayGainExtractorTests.swift +197 -0
- package/ios/Tests/ReplayGainProcessorTests.swift +90 -0
- package/ios/Tests/SkipCapabilityTests.swift +98 -0
- package/ios/Tests/SkipIndexTests.swift +259 -0
- package/ios/Tests/SleepTimerCoreTests.swift +98 -0
- package/ios/Tests/StreamingBitrateProbeTests.swift +29 -0
- package/ios/Tests/TrackSourceClassifierTests.swift +50 -0
- package/ios/Tests/VoiceDonationTests.swift +26 -0
- package/ios/TrackPlayer.swift +4766 -0
- package/ios/Visualizer.swift +274 -0
- package/ios/WindowApplicationSceneDelegate.swift +59 -0
- package/ios/tests-harness/Podfile +56 -0
- package/ios/tests-harness/TestHost/App.swift +12 -0
- package/ios/tests-harness/TestHost/Info.plist +37 -0
- package/ios/tests-harness/TestHost.xcodeproj/project.pbxproj +472 -0
- package/ios/tests-harness/TestHost.xcodeproj/xcshareddata/xcschemes/TestHost.xcscheme +68 -0
- package/ios/tests-harness/package.json +10 -0
- package/ios/tests-harness/scripts/seed-xcodeproj.rb +118 -0
- package/lib/module/CastManager.nitro.js +4 -0
- package/lib/module/CastManager.nitro.js.map +1 -0
- package/lib/module/Equalizer.nitro.js +4 -0
- package/lib/module/Equalizer.nitro.js.map +1 -0
- package/lib/module/TrackPlayer.nitro.js +4 -0
- package/lib/module/TrackPlayer.nitro.js.map +1 -0
- package/lib/module/Visualizer.nitro.js +4 -0
- package/lib/module/Visualizer.nitro.js.map +1 -0
- package/lib/module/cast/index.js +156 -0
- package/lib/module/cast/index.js.map +1 -0
- package/lib/module/clients.js +43 -0
- package/lib/module/clients.js.map +1 -0
- package/lib/module/equalizer.js +85 -0
- package/lib/module/equalizer.js.map +1 -0
- package/lib/module/hooks/index.js +16 -0
- package/lib/module/hooks/index.js.map +1 -0
- package/lib/module/hooks/useActiveTrack.js +50 -0
- package/lib/module/hooks/useActiveTrack.js.map +1 -0
- package/lib/module/hooks/useAudioRoute.js +60 -0
- package/lib/module/hooks/useAudioRoute.js.map +1 -0
- package/lib/module/hooks/useBufferState.js +34 -0
- package/lib/module/hooks/useBufferState.js.map +1 -0
- package/lib/module/hooks/useCanSkip.js +67 -0
- package/lib/module/hooks/useCanSkip.js.map +1 -0
- package/lib/module/hooks/useCast.js +107 -0
- package/lib/module/hooks/useCast.js.map +1 -0
- package/lib/module/hooks/useEqualizer.js +91 -0
- package/lib/module/hooks/useEqualizer.js.map +1 -0
- package/lib/module/hooks/useFullyBuffered.js +33 -0
- package/lib/module/hooks/useFullyBuffered.js.map +1 -0
- package/lib/module/hooks/useLookaheadCache.js +49 -0
- package/lib/module/hooks/useLookaheadCache.js.map +1 -0
- package/lib/module/hooks/useNowPlayingFormat.js +42 -0
- package/lib/module/hooks/useNowPlayingFormat.js.map +1 -0
- package/lib/module/hooks/usePlaybackState.js +36 -0
- package/lib/module/hooks/usePlaybackState.js.map +1 -0
- package/lib/module/hooks/useProgress.js +31 -0
- package/lib/module/hooks/useProgress.js.map +1 -0
- package/lib/module/hooks/useSleepTimer.js +65 -0
- package/lib/module/hooks/useSleepTimer.js.map +1 -0
- package/lib/module/hooks/useVisualizerState.js +99 -0
- package/lib/module/hooks/useVisualizerState.js.map +1 -0
- package/lib/module/index.js +37 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/playbackService.js +174 -0
- package/lib/module/playbackService.js.map +1 -0
- package/lib/module/types.js +592 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/CastManager.nitro.d.ts +154 -0
- package/lib/typescript/CastManager.nitro.d.ts.map +1 -0
- package/lib/typescript/Equalizer.nitro.d.ts +30 -0
- package/lib/typescript/Equalizer.nitro.d.ts.map +1 -0
- package/lib/typescript/TrackPlayer.nitro.d.ts +584 -0
- package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -0
- package/lib/typescript/Visualizer.nitro.d.ts +36 -0
- package/lib/typescript/Visualizer.nitro.d.ts.map +1 -0
- package/lib/typescript/cast/index.d.ts +65 -0
- package/lib/typescript/cast/index.d.ts.map +1 -0
- package/lib/typescript/clients.d.ts +9 -0
- package/lib/typescript/clients.d.ts.map +1 -0
- package/lib/typescript/equalizer.d.ts +45 -0
- package/lib/typescript/equalizer.d.ts.map +1 -0
- package/lib/typescript/hooks/index.d.ts +22 -0
- package/lib/typescript/hooks/index.d.ts.map +1 -0
- package/lib/typescript/hooks/useActiveTrack.d.ts +21 -0
- package/lib/typescript/hooks/useActiveTrack.d.ts.map +1 -0
- package/lib/typescript/hooks/useAudioRoute.d.ts +13 -0
- package/lib/typescript/hooks/useAudioRoute.d.ts.map +1 -0
- package/lib/typescript/hooks/useBufferState.d.ts +18 -0
- package/lib/typescript/hooks/useBufferState.d.ts.map +1 -0
- package/lib/typescript/hooks/useCanSkip.d.ts +43 -0
- package/lib/typescript/hooks/useCanSkip.d.ts.map +1 -0
- package/lib/typescript/hooks/useCast.d.ts +32 -0
- package/lib/typescript/hooks/useCast.d.ts.map +1 -0
- package/lib/typescript/hooks/useEqualizer.d.ts +20 -0
- package/lib/typescript/hooks/useEqualizer.d.ts.map +1 -0
- package/lib/typescript/hooks/useFullyBuffered.d.ts +15 -0
- package/lib/typescript/hooks/useFullyBuffered.d.ts.map +1 -0
- package/lib/typescript/hooks/useLookaheadCache.d.ts +13 -0
- package/lib/typescript/hooks/useLookaheadCache.d.ts.map +1 -0
- package/lib/typescript/hooks/useNowPlayingFormat.d.ts +18 -0
- package/lib/typescript/hooks/useNowPlayingFormat.d.ts.map +1 -0
- package/lib/typescript/hooks/usePlaybackState.d.ts +9 -0
- package/lib/typescript/hooks/usePlaybackState.d.ts.map +1 -0
- package/lib/typescript/hooks/useProgress.d.ts +15 -0
- package/lib/typescript/hooks/useProgress.d.ts.map +1 -0
- package/lib/typescript/hooks/useSleepTimer.d.ts +26 -0
- package/lib/typescript/hooks/useSleepTimer.d.ts.map +1 -0
- package/lib/typescript/hooks/useVisualizerState.d.ts +40 -0
- package/lib/typescript/hooks/useVisualizerState.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +14 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/playbackService.d.ts +140 -0
- package/lib/typescript/playbackService.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +1215 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/nitro.json +53 -0
- package/nitrogen/generated/android/c++/JAudioCategoryOptions.hpp +65 -0
- package/nitrogen/generated/android/c++/JAudioCodec.hpp +73 -0
- package/nitrogen/generated/android/c++/JAudioContainer.hpp +76 -0
- package/nitrogen/generated/android/c++/JAudioContentType.hpp +58 -0
- package/nitrogen/generated/android/c++/JAudioRoute.hpp +75 -0
- package/nitrogen/generated/android/c++/JAudioRouteKind.hpp +91 -0
- package/nitrogen/generated/android/c++/JBrowseItem.hpp +78 -0
- package/nitrogen/generated/android/c++/JBrowseSection.hpp +97 -0
- package/nitrogen/generated/android/c++/JBrowseSnapshot.hpp +87 -0
- package/nitrogen/generated/android/c++/JBufferState.hpp +64 -0
- package/nitrogen/generated/android/c++/JCacheStatus.hpp +92 -0
- package/nitrogen/generated/android/c++/JCastConfigureOptions.hpp +62 -0
- package/nitrogen/generated/android/c++/JCastConnectOptions.hpp +57 -0
- package/nitrogen/generated/android/c++/JCastDeviceKind.hpp +70 -0
- package/nitrogen/generated/android/c++/JCastDiscoveryState.hpp +57 -0
- package/nitrogen/generated/android/c++/JCastLocalNetworkPermission.hpp +61 -0
- package/nitrogen/generated/android/c++/JCastLocalNetworkPermissionEvent.hpp +58 -0
- package/nitrogen/generated/android/c++/JCastProtocol.hpp +67 -0
- package/nitrogen/generated/android/c++/JCastReceiver.hpp +89 -0
- package/nitrogen/generated/android/c++/JCastReceiverCapabilities.hpp +66 -0
- package/nitrogen/generated/android/c++/JCastRoute.hpp +85 -0
- package/nitrogen/generated/android/c++/JCastSessionDiedEvent.hpp +63 -0
- package/nitrogen/generated/android/c++/JCastSessionEndReason.hpp +76 -0
- package/nitrogen/generated/android/c++/JCastStreamingModel.hpp +58 -0
- package/nitrogen/generated/android/c++/JEqualizerBand.hpp +61 -0
- package/nitrogen/generated/android/c++/JEqualizerPreset.hpp +76 -0
- package/nitrogen/generated/android/c++/JEvictionPolicy.hpp +58 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_BrowseItem______std__string.hpp +146 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______MediaSearchRequest.hpp +161 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______std__string.hpp +147 -0
- package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_AudioRoute.hpp +80 -0
- package/nitrogen/generated/android/c++/JFunc_void_BufferState.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_CacheStatus.hpp +79 -0
- package/nitrogen/generated/android/c++/JFunc_void_CastDiscoveryState.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_CastLocalNetworkPermissionEvent.hpp +79 -0
- package/nitrogen/generated/android/c++/JFunc_void_CastRoute.hpp +86 -0
- package/nitrogen/generated/android/c++/JFunc_void_CastSessionDiedEvent.hpp +80 -0
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackError.hpp +80 -0
- package/nitrogen/generated/android/c++/JFunc_void_PlayerProgress.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_PlayerState_StateChangeReason.hpp +79 -0
- package/nitrogen/generated/android/c++/JFunc_void_ServiceReadyReason.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_SkipCapability.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_SleepTimerState.hpp +78 -0
- package/nitrogen/generated/android/c++/JFunc_void_VisualizerErrorReason.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_VisualizerFrame.hpp +80 -0
- package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_double_double.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.hpp +82 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.hpp +93 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_CastReceiver_.hpp +105 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_EqualizerBand_.hpp +96 -0
- package/nitrogen/generated/android/c++/JHybridCastManagerSpec.cpp +388 -0
- package/nitrogen/generated/android/c++/JHybridCastManagerSpec.hpp +84 -0
- package/nitrogen/generated/android/c++/JHybridEqualizerSpec.cpp +233 -0
- package/nitrogen/generated/android/c++/JHybridEqualizerSpec.hpp +74 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +1052 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +130 -0
- package/nitrogen/generated/android/c++/JHybridVisualizerSpec.cpp +84 -0
- package/nitrogen/generated/android/c++/JHybridVisualizerSpec.hpp +64 -0
- package/nitrogen/generated/android/c++/JLookaheadCacheConfig.hpp +61 -0
- package/nitrogen/generated/android/c++/JMediaSearchOrigin.hpp +58 -0
- package/nitrogen/generated/android/c++/JMediaSearchQueueLocation.hpp +61 -0
- package/nitrogen/generated/android/c++/JMediaSearchReference.hpp +58 -0
- package/nitrogen/generated/android/c++/JMediaSearchReleaseDateRange.hpp +61 -0
- package/nitrogen/generated/android/c++/JMediaSearchRepeatMode.hpp +61 -0
- package/nitrogen/generated/android/c++/JMediaSearchRequest.hpp +126 -0
- package/nitrogen/generated/android/c++/JMediaSearchType.hpp +67 -0
- package/nitrogen/generated/android/c++/JNowPlayingFormat.hpp +123 -0
- package/nitrogen/generated/android/c++/JNowPlayingFormatAndroidExtras.hpp +71 -0
- package/nitrogen/generated/android/c++/JNowPlayingFormatIosExtras.hpp +61 -0
- package/nitrogen/generated/android/c++/JPitchCorrectionMode.hpp +61 -0
- package/nitrogen/generated/android/c++/JPlaybackError.hpp +87 -0
- package/nitrogen/generated/android/c++/JPlaybackErrorCode.hpp +88 -0
- package/nitrogen/generated/android/c++/JPlaybackMode.hpp +63 -0
- package/nitrogen/generated/android/c++/JPlaybackModeKind.hpp +58 -0
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +126 -0
- package/nitrogen/generated/android/c++/JPlayerProgress.hpp +65 -0
- package/nitrogen/generated/android/c++/JPlayerState.hpp +73 -0
- package/nitrogen/generated/android/c++/JRemoteControlOptions.hpp +66 -0
- package/nitrogen/generated/android/c++/JRemoteSkipMode.hpp +58 -0
- package/nitrogen/generated/android/c++/JRepeatMode.hpp +61 -0
- package/nitrogen/generated/android/c++/JReplayGainMode.hpp +61 -0
- package/nitrogen/generated/android/c++/JSectionIcon.hpp +91 -0
- package/nitrogen/generated/android/c++/JServiceReadyReason.hpp +58 -0
- package/nitrogen/generated/android/c++/JShuffleResult.hpp +88 -0
- package/nitrogen/generated/android/c++/JSkipCapability.hpp +61 -0
- package/nitrogen/generated/android/c++/JSleepTimerState.hpp +69 -0
- package/nitrogen/generated/android/c++/JStateChangeReason.hpp +73 -0
- package/nitrogen/generated/android/c++/JTrackChangeReason.hpp +73 -0
- package/nitrogen/generated/android/c++/JTrackItem.hpp +100 -0
- package/nitrogen/generated/android/c++/JTrackSource.hpp +61 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_Double.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_Double.hpp +69 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_NowPlayingFormat.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_NowPlayingFormat.hpp +83 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_String.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_String.hpp +70 -0
- package/nitrogen/generated/android/c++/JVisualizerConfig.hpp +65 -0
- package/nitrogen/generated/android/c++/JVisualizerErrorReason.hpp +64 -0
- package/nitrogen/generated/android/c++/JVisualizerFrame.hpp +67 -0
- package/nitrogen/generated/android/c++/JVoiceVocabulary.hpp +98 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/AudioCategoryOptions.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/AudioCodec.kt +28 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/AudioContainer.kt +29 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/AudioContentType.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/AudioRoute.kt +71 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/AudioRouteKind.kt +34 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/BrowseItem.kt +76 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/BrowseSection.kt +71 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/BrowseSnapshot.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/BufferState.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/CacheStatus.kt +71 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/CastConfigureOptions.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/CastConnectOptions.kt +51 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/CastDeviceKind.kt +27 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/CastDiscoveryState.kt +51 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/CastLocalNetworkPermission.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/CastLocalNetworkPermissionEvent.kt +51 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/CastProtocol.kt +26 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/CastReceiver.kt +81 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/CastReceiverCapabilities.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/CastRoute.kt +76 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/CastSessionDiedEvent.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/CastSessionEndReason.kt +29 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/CastStreamingModel.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/EqualizerBand.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/EqualizerPreset.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/EvictionPolicy.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_BrowseItem______std__string.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______MediaSearchRequest.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______std__string.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_AudioRoute.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_BufferState.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CacheStatus.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastDiscoveryState.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastLocalNetworkPermissionEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastRoute.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastSessionDiedEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlaybackError.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerProgress.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerState_StateChangeReason.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_ServiceReadyReason.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SkipCapability.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SleepTimerState.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerErrorReason.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerFrame.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_bool.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_double_double.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_CastReceiver_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_EqualizerBand_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridCastManagerSpec.kt +169 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridEqualizerSpec.kt +109 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +419 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridVisualizerSpec.kt +63 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/LookaheadCacheConfig.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/MediaSearchOrigin.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/MediaSearchQueueLocation.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/MediaSearchReference.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/MediaSearchReleaseDateRange.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/MediaSearchRepeatMode.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/MediaSearchRequest.kt +121 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/MediaSearchType.kt +26 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/NowPlayingFormat.kt +116 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/NowPlayingFormatAndroidExtras.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/NowPlayingFormatIosExtras.kt +51 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PitchCorrectionMode.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlaybackError.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlaybackErrorCode.kt +33 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlaybackMode.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlaybackModeKind.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +111 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerProgress.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerState.kt +28 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/RemoteControlOptions.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/RemoteSkipMode.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/RepeatMode.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/ReplayGainMode.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/SectionIcon.kt +34 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/ServiceReadyReason.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/ShuffleResult.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/SkipCapability.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/SleepTimerState.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/StateChangeReason.kt +28 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackChangeReason.kt +28 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackItem.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackSource.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Variant_NullType_Double.kt +62 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Variant_NullType_NowPlayingFormat.kt +62 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Variant_NullType_String.kt +62 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/VisualizerConfig.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/VisualizerErrorReason.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/VisualizerFrame.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/VoiceVocabulary.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/queueplayerOnLoad.kt +35 -0
- package/nitrogen/generated/android/queueplayer+autolinking.cmake +90 -0
- package/nitrogen/generated/android/queueplayer+autolinking.gradle +27 -0
- package/nitrogen/generated/android/queueplayerOnLoad.cpp +152 -0
- package/nitrogen/generated/android/queueplayerOnLoad.hpp +34 -0
- package/nitrogen/generated/ios/QueuePlayer+autolinking.rb +62 -0
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +343 -0
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +1831 -0
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +256 -0
- package/nitrogen/generated/ios/QueuePlayerAutolinking.mm +57 -0
- package/nitrogen/generated/ios/QueuePlayerAutolinking.swift +62 -0
- package/nitrogen/generated/ios/c++/HybridCastManagerSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridCastManagerSpecSwift.hpp +298 -0
- package/nitrogen/generated/ios/c++/HybridEqualizerSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridEqualizerSpecSwift.hpp +178 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +749 -0
- package/nitrogen/generated/ios/c++/HybridVisualizerSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridVisualizerSpecSwift.hpp +103 -0
- package/nitrogen/generated/ios/swift/AudioCategoryOptions.swift +78 -0
- package/nitrogen/generated/ios/swift/AudioCodec.swift +60 -0
- package/nitrogen/generated/ios/swift/AudioContainer.swift +64 -0
- package/nitrogen/generated/ios/swift/AudioContentType.swift +40 -0
- package/nitrogen/generated/ios/swift/AudioRoute.swift +49 -0
- package/nitrogen/generated/ios/swift/AudioRouteKind.swift +84 -0
- package/nitrogen/generated/ios/swift/BrowseItem.swift +93 -0
- package/nitrogen/generated/ios/swift/BrowseSection.swift +74 -0
- package/nitrogen/generated/ios/swift/BrowseSnapshot.swift +40 -0
- package/nitrogen/generated/ios/swift/BufferState.swift +48 -0
- package/nitrogen/generated/ios/swift/CacheStatus.swift +55 -0
- package/nitrogen/generated/ios/swift/CastConfigureOptions.swift +47 -0
- package/nitrogen/generated/ios/swift/CastConnectOptions.swift +29 -0
- package/nitrogen/generated/ios/swift/CastDeviceKind.swift +56 -0
- package/nitrogen/generated/ios/swift/CastDiscoveryState.swift +29 -0
- package/nitrogen/generated/ios/swift/CastLocalNetworkPermission.swift +44 -0
- package/nitrogen/generated/ios/swift/CastLocalNetworkPermissionEvent.swift +29 -0
- package/nitrogen/generated/ios/swift/CastProtocol.swift +52 -0
- package/nitrogen/generated/ios/swift/CastReceiver.swift +59 -0
- package/nitrogen/generated/ios/swift/CastReceiverCapabilities.swift +39 -0
- package/nitrogen/generated/ios/swift/CastRoute.swift +54 -0
- package/nitrogen/generated/ios/swift/CastSessionDiedEvent.swift +34 -0
- package/nitrogen/generated/ios/swift/CastSessionEndReason.swift +64 -0
- package/nitrogen/generated/ios/swift/CastStreamingModel.swift +40 -0
- package/nitrogen/generated/ios/swift/EqualizerBand.swift +34 -0
- package/nitrogen/generated/ios/swift/EqualizerPreset.swift +45 -0
- package/nitrogen/generated/ios/swift/EvictionPolicy.swift +40 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_BrowseItem______std__string.swift +67 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______MediaSearchRequest.swift +67 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______std__string.swift +67 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_AudioRoute.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_BufferState.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_CacheStatus.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_CastDiscoveryState.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_CastLocalNetworkPermission.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_CastLocalNetworkPermissionEvent.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_CastRoute.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_CastSessionDiedEvent.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_PlaybackError.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_PlayerProgress.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_PlayerState_StateChangeReason.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_ServiceReadyReason.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_ShuffleResult.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_SkipCapability.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_SleepTimerState.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_VisualizerErrorReason.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_VisualizerFrame.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_double_double.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.swift +53 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.swift +65 -0
- package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_std__vector_BrowseItem___.swift +66 -0
- package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_std__vector_TrackItem___.swift +66 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_BrowseItem_.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_CastReceiver_.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_EqualizerBand_.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem_.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridCastManagerSpec.swift +76 -0
- package/nitrogen/generated/ios/swift/HybridCastManagerSpec_cxx.swift +519 -0
- package/nitrogen/generated/ios/swift/HybridEqualizerSpec.swift +66 -0
- package/nitrogen/generated/ios/swift/HybridEqualizerSpec_cxx.swift +353 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +122 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +1430 -0
- package/nitrogen/generated/ios/swift/HybridVisualizerSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridVisualizerSpec_cxx.swift +163 -0
- package/nitrogen/generated/ios/swift/LookaheadCacheConfig.swift +34 -0
- package/nitrogen/generated/ios/swift/MediaSearchOrigin.swift +40 -0
- package/nitrogen/generated/ios/swift/MediaSearchQueueLocation.swift +44 -0
- package/nitrogen/generated/ios/swift/MediaSearchReference.swift +40 -0
- package/nitrogen/generated/ios/swift/MediaSearchReleaseDateRange.swift +60 -0
- package/nitrogen/generated/ios/swift/MediaSearchRepeatMode.swift +44 -0
- package/nitrogen/generated/ios/swift/MediaSearchRequest.swift +239 -0
- package/nitrogen/generated/ios/swift/MediaSearchType.swift +52 -0
- package/nitrogen/generated/ios/swift/NowPlayingFormat.swift +394 -0
- package/nitrogen/generated/ios/swift/NowPlayingFormatAndroidExtras.swift +135 -0
- package/nitrogen/generated/ios/swift/NowPlayingFormatIosExtras.swift +61 -0
- package/nitrogen/generated/ios/swift/PitchCorrectionMode.swift +44 -0
- package/nitrogen/generated/ios/swift/PlaybackError.swift +64 -0
- package/nitrogen/generated/ios/swift/PlaybackErrorCode.swift +80 -0
- package/nitrogen/generated/ios/swift/PlaybackMode.swift +47 -0
- package/nitrogen/generated/ios/swift/PlaybackModeKind.swift +40 -0
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +251 -0
- package/nitrogen/generated/ios/swift/PlayerProgress.swift +39 -0
- package/nitrogen/generated/ios/swift/PlayerState.swift +60 -0
- package/nitrogen/generated/ios/swift/RemoteControlOptions.swift +39 -0
- package/nitrogen/generated/ios/swift/RemoteSkipMode.swift +40 -0
- package/nitrogen/generated/ios/swift/RepeatMode.swift +44 -0
- package/nitrogen/generated/ios/swift/ReplayGainMode.swift +44 -0
- package/nitrogen/generated/ios/swift/SectionIcon.swift +84 -0
- package/nitrogen/generated/ios/swift/ServiceReadyReason.swift +40 -0
- package/nitrogen/generated/ios/swift/ShuffleResult.swift +51 -0
- package/nitrogen/generated/ios/swift/SkipCapability.swift +34 -0
- package/nitrogen/generated/ios/swift/SleepTimerState.swift +70 -0
- package/nitrogen/generated/ios/swift/StateChangeReason.swift +60 -0
- package/nitrogen/generated/ios/swift/TrackChangeReason.swift +60 -0
- package/nitrogen/generated/ios/swift/TrackItem.swift +169 -0
- package/nitrogen/generated/ios/swift/TrackSource.swift +44 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_Double.swift +30 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_NowPlayingFormat.swift +30 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_String.swift +30 -0
- package/nitrogen/generated/ios/swift/VisualizerConfig.swift +52 -0
- package/nitrogen/generated/ios/swift/VisualizerErrorReason.swift +48 -0
- package/nitrogen/generated/ios/swift/VisualizerFrame.swift +52 -0
- package/nitrogen/generated/ios/swift/VoiceVocabulary.swift +46 -0
- package/nitrogen/generated/shared/c++/AudioCategoryOptions.hpp +91 -0
- package/nitrogen/generated/shared/c++/AudioCodec.hpp +96 -0
- package/nitrogen/generated/shared/c++/AudioContainer.hpp +100 -0
- package/nitrogen/generated/shared/c++/AudioContentType.hpp +76 -0
- package/nitrogen/generated/shared/c++/AudioRoute.hpp +101 -0
- package/nitrogen/generated/shared/c++/AudioRouteKind.hpp +120 -0
- package/nitrogen/generated/shared/c++/BrowseItem.hpp +104 -0
- package/nitrogen/generated/shared/c++/BrowseSection.hpp +106 -0
- package/nitrogen/generated/shared/c++/BrowseSnapshot.hpp +90 -0
- package/nitrogen/generated/shared/c++/BufferState.hpp +84 -0
- package/nitrogen/generated/shared/c++/CacheStatus.hpp +100 -0
- package/nitrogen/generated/shared/c++/CastConfigureOptions.hpp +88 -0
- package/nitrogen/generated/shared/c++/CastConnectOptions.hpp +83 -0
- package/nitrogen/generated/shared/c++/CastDeviceKind.hpp +92 -0
- package/nitrogen/generated/shared/c++/CastDiscoveryState.hpp +83 -0
- package/nitrogen/generated/shared/c++/CastLocalNetworkPermission.hpp +80 -0
- package/nitrogen/generated/shared/c++/CastLocalNetworkPermissionEvent.hpp +84 -0
- package/nitrogen/generated/shared/c++/CastProtocol.hpp +88 -0
- package/nitrogen/generated/shared/c++/CastReceiver.hpp +115 -0
- package/nitrogen/generated/shared/c++/CastReceiverCapabilities.hpp +92 -0
- package/nitrogen/generated/shared/c++/CastRoute.hpp +111 -0
- package/nitrogen/generated/shared/c++/CastSessionDiedEvent.hpp +89 -0
- package/nitrogen/generated/shared/c++/CastSessionEndReason.hpp +100 -0
- package/nitrogen/generated/shared/c++/CastStreamingModel.hpp +76 -0
- package/nitrogen/generated/shared/c++/EqualizerBand.hpp +87 -0
- package/nitrogen/generated/shared/c++/EqualizerPreset.hpp +92 -0
- package/nitrogen/generated/shared/c++/EvictionPolicy.hpp +76 -0
- package/nitrogen/generated/shared/c++/HybridCastManagerSpec.cpp +42 -0
- package/nitrogen/generated/shared/c++/HybridCastManagerSpec.hpp +115 -0
- package/nitrogen/generated/shared/c++/HybridEqualizerSpec.cpp +32 -0
- package/nitrogen/generated/shared/c++/HybridEqualizerSpec.hpp +81 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +88 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +209 -0
- package/nitrogen/generated/shared/c++/HybridVisualizerSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridVisualizerSpec.hpp +71 -0
- package/nitrogen/generated/shared/c++/LookaheadCacheConfig.hpp +87 -0
- package/nitrogen/generated/shared/c++/MediaSearchOrigin.hpp +76 -0
- package/nitrogen/generated/shared/c++/MediaSearchQueueLocation.hpp +80 -0
- package/nitrogen/generated/shared/c++/MediaSearchReference.hpp +76 -0
- package/nitrogen/generated/shared/c++/MediaSearchReleaseDateRange.hpp +87 -0
- package/nitrogen/generated/shared/c++/MediaSearchRepeatMode.hpp +80 -0
- package/nitrogen/generated/shared/c++/MediaSearchRequest.hpp +157 -0
- package/nitrogen/generated/shared/c++/MediaSearchType.hpp +88 -0
- package/nitrogen/generated/shared/c++/NowPlayingFormat.hpp +149 -0
- package/nitrogen/generated/shared/c++/NowPlayingFormatAndroidExtras.hpp +94 -0
- package/nitrogen/generated/shared/c++/NowPlayingFormatIosExtras.hpp +85 -0
- package/nitrogen/generated/shared/c++/PitchCorrectionMode.hpp +80 -0
- package/nitrogen/generated/shared/c++/PlaybackError.hpp +113 -0
- package/nitrogen/generated/shared/c++/PlaybackErrorCode.hpp +116 -0
- package/nitrogen/generated/shared/c++/PlaybackMode.hpp +89 -0
- package/nitrogen/generated/shared/c++/PlaybackModeKind.hpp +76 -0
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +141 -0
- package/nitrogen/generated/shared/c++/PlayerProgress.hpp +91 -0
- package/nitrogen/generated/shared/c++/PlayerState.hpp +96 -0
- package/nitrogen/generated/shared/c++/RemoteControlOptions.hpp +92 -0
- package/nitrogen/generated/shared/c++/RemoteSkipMode.hpp +76 -0
- package/nitrogen/generated/shared/c++/RepeatMode.hpp +80 -0
- package/nitrogen/generated/shared/c++/ReplayGainMode.hpp +80 -0
- package/nitrogen/generated/shared/c++/SectionIcon.hpp +120 -0
- package/nitrogen/generated/shared/c++/ServiceReadyReason.hpp +76 -0
- package/nitrogen/generated/shared/c++/ShuffleResult.hpp +94 -0
- package/nitrogen/generated/shared/c++/SkipCapability.hpp +87 -0
- package/nitrogen/generated/shared/c++/SleepTimerState.hpp +95 -0
- package/nitrogen/generated/shared/c++/StateChangeReason.hpp +96 -0
- package/nitrogen/generated/shared/c++/TrackChangeReason.hpp +96 -0
- package/nitrogen/generated/shared/c++/TrackItem.hpp +113 -0
- package/nitrogen/generated/shared/c++/TrackSource.hpp +80 -0
- package/nitrogen/generated/shared/c++/VisualizerConfig.hpp +91 -0
- package/nitrogen/generated/shared/c++/VisualizerErrorReason.hpp +84 -0
- package/nitrogen/generated/shared/c++/VisualizerFrame.hpp +92 -0
- package/nitrogen/generated/shared/c++/VoiceVocabulary.hpp +88 -0
- package/package.json +167 -0
- package/src/CastManager.nitro.ts +206 -0
- package/src/Equalizer.nitro.ts +37 -0
- package/src/TrackPlayer.nitro.ts +667 -0
- package/src/Visualizer.nitro.ts +48 -0
- package/src/cast/index.ts +224 -0
- package/src/clients.ts +50 -0
- package/src/equalizer.ts +86 -0
- package/src/hooks/index.ts +33 -0
- package/src/hooks/useActiveTrack.ts +55 -0
- package/src/hooks/useAudioRoute.ts +69 -0
- package/src/hooks/useBufferState.ts +35 -0
- package/src/hooks/useCanSkip.ts +92 -0
- package/src/hooks/useCast.ts +121 -0
- package/src/hooks/useEqualizer.ts +111 -0
- package/src/hooks/useFullyBuffered.ts +32 -0
- package/src/hooks/useLookaheadCache.ts +54 -0
- package/src/hooks/useNowPlayingFormat.ts +43 -0
- package/src/hooks/usePlaybackState.ts +34 -0
- package/src/hooks/useProgress.ts +36 -0
- package/src/hooks/useSleepTimer.ts +63 -0
- package/src/hooks/useVisualizerState.ts +121 -0
- package/src/index.ts +125 -0
- package/src/playbackService.ts +304 -0
- package/src/types.ts +1387 -0
|
@@ -0,0 +1,1716 @@
|
|
|
1
|
+
package com.margelo.nitro.queueplayer
|
|
2
|
+
|
|
3
|
+
import android.app.Notification
|
|
4
|
+
import android.app.NotificationChannel
|
|
5
|
+
import android.app.NotificationManager
|
|
6
|
+
import android.app.PendingIntent
|
|
7
|
+
import android.content.BroadcastReceiver
|
|
8
|
+
import android.content.Context
|
|
9
|
+
import android.app.Service
|
|
10
|
+
import android.content.Intent
|
|
11
|
+
import android.content.IntentFilter
|
|
12
|
+
import android.media.AudioManager
|
|
13
|
+
import android.os.Binder
|
|
14
|
+
import android.os.Build
|
|
15
|
+
import android.os.Handler
|
|
16
|
+
import android.os.IBinder
|
|
17
|
+
import android.os.Looper
|
|
18
|
+
import androidx.core.content.ContextCompat
|
|
19
|
+
import androidx.annotation.VisibleForTesting
|
|
20
|
+
import androidx.media3.common.C
|
|
21
|
+
import androidx.media3.common.MediaItem
|
|
22
|
+
import androidx.media3.common.Player
|
|
23
|
+
import androidx.media3.common.util.UnstableApi
|
|
24
|
+
import androidx.media3.datasource.DataSourceBitmapLoader
|
|
25
|
+
import androidx.media3.exoplayer.ExoPlayer
|
|
26
|
+
import androidx.media3.exoplayer.source.MediaSource
|
|
27
|
+
import androidx.media3.session.CacheBitmapLoader
|
|
28
|
+
import androidx.media3.session.CommandButton
|
|
29
|
+
import androidx.media3.session.LibraryResult
|
|
30
|
+
import androidx.media3.session.MediaLibraryService
|
|
31
|
+
import androidx.media3.session.MediaLibraryService.LibraryParams
|
|
32
|
+
import androidx.media3.session.MediaLibraryService.MediaLibrarySession
|
|
33
|
+
import androidx.media3.session.MediaSession
|
|
34
|
+
import androidx.media3.session.SessionResult
|
|
35
|
+
import com.google.common.collect.ImmutableList
|
|
36
|
+
import com.google.common.util.concurrent.Futures
|
|
37
|
+
import com.google.common.util.concurrent.ListenableFuture
|
|
38
|
+
import com.google.common.util.concurrent.MoreExecutors
|
|
39
|
+
import com.google.common.util.concurrent.SettableFuture
|
|
40
|
+
import com.margelo.nitro.core.Promise
|
|
41
|
+
import com.margelo.nitro.queueplayer.cast.PlaybackStateRouter
|
|
42
|
+
import java.util.UUID
|
|
43
|
+
import kotlinx.coroutines.cancel
|
|
44
|
+
import kotlinx.coroutines.currentCoroutineContext
|
|
45
|
+
import kotlinx.coroutines.delay
|
|
46
|
+
import kotlinx.coroutines.flow.collect
|
|
47
|
+
import kotlinx.coroutines.isActive
|
|
48
|
+
import kotlinx.coroutines.launch
|
|
49
|
+
import kotlinx.coroutines.withContext
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Foreground service that owns the ExoPlayer + MediaLibrarySession.
|
|
53
|
+
* In-process clients (the `TrackPlayer` Hybrid) obtain a
|
|
54
|
+
* [LocalBinder] handle via [ACTION_LOCAL_BIND] to access the
|
|
55
|
+
* player + session directly; framework media controllers (Android
|
|
56
|
+
* Auto, Wear, Assistant) bind through the standard
|
|
57
|
+
* [MediaLibraryService] path declared in the manifest.
|
|
58
|
+
*
|
|
59
|
+
* Extends [HeadlessJsMediaService] (which extends
|
|
60
|
+
* [MediaLibraryService] which is a strict superset of
|
|
61
|
+
* `MediaSessionService`) to provide:
|
|
62
|
+
* - Browse + search affordances to Android Auto / Google Assistant
|
|
63
|
+
* via [PlaybackServiceCallback].
|
|
64
|
+
* - JS-task spawning on cold-wake (Auto / Assistant binds the
|
|
65
|
+
* service before any Activity has booted React) via
|
|
66
|
+
* [getTaskConfig] returning a [HeadlessJsTaskConfig] for known
|
|
67
|
+
* wake actions.
|
|
68
|
+
*
|
|
69
|
+
* Threading: ExoPlayer is constructed + accessed on the service's
|
|
70
|
+
* main looper, which equals the application's main looper in this
|
|
71
|
+
* single-process library. Heavy work (decode, buffer, network
|
|
72
|
+
* I/O) happens on ExoPlayer's internal threads regardless. Binder
|
|
73
|
+
* consumers MUST hop to [LocalBinder.handler] before touching the
|
|
74
|
+
* player so the host-handler indirection is canonical.
|
|
75
|
+
*/
|
|
76
|
+
@UnstableApi
|
|
77
|
+
class PlaybackService : HeadlessJsMediaService() {
|
|
78
|
+
|
|
79
|
+
companion object {
|
|
80
|
+
/**
|
|
81
|
+
* Intent action consumers use with `bindService` to obtain the
|
|
82
|
+
* [LocalBinder]. Falls through to [MediaLibraryService.onBind]'s
|
|
83
|
+
* default behaviour for any other action so the framework's own
|
|
84
|
+
* media controller connection path keeps working.
|
|
85
|
+
*/
|
|
86
|
+
const val ACTION_LOCAL_BIND = "com.margelo.nitro.queueplayer.LOCAL_BIND"
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The currently-active playback engine. Mirrors the engine the
|
|
90
|
+
* service is wrapping. Set on engine boot + every swap; cleared
|
|
91
|
+
* on service destroy.
|
|
92
|
+
*
|
|
93
|
+
* The Equalizer + Visualizer Hybrids reach this on construction
|
|
94
|
+
* to wire themselves to an already-running engine — covers the
|
|
95
|
+
* case where a Hybrid is constructed AFTER the service has
|
|
96
|
+
* dispatched its initial wireToPlaybackEngine fires (which
|
|
97
|
+
* would have hit a `null` static reference). Without this, a
|
|
98
|
+
* Hybrid that lazy-constructs after service onCreate would
|
|
99
|
+
* silently never receive any wire.
|
|
100
|
+
*/
|
|
101
|
+
@JvmStatic
|
|
102
|
+
@Volatile
|
|
103
|
+
internal var activeEngine: PlaybackEngine? = null
|
|
104
|
+
private set
|
|
105
|
+
|
|
106
|
+
@JvmSynthetic
|
|
107
|
+
internal fun setActiveEngine(engine: PlaybackEngine?) {
|
|
108
|
+
activeEngine = engine
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* NotificationChannel ID for the media-style foreground
|
|
113
|
+
* notification. Fully-qualified to avoid collision with
|
|
114
|
+
* channels created elsewhere in the consumer app. `internal`
|
|
115
|
+
* so [NotificationProvider] can pin it without a cross-class
|
|
116
|
+
* literal.
|
|
117
|
+
*/
|
|
118
|
+
internal const val NOTIFICATION_CHANNEL_ID = "com.margelo.nitro.queueplayer.playback"
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Notification ID for Media3's notification provider. Stable
|
|
122
|
+
* across the service lifecycle so updates replace the post in
|
|
123
|
+
* place rather than spawning duplicates. `internal` for the
|
|
124
|
+
* same reason as [NOTIFICATION_CHANNEL_ID].
|
|
125
|
+
*/
|
|
126
|
+
internal const val NOTIFICATION_ID = 1001
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Request code paired with the launcher [PendingIntent] handed
|
|
130
|
+
* to [MediaSession.Builder.setSessionActivity]. Stable across
|
|
131
|
+
* the process so [PendingIntent.FLAG_UPDATE_CURRENT] mutates
|
|
132
|
+
* the existing entry instead of spawning a duplicate.
|
|
133
|
+
*/
|
|
134
|
+
private const val REQUEST_CODE_OPEN_APP = 0
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Maximum wall-clock wait for a JS-side search-request handler
|
|
138
|
+
* to resolve. After this expires the dispatch surfaces an empty
|
|
139
|
+
* result list to the controller — better a clean empty than a
|
|
140
|
+
* spinner that never goes away. 10 s mirrors the standard
|
|
141
|
+
* Android Auto / Assistant search-tile timeout.
|
|
142
|
+
*/
|
|
143
|
+
private const val SEARCH_DISPATCH_TIMEOUT_MS = 10_000L
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Maximum wall-clock wait for a cold-start headless dispatch to
|
|
147
|
+
* resolve — covers (a) the JS bridge spinning up + the consumer's
|
|
148
|
+
* `registerPlaybackService` running, AND (b) the consumer's
|
|
149
|
+
* resolution Promise running. 20 s mirrors the iOS
|
|
150
|
+
* [PendingIntentBuffer] TTL: ~3 s for bridge boot + ~17 s of
|
|
151
|
+
* headroom for the consumer's network calls before the controller
|
|
152
|
+
* surfaces "no results" and the user retries. The two platforms'
|
|
153
|
+
* cold-start windows are deliberately the same so the manual
|
|
154
|
+
* playbook reads identically on iPhone + Android.
|
|
155
|
+
*/
|
|
156
|
+
private const val HEADLESS_DISPATCH_TIMEOUT_MS = 20_000L
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Intent action that flags an `onStartCommand` invocation as a
|
|
160
|
+
* headless-controller cold-wake. [getTaskConfig] returns a
|
|
161
|
+
* non-null config only for this action so an in-process
|
|
162
|
+
* `LOCAL_BIND` start does not also spawn the JS task.
|
|
163
|
+
*/
|
|
164
|
+
internal const val ACTION_HEADLESS_WAKE =
|
|
165
|
+
"com.margelo.nitro.queueplayer.HEADLESS_WAKE"
|
|
166
|
+
|
|
167
|
+
/** Bundle key carrying the connecting controller's package name
|
|
168
|
+
* through the wake intent → JS task data map. */
|
|
169
|
+
internal const val EXTRA_WAKE_PACKAGE = "wakePackage"
|
|
170
|
+
|
|
171
|
+
/** Bundle key carrying the wake intent's `action` through the JS
|
|
172
|
+
* task data map so the consumer's playbackService handler can
|
|
173
|
+
* branch on the wake source if it wants to. */
|
|
174
|
+
internal const val EXTRA_WAKE_ACTION = "wakeAction"
|
|
175
|
+
|
|
176
|
+
/** Intent actions that signal a cold-wake — either an in-process
|
|
177
|
+
* self-wake from `onConnect` (`ACTION_HEADLESS_WAKE`) or an
|
|
178
|
+
* OS-routed hardware media-button intent landing on a service
|
|
179
|
+
* whose JS bridge isn't yet up (`ACTION_MEDIA_BUTTON`). */
|
|
180
|
+
private val HEADLESS_WAKE_ACTIONS: Set<String> = setOf(
|
|
181
|
+
ACTION_HEADLESS_WAKE,
|
|
182
|
+
Intent.ACTION_MEDIA_BUTTON
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Headless task name handed to React Native's
|
|
187
|
+
* `HeadlessJsTaskService`. Must match the
|
|
188
|
+
* `AppRegistry.registerHeadlessTask` registration the lib's
|
|
189
|
+
* `registerPlaybackService` does on the JS side.
|
|
190
|
+
*/
|
|
191
|
+
internal const val HEADLESS_TASK_NAME = "RNQueuePlayerHeadlessTask"
|
|
192
|
+
|
|
193
|
+
/** Logcat tag for service-level lifecycle + dispatch warnings.
|
|
194
|
+
* Member of the `QueuePlayer.<area>` hierarchy used across the
|
|
195
|
+
* lib's Android sources. */
|
|
196
|
+
private const val LOG_TAG = "QueuePlayer.Service"
|
|
197
|
+
|
|
198
|
+
/** Minimum spacing between consecutive `selfWakeForHeadlessClient`
|
|
199
|
+
* fires from the same service instance. Defence against
|
|
200
|
+
* gearhead's rapid bind/unbind retry loop when its initial
|
|
201
|
+
* `onConnect` resolves to a not-supported state. */
|
|
202
|
+
private const val SELF_WAKE_MIN_INTERVAL_MS = 30_000L
|
|
203
|
+
|
|
204
|
+
/** Grace window between an idle `onHeadlessJsTaskFinish` and the
|
|
205
|
+
* actual `stopSelf()`. The JS bundle-load side effects
|
|
206
|
+
* (`bootstrap.ts` → `configure()` + `installCarService()`) run
|
|
207
|
+
* AFTER the headless task body resolves; tearing the service
|
|
208
|
+
* down immediately races that chain. 5s comfortably covers the
|
|
209
|
+
* cold-start bootstrap timing without keeping a genuinely-idle
|
|
210
|
+
* service alive for long. */
|
|
211
|
+
private const val IDLE_STOP_GRACE_MS = 5_000L
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Timestamp of the last actual self-wake fire — used by the
|
|
215
|
+
* rate-limiter in [selfWakeForHeadlessClient]. Zero means "never
|
|
216
|
+
* fired in this service instance". */
|
|
217
|
+
@Volatile
|
|
218
|
+
private var lastSelfWakeAtMs: Long = 0L
|
|
219
|
+
|
|
220
|
+
/** True when the consumer app's React host has an active context
|
|
221
|
+
* AND a foregrounded Activity. Used by [selfWakeForHeadlessClient]
|
|
222
|
+
* to skip the wake-intent self-start that would otherwise race
|
|
223
|
+
* the live JS instance. The `currentActivity` check is more
|
|
224
|
+
* reliable in Expo dev-client mode than reading
|
|
225
|
+
* `currentReactContext` alone — the context can be null during a
|
|
226
|
+
* JS reload while the activity is still up. */
|
|
227
|
+
private fun hasLiveReactContext(): Boolean {
|
|
228
|
+
val reactApp = application as? com.facebook.react.ReactApplication ?: return false
|
|
229
|
+
val ctx = reactApp.reactHost?.currentReactContext
|
|
230
|
+
return ctx != null && ctx.currentActivity != null
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Built in onCreate; released in onDestroy. `lateinit var` (vs
|
|
234
|
+
// `?:`) reflects the contract that any consumer reading these
|
|
235
|
+
// through the binder is guaranteed to be post-onCreate by Android's
|
|
236
|
+
// service lifecycle.
|
|
237
|
+
//
|
|
238
|
+
// The service owns a [GaplessEngine] wrapper around the ExoPlayer.
|
|
239
|
+
// Service-level wiring (audio focus listener, MediaSession.setPlayer)
|
|
240
|
+
// continues to access the underlying player via [engine].`exoPlayer`;
|
|
241
|
+
// the abstract [PlaybackEngine] surface is exposed via
|
|
242
|
+
// [LocalBinder.engine] for [TrackPlayer] and other engine-aware
|
|
243
|
+
// consumers.
|
|
244
|
+
private lateinit var engine: PlaybackEngine
|
|
245
|
+
private val exoPlayer: ExoPlayer get() = engine.mediaSessionPlayer
|
|
246
|
+
private var mediaSession: MediaLibrarySession? = null
|
|
247
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Native cache for the consumer's `BrowseSnapshot`. Read by
|
|
251
|
+
* [PlaybackServiceCallback]'s library callbacks
|
|
252
|
+
* (`onGetLibraryRoot`, `onGetChildren`, `onGetItem`, etc.) so a
|
|
253
|
+
* cold-wake from Android Auto / Assistant doesn't have to wait
|
|
254
|
+
* for a JS bridge round-trip. Written via the binder by
|
|
255
|
+
* `TrackPlayer.setBrowseSnapshot`. Empty by default — the
|
|
256
|
+
* library surface returns `RESULT_ERROR_NOT_SUPPORTED` until a
|
|
257
|
+
* snapshot is pushed.
|
|
258
|
+
*/
|
|
259
|
+
internal val browseDataProvider = BrowseDataProvider()
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Per-query cache of JS-supplied search results. Populated by
|
|
263
|
+
* `TrackPlayer.setSearchResults` once the JS handler
|
|
264
|
+
* responds to a search request; read by
|
|
265
|
+
* [PlaybackServiceCallback.onGetSearchResult]. Empty by default —
|
|
266
|
+
* a controller's `getSearchResult` call before the JS responder
|
|
267
|
+
* has answered returns an empty page.
|
|
268
|
+
*/
|
|
269
|
+
internal val searchResultCache = SearchResultCache()
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Native queue of `parentId`s that a controller asked the
|
|
273
|
+
* MediaLibrarySession to enumerate before the consumer's JS bridge
|
|
274
|
+
* had pushed a [BrowseSnapshot]. Drained by
|
|
275
|
+
* [notifyChildrenChangedForParents] on the next snapshot push so
|
|
276
|
+
* the controller re-fetches with real data. See
|
|
277
|
+
* [PendingBrowseRequests] for the cold-start race rationale.
|
|
278
|
+
*/
|
|
279
|
+
internal val pendingBrowseRequests = PendingBrowseRequests()
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Holds the JS-side automotive callbacks (`onBrowseRequest`,
|
|
283
|
+
* `onSearchRequest`, `onPlayFromSearchRequest`,
|
|
284
|
+
* `onPlayFromIdRequest`, `onCarConnect`, `onCarDisconnect`).
|
|
285
|
+
* Registered by `TrackPlayer.on*` Hybrid methods; consulted by
|
|
286
|
+
* the [callbackEnvironment] hooks when the `MediaLibrarySession`
|
|
287
|
+
* callback path needs to dispatch into JS.
|
|
288
|
+
*/
|
|
289
|
+
internal val browseCallbacks = BrowseCallbackRegistry()
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Dependency surface [PlaybackServiceCallback] reads through.
|
|
293
|
+
* Lambda-based providers for fields that mutate over the service
|
|
294
|
+
* lifetime (`commandHandler`, `mediaButtonDispatch`) so the
|
|
295
|
+
* callback always sees the live value without a reference rebuild.
|
|
296
|
+
*/
|
|
297
|
+
@VisibleForTesting
|
|
298
|
+
internal val callbackEnvironment = object : PlaybackServiceCallback.Environment {
|
|
299
|
+
override val browseDataProvider: BrowseDataProvider
|
|
300
|
+
get() = this@PlaybackService.browseDataProvider
|
|
301
|
+
|
|
302
|
+
override val searchResultCache: SearchResultCache
|
|
303
|
+
get() = this@PlaybackService.searchResultCache
|
|
304
|
+
|
|
305
|
+
override fun commandHandler() = this@PlaybackService.commandHandler
|
|
306
|
+
|
|
307
|
+
override fun mediaButtonDispatch() = this@PlaybackService.mediaButtonDispatch
|
|
308
|
+
|
|
309
|
+
override fun playerCommandsFor(
|
|
310
|
+
capability: SkipCapability,
|
|
311
|
+
hasMediaItems: Boolean
|
|
312
|
+
): Player.Commands =
|
|
313
|
+
this@PlaybackService.playerCommandsFor(capability, hasMediaItems)
|
|
314
|
+
|
|
315
|
+
override fun onHeadlessControllerConnect(packageName: String) {
|
|
316
|
+
// Cold-wake the JS bridge so the consumer's
|
|
317
|
+
// `registerPlaybackService` can fire its callbacks before
|
|
318
|
+
// Media3's framework dispatches `onAddMediaItems`. Idempotent
|
|
319
|
+
// — `HeadlessJsMediaService.initialized` latches the JS task
|
|
320
|
+
// spawn so re-entrant connects do not double-spawn.
|
|
321
|
+
this@PlaybackService.selfWakeForHeadlessClient(packageName)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
override fun onFirstHeadlessControllerAttached() {
|
|
325
|
+
// Cold-start race: the FIRST headless controller's onConnect
|
|
326
|
+
// fires BEFORE the JS task spawned by [selfWakeForHeadlessClient]
|
|
327
|
+
// has run `registerPlaybackService`, so reading the callback
|
|
328
|
+
// synchronously would silently drop the dispatch. Launch on
|
|
329
|
+
// [callbackDispatchScope] and poll up to
|
|
330
|
+
// [HEADLESS_DISPATCH_TIMEOUT_MS], matching dispatchAssistantPlay's
|
|
331
|
+
// shape. Subsequent connects in the same session land on a
|
|
332
|
+
// registered callback and fire on the first poll.
|
|
333
|
+
this@PlaybackService.dispatchCarConnect()
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
override fun onHeadlessControllerDisconnect() {
|
|
337
|
+
this@PlaybackService.browseCallbacks.carDisconnectCallback()?.invoke()
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
override fun onSearchQueryReceived(query: String) {
|
|
341
|
+
this@PlaybackService.dispatchSearchToJs(query)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
override fun onPlayFromSearchRequest(
|
|
345
|
+
request: MediaSearchRequest
|
|
346
|
+
): ListenableFuture<List<MediaItem>> =
|
|
347
|
+
this@PlaybackService.dispatchPlayFromSearchToJs(request)
|
|
348
|
+
|
|
349
|
+
override fun onPlayFromIdRequest(
|
|
350
|
+
mediaId: String
|
|
351
|
+
): ListenableFuture<List<MediaItem>> =
|
|
352
|
+
this@PlaybackService.dispatchPlayFromIdToJs(mediaId)
|
|
353
|
+
|
|
354
|
+
override fun onPlayFromIdRequestNoSync(mediaId: String) {
|
|
355
|
+
this@PlaybackService.dispatchPlayFromIdToJsFireOnly(mediaId)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
override fun enqueuePendingBrowseRequest(parentId: String) {
|
|
359
|
+
this@PlaybackService.pendingBrowseRequests.enqueue(parentId)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
override fun onBrowseRequestForChildren(
|
|
363
|
+
parentId: String
|
|
364
|
+
): ListenableFuture<List<BrowseItem>> =
|
|
365
|
+
this@PlaybackService.dispatchBrowseRequestToJs(parentId)
|
|
366
|
+
|
|
367
|
+
override fun libraryRootItem(): MediaItem = libraryRootItemFromPackage()
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Cached display label for the consumer app. Resolved lazily on
|
|
371
|
+
* first `onGetLibraryRoot` since `packageManager` reads cost a
|
|
372
|
+
* PackageManager round-trip. */
|
|
373
|
+
@Volatile
|
|
374
|
+
private var cachedLibraryRootTitle: String? = null
|
|
375
|
+
|
|
376
|
+
/** Build the lib-owned root MediaItem with the consumer app's
|
|
377
|
+
* display label as title (e.g. "RNQP-Demo"). Cross-process
|
|
378
|
+
* controllers (Android Auto's gearhead) render this title in the
|
|
379
|
+
* browse drawer; an empty title makes the drawer entry look
|
|
380
|
+
* broken on some AA builds. */
|
|
381
|
+
private fun libraryRootItemFromPackage(): MediaItem {
|
|
382
|
+
val title = cachedLibraryRootTitle ?: run {
|
|
383
|
+
val resolved = try {
|
|
384
|
+
val pm = packageManager
|
|
385
|
+
val info = pm.getApplicationInfo(packageName, 0)
|
|
386
|
+
pm.getApplicationLabel(info).toString().ifBlank { "Library" }
|
|
387
|
+
} catch (_: Throwable) {
|
|
388
|
+
"Library"
|
|
389
|
+
}
|
|
390
|
+
cachedLibraryRootTitle = resolved
|
|
391
|
+
resolved
|
|
392
|
+
}
|
|
393
|
+
return MediaItemMapper.toMediaItem(
|
|
394
|
+
BrowseItem(
|
|
395
|
+
id = PlaybackServiceCallback.LIB_ROOT_ID,
|
|
396
|
+
title = title,
|
|
397
|
+
subtitle = null,
|
|
398
|
+
artworkUrl = null,
|
|
399
|
+
playable = false,
|
|
400
|
+
hasChildren = true
|
|
401
|
+
)
|
|
402
|
+
)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Binder exposed to in-process clients. Holds direct references
|
|
407
|
+
* to the ExoPlayer + MediaSession + the service's main handler.
|
|
408
|
+
* Single-process library: no IPC marshalling — the binder is
|
|
409
|
+
* effectively a typed pointer. Reads are guaranteed valid
|
|
410
|
+
* between the service's `onCreate` returning and `onDestroy`
|
|
411
|
+
* starting; consumers MUST `unbindService` before the service
|
|
412
|
+
* is destroyed.
|
|
413
|
+
*/
|
|
414
|
+
inner class LocalBinder : Binder() {
|
|
415
|
+
val service: PlaybackService get() = this@PlaybackService
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Stable identity for this service instance. JS-side
|
|
419
|
+
* `TrackPlayer.serviceConnection.onServiceConnected` compares
|
|
420
|
+
* the new binder's id against the previously-seen value to
|
|
421
|
+
* distinguish a first bind (`first-bind`) from a rebind after
|
|
422
|
+
* an OS-driven service kill (`service-reborn`). The OS rebuilds
|
|
423
|
+
* the service from scratch in the kill-and-restart path, so
|
|
424
|
+
* the new instance gets a fresh `instanceId` and the lib's
|
|
425
|
+
* `onServiceReady` dispatch fires with the right reason.
|
|
426
|
+
*/
|
|
427
|
+
val instanceId: java.util.UUID = java.util.UUID.randomUUID()
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Direct access to the underlying ExoPlayer. Retained as a
|
|
431
|
+
* back-compat shim for [TrackPlayer] until its transport /
|
|
432
|
+
* queue calls migrate onto the [engine] surface; engine-aware
|
|
433
|
+
* code paths use [engine] instead.
|
|
434
|
+
*/
|
|
435
|
+
val player: ExoPlayer get() = exoPlayer
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Strategy-pattern engine wrapping the active player(s).
|
|
439
|
+
* Routes through the abstract [PlaybackEngine] surface so
|
|
440
|
+
* consumers don't depend on the concrete engine class.
|
|
441
|
+
*/
|
|
442
|
+
val engine: PlaybackEngine get() = this@PlaybackService.engine
|
|
443
|
+
val session: MediaLibrarySession get() = mediaSession!!
|
|
444
|
+
val handler: Handler get() = mainHandler
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Register the [MediaSessionCommandHandler] that the
|
|
448
|
+
* `MediaSession.Callback` consults when a controller
|
|
449
|
+
* (lock-screen, Bluetooth, Android Auto) issues a player
|
|
450
|
+
* command, and that the [MediaButtonDispatch] consults when a
|
|
451
|
+
* raw `Intent.ACTION_MEDIA_BUTTON` lands via callback / service
|
|
452
|
+
* path. `null` clears the registration. Calls run on the service
|
|
453
|
+
* main looper.
|
|
454
|
+
*/
|
|
455
|
+
internal fun setCommandHandler(handler: MediaSessionCommandHandler?) {
|
|
456
|
+
if (commandHandler === handler) return
|
|
457
|
+
commandHandler = handler
|
|
458
|
+
mediaButtonDispatch = handler?.let { MediaButtonDispatch(it) }
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Push the latest skip-capability-derived [Player.Commands] to
|
|
463
|
+
* every connected controller. Called by [TrackPlayer] whenever
|
|
464
|
+
* `cachedSkipCapability` changes so the lock-screen, Bluetooth
|
|
465
|
+
* AVRCP, Android Auto, and Wear surfaces grey out skip buttons
|
|
466
|
+
* that the queue boundary makes invalid. No-op when no handler
|
|
467
|
+
* is registered (no authoritative capability source) or when no
|
|
468
|
+
* controllers have connected yet (next [PlaybackCallback.onConnect]
|
|
469
|
+
* picks up the fresh capability).
|
|
470
|
+
*/
|
|
471
|
+
internal fun refreshAvailableCommands() {
|
|
472
|
+
val handler = commandHandler ?: return
|
|
473
|
+
val session = mediaSession ?: return
|
|
474
|
+
// The cap is global — same Player.Commands for every controller.
|
|
475
|
+
// `hasMediaItems` reads from the live player so a freshly-empty
|
|
476
|
+
// queue strips PLAY/PAUSE/PREPARE from the action mask and AA's
|
|
477
|
+
// dashboard tap routes to Library instead of an empty Now Playing.
|
|
478
|
+
val hasMediaItems = session.player.mediaItemCount > 0
|
|
479
|
+
val commands = playerCommandsFor(
|
|
480
|
+
handler.getCachedSkipCapability(),
|
|
481
|
+
hasMediaItems
|
|
482
|
+
)
|
|
483
|
+
for (controller in session.connectedControllers) {
|
|
484
|
+
session.setAvailableCommands(
|
|
485
|
+
controller,
|
|
486
|
+
MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS,
|
|
487
|
+
commands,
|
|
488
|
+
)
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Apply the lock-screen / notification remote-control config (skip mode +
|
|
494
|
+
* jump intervals). Called on the main thread from
|
|
495
|
+
* [TrackPlayer.setRemoteControls]; re-applies live.
|
|
496
|
+
*/
|
|
497
|
+
internal fun setRemoteControls(mode: RemoteSkipMode, forwardMs: Long, backwardMs: Long) {
|
|
498
|
+
this@PlaybackService.applyRemoteControls(mode, forwardMs, backwardMs)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Mirror `PlayerConfig.clampSeekToBuffered` onto the forwarding player's
|
|
503
|
+
* interval skip-forward. Called on the main thread from
|
|
504
|
+
* [TrackPlayer.configureInternal] + on rebind.
|
|
505
|
+
*/
|
|
506
|
+
internal fun setClampSeekToBuffered(enabled: Boolean) {
|
|
507
|
+
this@PlaybackService.applyClampSeekToBuffered(enabled)
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
private val localBinder by lazy { LocalBinder() }
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Currently-registered handler consulted by [PlaybackCallback]
|
|
515
|
+
* before the default routing runs. `null` until
|
|
516
|
+
* [TrackPlayer.configureInternal] wires itself in; under that
|
|
517
|
+
* state every callback falls through to the Media3 default and
|
|
518
|
+
* raw media-button intents fall through to the framework.
|
|
519
|
+
*/
|
|
520
|
+
private var commandHandler: MediaSessionCommandHandler? = null
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Per-handler [MediaButtonDispatch] that absorbs duplicate fires
|
|
524
|
+
* when the same physical media-button press reaches the service
|
|
525
|
+
* via more than one path (callback `onMediaButtonEvent`, service
|
|
526
|
+
* `onStartCommand`, or a manifest-registered `MediaButtonReceiver`
|
|
527
|
+
* that forwards into `onStartCommand`). Created/torn down with the
|
|
528
|
+
* handler so its dedup state lives exactly as long as the handler
|
|
529
|
+
* does.
|
|
530
|
+
*/
|
|
531
|
+
private var mediaButtonDispatch: MediaButtonDispatch? = null
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Tracks the previous [Player.PlaybackSuppressionReason] so the
|
|
535
|
+
* audio-focus listener can detect entry into and exit from
|
|
536
|
+
* `TRANSIENT_AUDIO_FOCUS_LOSS`. Initialized to `REASON_NONE` to
|
|
537
|
+
* match the player's startup state; rewritten on every
|
|
538
|
+
* `onPlaybackSuppressionReasonChanged` fire after the mapping
|
|
539
|
+
* has been computed.
|
|
540
|
+
*/
|
|
541
|
+
private var previousSuppressionReason: Int = Player.PLAYBACK_SUPPRESSION_REASON_NONE
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Player instance the [audioFocusListener] is currently attached
|
|
545
|
+
* to. For GaplessEngine this is the underlying ExoPlayer; for
|
|
546
|
+
* CrossfadeEngine this is the [CrossfadeForwardingPlayer]
|
|
547
|
+
* wrapper (so listener events follow role swaps via the
|
|
548
|
+
* wrapper's `ForwardingSimpleBasePlayer` listener routing).
|
|
549
|
+
* Tracked here so the next [swapEngineInternal] can remove the
|
|
550
|
+
* listener from the same instance it was attached to.
|
|
551
|
+
*/
|
|
552
|
+
private var audioFocusListenerHost: Player? = null
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* [Player.Listener] that translates Media3's audio-focus-driven
|
|
556
|
+
* state changes into typed [PlaybackError] events on the JS
|
|
557
|
+
* surface. `onPlayWhenReadyChanged` with reason
|
|
558
|
+
* `PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS` carries
|
|
559
|
+
* permanent loss; `onPlaybackSuppressionReasonChanged` carries
|
|
560
|
+
* transient loss + regain. Re-attached to the new player on every
|
|
561
|
+
* [configurePlayer] swap.
|
|
562
|
+
*/
|
|
563
|
+
private val audioFocusListener = object : Player.Listener {
|
|
564
|
+
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
|
|
565
|
+
val mapping = AudioFocusErrorMapping.mapPlayWhenReadyReason(reason) ?: return
|
|
566
|
+
commandHandler?.onAudioFocusEvent(mapping)
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
override fun onPlaybackSuppressionReasonChanged(reason: Int) {
|
|
570
|
+
val previous = previousSuppressionReason
|
|
571
|
+
previousSuppressionReason = reason
|
|
572
|
+
val mapping = AudioFocusErrorMapping.mapSuppressionReason(reason, previous) ?: return
|
|
573
|
+
commandHandler?.onAudioFocusEvent(mapping)
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Receives [AudioManager.ACTION_AUDIO_BECOMING_NOISY] (headphone
|
|
579
|
+
* unplug, A2DP disconnect, USB DAC removal). Media3 already pauses
|
|
580
|
+
* playback in response to this broadcast via
|
|
581
|
+
* `setHandleAudioBecomingNoisy(true)` on the bound player; the
|
|
582
|
+
* receiver is purely a JS-event side-channel so consumers can render
|
|
583
|
+
* UX (e.g. "Paused — headphones disconnected" toast) without
|
|
584
|
+
* inferring the cause from a state-change emit. The receiver is NOT
|
|
585
|
+
* coupled to a specific player instance — [configurePlayer] swaps
|
|
586
|
+
* the ExoPlayer without disturbing the registration; the next
|
|
587
|
+
* broadcast routes through whatever handler is currently registered.
|
|
588
|
+
*/
|
|
589
|
+
private val becomingNoisyReceiver = object : BroadcastReceiver() {
|
|
590
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
591
|
+
if (intent.action != AudioManager.ACTION_AUDIO_BECOMING_NOISY) return
|
|
592
|
+
commandHandler?.onBecomingNoisy()
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Tracks whether [becomingNoisyReceiver] is currently registered so
|
|
598
|
+
* [onDestroy] is idempotent — `unregisterReceiver` throws
|
|
599
|
+
* `IllegalArgumentException` for receivers that aren't registered,
|
|
600
|
+
* which would surface if a service ever transitions through
|
|
601
|
+
* `onDestroy` more than once (e.g. a partial-init failure path or a
|
|
602
|
+
* test harness that walks the lifecycle twice).
|
|
603
|
+
*/
|
|
604
|
+
private var becomingNoisyReceiverRegistered = false
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* The receiver's CastPlayer while a Chromecast session is active —
|
|
608
|
+
* the MediaSession is bound to it so the system notification +
|
|
609
|
+
* lockscreen follow the receiver. Non-null only while cast-bound;
|
|
610
|
+
* main-thread only. The local engine keeps running idle underneath.
|
|
611
|
+
*/
|
|
612
|
+
private var castSessionPlayer: Player? = null
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* The local engine's MediaSession player — what is bound when not
|
|
616
|
+
* casting. Kept current by [swapEngineInternal] so a cast disconnect
|
|
617
|
+
* restores the right instance even if the engine changed (gapless↔
|
|
618
|
+
* crossfade) mid-cast.
|
|
619
|
+
*/
|
|
620
|
+
private var localSessionPlayer: Player? = null
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Stable wrapper the [mediaSession] is bound to for its lifetime.
|
|
624
|
+
* External-controller commands route through its `handle*` overrides
|
|
625
|
+
* (queue-aware skip + system-reason stamping); engine and cast swaps
|
|
626
|
+
* retarget its delegate via [SessionCommandForwardingPlayer.retarget]
|
|
627
|
+
* rather than re-binding the session player.
|
|
628
|
+
*/
|
|
629
|
+
private var sessionForwardingPlayer: SessionCommandForwardingPlayer? = null
|
|
630
|
+
|
|
631
|
+
// Remote-control config (lock-screen / notification skip mode + jump
|
|
632
|
+
// intervals). Set from TrackPlayer via [LocalBinder.setRemoteControls];
|
|
633
|
+
// drives the notification button layout + the forwarding player's intervals.
|
|
634
|
+
private var remoteSkipMode: RemoteSkipMode = RemoteSkipMode.TRACK
|
|
635
|
+
private var remoteForwardMs: Long = 15_000
|
|
636
|
+
private var remoteBackwardMs: Long = 15_000
|
|
637
|
+
// Mirrors PlayerConfig.clampSeekToBuffered so the forwarding player's
|
|
638
|
+
// interval skip-forward obeys the same clamp as a JS seekTo. Survives a
|
|
639
|
+
// session rebuild (re-applied below); re-pushed by TrackPlayer on rebind.
|
|
640
|
+
private var clampSeekToBuffered: Boolean = false
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Main-thread scope observing [PlaybackStateRouter.activeFlow] to bind
|
|
644
|
+
* the receiver's CastPlayer onto the MediaSession on cast connect and
|
|
645
|
+
* restore the local player on disconnect. Cancelled in [onDestroy].
|
|
646
|
+
* AirPlay never reaches here — `activeFlow` is RemotePlayer-only and a
|
|
647
|
+
* PcmStream (AirPlay) session is not a RemotePlayer.
|
|
648
|
+
*/
|
|
649
|
+
private val castBindScope = kotlinx.coroutines.CoroutineScope(
|
|
650
|
+
kotlinx.coroutines.SupervisorJob() + kotlinx.coroutines.Dispatchers.Main.immediate
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
override fun onCreate() {
|
|
654
|
+
super.onCreate()
|
|
655
|
+
// Channel must exist before Media3 posts; `DefaultMediaNotificationProvider`
|
|
656
|
+
// does not create it. Android dedups by ID, so re-creation on each
|
|
657
|
+
// onCreate is idempotent.
|
|
658
|
+
createNotificationChannel()
|
|
659
|
+
engine = GaplessEngine(this)
|
|
660
|
+
exoPlayer.addListener(audioFocusListener)
|
|
661
|
+
audioFocusListenerHost = exoPlayer
|
|
662
|
+
localSessionPlayer = exoPlayer
|
|
663
|
+
// Unique per-onCreate session ID. Media3 keeps a process-wide
|
|
664
|
+
// static `MediaSession` registry keyed by ID — `MediaSession`'s
|
|
665
|
+
// constructor throws `Session ID must be unique` if a stale
|
|
666
|
+
// session for the same ID is still in the map. Service lifecycle
|
|
667
|
+
// is async (onDestroy of an old instance can race with onCreate
|
|
668
|
+
// of a fresh one), so a per-instance UUID is the only fully
|
|
669
|
+
// race-free strategy.
|
|
670
|
+
val callback = PlaybackServiceCallback(callbackEnvironment)
|
|
671
|
+
// Bind the session to a stable forwarding wrapper so external-
|
|
672
|
+
// controller commands route through its handle* overrides. The
|
|
673
|
+
// session + handler are read lazily: `mediaSession` is assigned just
|
|
674
|
+
// below, and `commandHandler` is registered later by TrackPlayer.
|
|
675
|
+
val forwardingPlayer = SessionCommandForwardingPlayer(
|
|
676
|
+
exoPlayer,
|
|
677
|
+
session = { mediaSession },
|
|
678
|
+
commandHandler = { commandHandler },
|
|
679
|
+
)
|
|
680
|
+
sessionForwardingPlayer = forwardingPlayer
|
|
681
|
+
// Re-apply any remote-control config set before this session existed (or
|
|
682
|
+
// carried across a reconfigure); the notification layout reads
|
|
683
|
+
// `remoteSkipMode` in `buildMediaButtonPreferences()` below.
|
|
684
|
+
forwardingPlayer.forwardJumpMs = remoteForwardMs
|
|
685
|
+
forwardingPlayer.backwardJumpMs = remoteBackwardMs
|
|
686
|
+
forwardingPlayer.clampSeekToBuffered = clampSeekToBuffered
|
|
687
|
+
val session = MediaLibrarySession.Builder(this, forwardingPlayer, callback)
|
|
688
|
+
.setId(UUID.randomUUID().toString())
|
|
689
|
+
.setBitmapLoader(buildBitmapLoader())
|
|
690
|
+
.setMediaButtonPreferences(buildMediaButtonPreferences())
|
|
691
|
+
// Skip when consumer is launcher-less (rare service-only consumer); the
|
|
692
|
+
// session has no sessionActivity in that case, so the notification body
|
|
693
|
+
// is built without a content intent.
|
|
694
|
+
.also { builder -> buildSessionActivityPendingIntent()?.let(builder::setSessionActivity) }
|
|
695
|
+
.build()
|
|
696
|
+
mediaSession = session
|
|
697
|
+
addSession(session)
|
|
698
|
+
observeCastBinding()
|
|
699
|
+
// Routes Media3's foreground-promotion notification through this
|
|
700
|
+
// channel + notification ID and uses the lib's
|
|
701
|
+
// [NotificationProvider] subclass; the provider reads media-button
|
|
702
|
+
// preferences off the session above to populate the compact view.
|
|
703
|
+
setMediaNotificationProvider(NotificationProvider(this))
|
|
704
|
+
// ContextCompat handles the API < 34 fallback. Explicit
|
|
705
|
+
// RECEIVER_NOT_EXPORTED satisfies the Android 14 requirement for
|
|
706
|
+
// dynamically-registered receivers and matches the lint guidance
|
|
707
|
+
// for system broadcasts that the app does not need to expose to
|
|
708
|
+
// other UIDs.
|
|
709
|
+
ContextCompat.registerReceiver(
|
|
710
|
+
this,
|
|
711
|
+
becomingNoisyReceiver,
|
|
712
|
+
IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY),
|
|
713
|
+
ContextCompat.RECEIVER_NOT_EXPORTED,
|
|
714
|
+
)
|
|
715
|
+
becomingNoisyReceiverRegistered = true
|
|
716
|
+
|
|
717
|
+
// Wire the equalizer's IEqualizerEngine to ExoPlayer's audio
|
|
718
|
+
// session as soon as the engine publishes a session ID. The
|
|
719
|
+
// Equalizer Hybrid is a JS-side singleton; the static reference
|
|
720
|
+
// captures the most recently constructed instance which always
|
|
721
|
+
// matches the JS getEqualizer() singleton in practice. The
|
|
722
|
+
// Visualizer Hybrid follows the same pattern — its FFT
|
|
723
|
+
// processors attach via the engine's bufferSinks accumulator.
|
|
724
|
+
setActiveEngine(engine)
|
|
725
|
+
Equalizer.current?.wireToPlaybackEngine(engine)
|
|
726
|
+
Visualizer.current?.wireToPlaybackEngine(engine)
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Resolve the consumer app's launcher activity at runtime and wrap
|
|
731
|
+
* it in a [PendingIntent] that Media3 hands to
|
|
732
|
+
* `DefaultMediaNotificationProvider` as the notification's content
|
|
733
|
+
* intent. Returns `null` when the consumer process exposes no
|
|
734
|
+
* launcher activity (rare service-only hosts) so the
|
|
735
|
+
* `setSessionActivity` call site can skip the wiring without
|
|
736
|
+
* crashing the service.
|
|
737
|
+
*
|
|
738
|
+
* `FLAG_ACTIVITY_SINGLE_TOP | FLAG_ACTIVITY_CLEAR_TOP` matches the
|
|
739
|
+
* standard "tap to return to running app" semantics — reuse the
|
|
740
|
+
* existing task instead of spawning a duplicate, and clear any
|
|
741
|
+
* activities stacked on top of the launcher so the user lands
|
|
742
|
+
* back on the app's root.
|
|
743
|
+
*
|
|
744
|
+
* `FLAG_IMMUTABLE` satisfies API 31+'s mutability requirement
|
|
745
|
+
* (Media3 does not mutate the intent) and `FLAG_UPDATE_CURRENT`
|
|
746
|
+
* keeps a single PendingIntent slot per [REQUEST_CODE_OPEN_APP]
|
|
747
|
+
* across service-recreate cycles.
|
|
748
|
+
*/
|
|
749
|
+
private fun buildSessionActivityPendingIntent(): PendingIntent? {
|
|
750
|
+
val launchIntent = packageManager.getLaunchIntentForPackage(packageName) ?: return null
|
|
751
|
+
launchIntent.flags = launchIntent.flags or
|
|
752
|
+
Intent.FLAG_ACTIVITY_SINGLE_TOP or
|
|
753
|
+
Intent.FLAG_ACTIVITY_CLEAR_TOP
|
|
754
|
+
return PendingIntent.getActivity(
|
|
755
|
+
this,
|
|
756
|
+
REQUEST_CODE_OPEN_APP,
|
|
757
|
+
launchIntent,
|
|
758
|
+
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
|
759
|
+
)
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Build the BitmapLoader chain Media3 uses to resolve artwork for
|
|
764
|
+
* the lock-screen + notification surface:
|
|
765
|
+
* PlaceholderFallbackBitmapLoader → CacheBitmapLoader →
|
|
766
|
+
* DataSourceBitmapLoader.
|
|
767
|
+
*
|
|
768
|
+
* `DataSourceBitmapLoader` reuses Media3's own DataSource stack
|
|
769
|
+
* (the same one ExoPlayer drives for HTTP playback), so consumer
|
|
770
|
+
* `userAgent` + custom HTTP headers configured on the player's
|
|
771
|
+
* `MediaSource.Factory` propagate into artwork fetches without a
|
|
772
|
+
* second HTTP client. `CacheBitmapLoader` deduplicates repeat
|
|
773
|
+
* requests for the same URI inside a single foreground session.
|
|
774
|
+
* The outer [PlaceholderFallbackBitmapLoader] guarantees a bitmap
|
|
775
|
+
* always reaches the notification — the OEM-skin failure mode
|
|
776
|
+
* where a null bitmap drops the transport controls is closed.
|
|
777
|
+
*
|
|
778
|
+
* Resolving the placeholder bitmap performs a synchronous read of
|
|
779
|
+
* `res/raw/rnqp_placeholder.jpg` once per [onCreate] (~10KB JPEG,
|
|
780
|
+
* ~5–15ms). Acceptable on the service-creation path; the result
|
|
781
|
+
* is cached for the service's lifetime by [PlaceholderArtwork].
|
|
782
|
+
*/
|
|
783
|
+
@VisibleForTesting
|
|
784
|
+
internal fun buildBitmapLoader(): PlaceholderFallbackBitmapLoader =
|
|
785
|
+
PlaceholderFallbackBitmapLoader(
|
|
786
|
+
delegate = CacheBitmapLoader(DataSourceBitmapLoader.Builder(this).build()),
|
|
787
|
+
placeholder = PlaceholderArtwork.bitmap(this),
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* The compact notification view shows three buttons in priority
|
|
792
|
+
* order: prev / play-pause / next. Media3 1.9+ resolves these from
|
|
793
|
+
* standard player commands set via [CommandButton.Builder.setPlayerCommand]
|
|
794
|
+
* — no custom command handlers required. The expanded view shows
|
|
795
|
+
* the same three buttons plus the scrub progress bar that
|
|
796
|
+
* `DefaultMediaNotificationProvider` adds automatically when the
|
|
797
|
+
* session reports a known duration.
|
|
798
|
+
*/
|
|
799
|
+
private fun buildMediaButtonPreferences(): List<CommandButton> {
|
|
800
|
+
val playPause = CommandButton.Builder(CommandButton.ICON_PLAY)
|
|
801
|
+
.setPlayerCommand(Player.COMMAND_PLAY_PAUSE)
|
|
802
|
+
.setSlots(CommandButton.SLOT_CENTRAL)
|
|
803
|
+
.setDisplayName(getString(R.string.rnqp_action_play_pause))
|
|
804
|
+
.build()
|
|
805
|
+
// In `interval` mode the back/forward slots jump within the current track
|
|
806
|
+
// (COMMAND_SEEK_BACK / COMMAND_SEEK_FORWARD, honored by
|
|
807
|
+
// [SessionCommandForwardingPlayer] with the runtime interval); in `track`
|
|
808
|
+
// mode they are previous/next-track.
|
|
809
|
+
return when (remoteSkipMode) {
|
|
810
|
+
RemoteSkipMode.INTERVAL -> listOf(
|
|
811
|
+
CommandButton.Builder(CommandButton.ICON_SKIP_BACK)
|
|
812
|
+
.setPlayerCommand(Player.COMMAND_SEEK_BACK)
|
|
813
|
+
.setSlots(CommandButton.SLOT_BACK)
|
|
814
|
+
.setDisplayName(getString(R.string.rnqp_action_jump_backward))
|
|
815
|
+
.build(),
|
|
816
|
+
playPause,
|
|
817
|
+
CommandButton.Builder(CommandButton.ICON_SKIP_FORWARD)
|
|
818
|
+
.setPlayerCommand(Player.COMMAND_SEEK_FORWARD)
|
|
819
|
+
.setSlots(CommandButton.SLOT_FORWARD)
|
|
820
|
+
.setDisplayName(getString(R.string.rnqp_action_jump_forward))
|
|
821
|
+
.build(),
|
|
822
|
+
)
|
|
823
|
+
RemoteSkipMode.TRACK -> listOf(
|
|
824
|
+
CommandButton.Builder(CommandButton.ICON_PREVIOUS)
|
|
825
|
+
.setPlayerCommand(Player.COMMAND_SEEK_TO_PREVIOUS)
|
|
826
|
+
.setSlots(CommandButton.SLOT_BACK)
|
|
827
|
+
.setDisplayName(getString(R.string.rnqp_action_previous))
|
|
828
|
+
.build(),
|
|
829
|
+
playPause,
|
|
830
|
+
CommandButton.Builder(CommandButton.ICON_NEXT)
|
|
831
|
+
.setPlayerCommand(Player.COMMAND_SEEK_TO_NEXT)
|
|
832
|
+
.setSlots(CommandButton.SLOT_FORWARD)
|
|
833
|
+
.setDisplayName(getString(R.string.rnqp_action_next))
|
|
834
|
+
.build(),
|
|
835
|
+
)
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Apply a remote-control config: point the forwarding player at the new
|
|
841
|
+
* jump intervals and swap the notification button layout live. Called from
|
|
842
|
+
* [LocalBinder.setRemoteControls] on the main thread.
|
|
843
|
+
*/
|
|
844
|
+
internal fun applyRemoteControls(mode: RemoteSkipMode, forwardMs: Long, backwardMs: Long) {
|
|
845
|
+
remoteSkipMode = mode
|
|
846
|
+
remoteForwardMs = forwardMs
|
|
847
|
+
remoteBackwardMs = backwardMs
|
|
848
|
+
sessionForwardingPlayer?.forwardJumpMs = forwardMs
|
|
849
|
+
sessionForwardingPlayer?.backwardJumpMs = backwardMs
|
|
850
|
+
mediaSession?.setMediaButtonPreferences(buildMediaButtonPreferences())
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Mirror `PlayerConfig.clampSeekToBuffered` onto the forwarding player so the
|
|
855
|
+
* controller interval skip-forward obeys the same clamp as a JS `seekTo`.
|
|
856
|
+
* Called from [LocalBinder.setClampSeekToBuffered] on the main thread.
|
|
857
|
+
*/
|
|
858
|
+
internal fun applyClampSeekToBuffered(enabled: Boolean) {
|
|
859
|
+
clampSeekToBuffered = enabled
|
|
860
|
+
sessionForwardingPlayer?.clampSeekToBuffered = enabled
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Create the playback NotificationChannel. No-op below API 26
|
|
865
|
+
* (channels do not exist). Settings tune the channel as a silent,
|
|
866
|
+
* lockscreen-visible transport surface — not a message indicator.
|
|
867
|
+
*/
|
|
868
|
+
private fun createNotificationChannel() {
|
|
869
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
|
870
|
+
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
871
|
+
val channel = NotificationChannel(
|
|
872
|
+
NOTIFICATION_CHANNEL_ID,
|
|
873
|
+
getString(R.string.rnqp_playback_channel_name),
|
|
874
|
+
NotificationManager.IMPORTANCE_LOW
|
|
875
|
+
).apply {
|
|
876
|
+
description = getString(R.string.rnqp_playback_channel_description)
|
|
877
|
+
setShowBadge(false)
|
|
878
|
+
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
|
879
|
+
}
|
|
880
|
+
manager.createNotificationChannel(channel)
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Replace the ExoPlayer attached to the current MediaSession with
|
|
885
|
+
* one built around the supplied [factory]. The session itself is
|
|
886
|
+
* NOT recreated — Media3 supports `MediaSession.setPlayer(...)`
|
|
887
|
+
* for atomic player swaps, which keeps the session ID stable +
|
|
888
|
+
* avoids the "Session ID must be unique" static-registry collision.
|
|
889
|
+
*
|
|
890
|
+
* Called from `TrackPlayer.configureInternal` when the consumer's
|
|
891
|
+
* `PlayerConfig` headers / userAgent / audioContentType change —
|
|
892
|
+
* Media3 doesn't support swapping a `MediaSource.Factory` on a
|
|
893
|
+
* live player, so the factory is bound at player build time. The
|
|
894
|
+
* `audioContentType` likewise feeds into the new player's
|
|
895
|
+
* `AudioAttributes` and is re-applied on every swap.
|
|
896
|
+
*/
|
|
897
|
+
fun configurePlayer(
|
|
898
|
+
factory: MediaSource.Factory,
|
|
899
|
+
audioContentType: AudioContentType? = null
|
|
900
|
+
): ExoPlayer {
|
|
901
|
+
val newEngine = GaplessEngine(
|
|
902
|
+
context = this,
|
|
903
|
+
mediaSourceFactory = factory,
|
|
904
|
+
audioContentType = audioContentType
|
|
905
|
+
)
|
|
906
|
+
return swapEngineInternal(newEngine).mediaSessionPlayer
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Mode-switch entry point — replace the active engine with one
|
|
911
|
+
* implementing the consumer's requested `PlaybackMode`. Called
|
|
912
|
+
* by `TrackPlayer.setPlaybackMode` when the
|
|
913
|
+
* `PlaybackModeStateMachine` returns `ImmediateSwap`.
|
|
914
|
+
*
|
|
915
|
+
* Sibling to [configurePlayer]: `configurePlayer` rebuilds the
|
|
916
|
+
* engine when the consumer's `PlayerConfig` changes and always
|
|
917
|
+
* stays on `GaplessEngine`; `swapEngine` flips between
|
|
918
|
+
* `GaplessEngine` and `CrossfadeEngine` when the mode changes.
|
|
919
|
+
*
|
|
920
|
+
* Same service-level wiring: re-attach `audioFocusListener` to
|
|
921
|
+
* the new leading player, swap `MediaSession` to the new
|
|
922
|
+
* player, carry the delegate over, re-wire the Equalizer, and
|
|
923
|
+
* release the old engine. Queue migration is the caller's
|
|
924
|
+
* responsibility — `TrackPlayer` re-installs the canonical
|
|
925
|
+
* queue model onto the new engine before the swap if needed.
|
|
926
|
+
*/
|
|
927
|
+
fun swapEngine(newEngine: PlaybackEngine): PlaybackEngine {
|
|
928
|
+
return swapEngineInternal(newEngine)
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
private fun swapEngineInternal(newEngine: PlaybackEngine): PlaybackEngine {
|
|
932
|
+
val oldEngine = engine
|
|
933
|
+
val oldPlayer = oldEngine.mediaSessionPlayer
|
|
934
|
+
val newPlayer = newEngine.mediaSessionPlayer
|
|
935
|
+
|
|
936
|
+
// Bind MediaSession to a stable wrapper for CrossfadeEngine so
|
|
937
|
+
// the lock-screen scrubber survives mid-fade role swaps. The
|
|
938
|
+
// wrapper retargets via setPlayer + invalidateState whenever the
|
|
939
|
+
// engine flips currentRoleIsA. GaplessEngine binds directly —
|
|
940
|
+
// single ExoPlayer, no role to swap.
|
|
941
|
+
val sessionPlayer: Player = if (newEngine is CrossfadeEngine) {
|
|
942
|
+
val wrapper = CrossfadeForwardingPlayer(newEngine)
|
|
943
|
+
newEngine.onRoleSwap = { wrapper.onRoleSwapped() }
|
|
944
|
+
newEngine.onCrossfadeBegin = { wrapper.onCrossfadeBegin() }
|
|
945
|
+
// Mid-fade cancel re-binds the wrapper back to the leading
|
|
946
|
+
// leg — `onRoleSwapped` already does setPlayer(leadingPlayer)
|
|
947
|
+
// + invalidateState which is exactly what we need on cancel
|
|
948
|
+
// (no role flip happened, leading is still leading).
|
|
949
|
+
newEngine.onCrossfadeCancel = { wrapper.onRoleSwapped() }
|
|
950
|
+
wrapper
|
|
951
|
+
} else {
|
|
952
|
+
newPlayer
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
audioFocusListenerHost?.removeListener(audioFocusListener)
|
|
956
|
+
previousSuppressionReason = Player.PLAYBACK_SUPPRESSION_REASON_NONE
|
|
957
|
+
// Audio-focus listener attaches to whatever is bound to the
|
|
958
|
+
// MediaSession — for CrossfadeEngine that's the wrapper which
|
|
959
|
+
// forwards Player.Listener events from whichever leg is
|
|
960
|
+
// currently leading via ForwardingSimpleBasePlayer's listener
|
|
961
|
+
// routing. For GaplessEngine the wrapper IS the underlying
|
|
962
|
+
// player. Either way, the listener follows role swaps without
|
|
963
|
+
// leaking against a specific leg. Tracked in
|
|
964
|
+
// audioFocusListenerHost so the next swap can remove from the
|
|
965
|
+
// same instance.
|
|
966
|
+
localSessionPlayer = sessionPlayer
|
|
967
|
+
// While cast-bound the MediaSession stays on the CastPlayer; record
|
|
968
|
+
// the new engine's player so the cast disconnect binds it, but don't
|
|
969
|
+
// steal the session surface from the receiver here.
|
|
970
|
+
if (castSessionPlayer == null) {
|
|
971
|
+
sessionPlayer.addListener(audioFocusListener)
|
|
972
|
+
audioFocusListenerHost = sessionPlayer
|
|
973
|
+
sessionForwardingPlayer?.retarget(sessionPlayer)
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
newEngine.delegate = oldEngine.delegate
|
|
977
|
+
engine = newEngine
|
|
978
|
+
|
|
979
|
+
setActiveEngine(engine)
|
|
980
|
+
Equalizer.current?.wireToPlaybackEngine(engine)
|
|
981
|
+
Visualizer.current?.wireToPlaybackEngine(engine)
|
|
982
|
+
oldEngine.release()
|
|
983
|
+
return newEngine
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Observe the active Chromecast session and bind/unbind its CastPlayer
|
|
988
|
+
* onto the MediaSession. The collector runs on the main thread because
|
|
989
|
+
* both `MediaSession.setPlayer` and the lazy `CastPlayer` construction
|
|
990
|
+
* (`session.mediaSessionPlayer`) are main-thread-bound.
|
|
991
|
+
*/
|
|
992
|
+
private fun observeCastBinding() {
|
|
993
|
+
castBindScope.launch {
|
|
994
|
+
PlaybackStateRouter.activeFlow.collect { session ->
|
|
995
|
+
val castPlayer = session?.mediaSessionPlayer
|
|
996
|
+
if (castPlayer != null) bindCastSessionPlayer(castPlayer)
|
|
997
|
+
else unbindCastSessionPlayer()
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Bind the receiver's CastPlayer to the MediaSession for the cast
|
|
1004
|
+
* session's duration so the system notification + lockscreen follow
|
|
1005
|
+
* the receiver. The receiver owns audio, so the local audio-focus
|
|
1006
|
+
* listener is detached (it tracks LOCAL focus, irrelevant while the
|
|
1007
|
+
* device isn't decoding). Idempotent; main thread only.
|
|
1008
|
+
*/
|
|
1009
|
+
private fun bindCastSessionPlayer(castPlayer: Player) {
|
|
1010
|
+
if (castSessionPlayer === castPlayer) return
|
|
1011
|
+
audioFocusListenerHost?.removeListener(audioFocusListener)
|
|
1012
|
+
audioFocusListenerHost = null
|
|
1013
|
+
castSessionPlayer = castPlayer
|
|
1014
|
+
sessionForwardingPlayer?.retarget(castPlayer)
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Restore the local engine's player on cast disconnect, reattach the
|
|
1019
|
+
* audio-focus listener, then release the cast player — setPlayer
|
|
1020
|
+
* before release, the same ordering [swapEngineInternal] uses, so the
|
|
1021
|
+
* MediaSession never points at a released player. The CastPlayer's
|
|
1022
|
+
* binding lifetime is owned here (ChromecastSession builds it but does
|
|
1023
|
+
* not release it). No-op when not cast-bound; main thread only.
|
|
1024
|
+
*/
|
|
1025
|
+
private fun unbindCastSessionPlayer() {
|
|
1026
|
+
val castPlayer = castSessionPlayer ?: return
|
|
1027
|
+
castSessionPlayer = null
|
|
1028
|
+
val local = localSessionPlayer ?: engine.mediaSessionPlayer
|
|
1029
|
+
audioFocusListenerHost?.removeListener(audioFocusListener)
|
|
1030
|
+
previousSuppressionReason = Player.PLAYBACK_SUPPRESSION_REASON_NONE
|
|
1031
|
+
local.addListener(audioFocusListener)
|
|
1032
|
+
audioFocusListenerHost = local
|
|
1033
|
+
sessionForwardingPlayer?.retarget(local)
|
|
1034
|
+
castPlayer.release()
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? =
|
|
1038
|
+
mediaSession
|
|
1039
|
+
|
|
1040
|
+
override fun onBind(intent: Intent?): IBinder? {
|
|
1041
|
+
if (intent?.action == ACTION_LOCAL_BIND) {
|
|
1042
|
+
return localBinder
|
|
1043
|
+
}
|
|
1044
|
+
return super.onBind(intent)
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Path B of the media-button routing. A raw media-button intent
|
|
1049
|
+
* reaches the service either because a manifest-registered
|
|
1050
|
+
* `MediaButtonReceiver` forwarded it, or because the OS started
|
|
1051
|
+
* the service directly with `ACTION_MEDIA_BUTTON`. The dispatcher
|
|
1052
|
+
* dedups against any prior fire of the same physical press routed
|
|
1053
|
+
* through path A (`onMediaButtonEvent`). Unrecognised actions are
|
|
1054
|
+
* ignored. The `super` call keeps `MediaLibraryService` lifecycle
|
|
1055
|
+
* semantics intact.
|
|
1056
|
+
*/
|
|
1057
|
+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
1058
|
+
if (intent?.action == Intent.ACTION_MEDIA_BUTTON) {
|
|
1059
|
+
mediaButtonDispatch?.handle(intent, RemoteCommandDeduper.Channel.BROADCAST)
|
|
1060
|
+
}
|
|
1061
|
+
return super.onStartCommand(intent, flags, startId)
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* Swiping the app away from recents is an explicit user kill: stop playback,
|
|
1066
|
+
* clear the notification, and stop the service so the process is reaped.
|
|
1067
|
+
* Unconditional by design — a paused-but-queued session is NOT kept alive
|
|
1068
|
+
* here (resurrecting audio the user just dismissed would be wrong). This is
|
|
1069
|
+
* distinct from home-backgrounding, where the foreground service keeps
|
|
1070
|
+
* playing. `stopSelf()` on this explicit action also prevents the
|
|
1071
|
+
* `START_STICKY` recreation the OS applies only to system-killed services.
|
|
1072
|
+
*
|
|
1073
|
+
* Deliberately does not call `super.onTaskRemoved` (whose only job is a
|
|
1074
|
+
* conditional `pauseAllPlayersAndStopSelf`): this service owns exactly one
|
|
1075
|
+
* session, so stopping its player + the explicit `stopSelf` below is the
|
|
1076
|
+
* complete kill. A future multi-session change would need to stop each.
|
|
1077
|
+
*/
|
|
1078
|
+
override fun onTaskRemoved(rootIntent: Intent?) {
|
|
1079
|
+
mediaSession?.player?.stop()
|
|
1080
|
+
stopForeground(Service.STOP_FOREGROUND_REMOVE)
|
|
1081
|
+
stopSelf()
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
override fun onDestroy() {
|
|
1085
|
+
callbackDispatchScope.cancel()
|
|
1086
|
+
browseCallbacks.clear()
|
|
1087
|
+
pendingBrowseRequests.clear()
|
|
1088
|
+
// Drop any pending idle stopSelf check — the service is going
|
|
1089
|
+
// away anyway, no need to fire the re-check on a dead instance.
|
|
1090
|
+
stopSelfHandler.removeCallbacks(stopSelfRunnable)
|
|
1091
|
+
if (becomingNoisyReceiverRegistered) {
|
|
1092
|
+
unregisterReceiver(becomingNoisyReceiver)
|
|
1093
|
+
becomingNoisyReceiverRegistered = false
|
|
1094
|
+
}
|
|
1095
|
+
// Detach the equalizer + visualizer before tearing the engine
|
|
1096
|
+
// down so the session-ID listener + audio-buffer sinks don't
|
|
1097
|
+
// fire against a released engine.
|
|
1098
|
+
setActiveEngine(null)
|
|
1099
|
+
Equalizer.current?.unwireFromPlaybackEngine()
|
|
1100
|
+
Visualizer.current?.unwireFromPlaybackEngine()
|
|
1101
|
+
audioFocusListenerHost?.removeListener(audioFocusListener)
|
|
1102
|
+
audioFocusListenerHost = null
|
|
1103
|
+
castBindScope.cancel()
|
|
1104
|
+
castSessionPlayer?.release()
|
|
1105
|
+
castSessionPlayer = null
|
|
1106
|
+
commandHandler = null
|
|
1107
|
+
mediaButtonDispatch = null
|
|
1108
|
+
mediaSession?.release()
|
|
1109
|
+
mediaSession = null
|
|
1110
|
+
sessionForwardingPlayer = null
|
|
1111
|
+
engine.release()
|
|
1112
|
+
super.onDestroy()
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
/**
|
|
1116
|
+
* Coroutine scope tied to the service lifetime. Headless callback
|
|
1117
|
+
* dispatches (e.g. an Auto search awaiting JS) launch on this
|
|
1118
|
+
* scope so they cancel cleanly when [onDestroy] fires.
|
|
1119
|
+
*
|
|
1120
|
+
* `Dispatchers.IO` (not `Main`): the dispatch body is suspending
|
|
1121
|
+
* await on a Nitro Promise round-trip — never touches Player or
|
|
1122
|
+
* UI state — so blocking Main here would needlessly stall Media3
|
|
1123
|
+
* + UI. Future consumers run on `directExecutor()` and don't
|
|
1124
|
+
* inherit this dispatcher.
|
|
1125
|
+
*/
|
|
1126
|
+
private val callbackDispatchScope =
|
|
1127
|
+
kotlinx.coroutines.CoroutineScope(
|
|
1128
|
+
kotlinx.coroutines.SupervisorJob() + kotlinx.coroutines.Dispatchers.IO
|
|
1129
|
+
)
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* Dispatch [query] to the JS-registered `onSearchRequest`
|
|
1133
|
+
* callback, await the result, write the items into
|
|
1134
|
+
* [searchResultCache], and emit `notifySearchResultChanged` for
|
|
1135
|
+
* every connected controller so they re-pull via
|
|
1136
|
+
* `getSearchResult`. No-op when no callback is registered.
|
|
1137
|
+
*
|
|
1138
|
+
* Threading: launched on [callbackDispatchScope] so the await
|
|
1139
|
+
* doesn't block the Media3 callback thread + cancels when the
|
|
1140
|
+
* service is destroyed. JS callbacks return a Nitro
|
|
1141
|
+
* `Promise<Promise<...>>` (outer = Hybrid wrapper, inner = JS
|
|
1142
|
+
* promise) — both layers are awaited.
|
|
1143
|
+
*/
|
|
1144
|
+
internal fun dispatchSearchToJs(query: String) {
|
|
1145
|
+
val callback = browseCallbacks.searchRequestCallback() ?: return
|
|
1146
|
+
callbackDispatchScope.launch {
|
|
1147
|
+
val items: List<BrowseItem> = try {
|
|
1148
|
+
kotlinx.coroutines.withTimeoutOrNull(SEARCH_DISPATCH_TIMEOUT_MS) {
|
|
1149
|
+
callback(query).await().await().toList()
|
|
1150
|
+
} ?: emptyList()
|
|
1151
|
+
} catch (t: Throwable) {
|
|
1152
|
+
// JS handler threw — surface as an empty answered result so
|
|
1153
|
+
// the controller stops waiting. The thrown error is the
|
|
1154
|
+
// consumer's bug; logging helps diagnose without taking the
|
|
1155
|
+
// process down.
|
|
1156
|
+
android.util.Log.w(
|
|
1157
|
+
LOG_TAG,
|
|
1158
|
+
"onSearchRequest('$query') threw — surfacing empty results",
|
|
1159
|
+
t
|
|
1160
|
+
)
|
|
1161
|
+
emptyList()
|
|
1162
|
+
}
|
|
1163
|
+
searchResultCache.setResults(query, items)
|
|
1164
|
+
notifySearchResultChanged(query, items.size)
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
* Dispatch a structured voice-search [request] (Google Assistant,
|
|
1170
|
+
* Auto voice tile, future Siri intent extension) to the
|
|
1171
|
+
* JS-registered `onPlayFromSearchRequest` handler. Awaits the
|
|
1172
|
+
* resolved [TrackItem] list, applies it to the lib's authoritative
|
|
1173
|
+
* queue state, and returns the [MediaItem] list Media3's framework
|
|
1174
|
+
* should set on the player.
|
|
1175
|
+
*
|
|
1176
|
+
* On a cold-start Assistant boot, the JS bridge may not yet have
|
|
1177
|
+
* registered the callback when `onAddMediaItems` lands. The
|
|
1178
|
+
* coroutine waits up to [HEADLESS_DISPATCH_TIMEOUT_MS] for the
|
|
1179
|
+
* registration to land before timing out + returning an empty
|
|
1180
|
+
* list (which surfaces as "no results" to Assistant — the user
|
|
1181
|
+
* retries).
|
|
1182
|
+
*/
|
|
1183
|
+
internal fun dispatchPlayFromSearchToJs(
|
|
1184
|
+
request: MediaSearchRequest
|
|
1185
|
+
): ListenableFuture<List<MediaItem>> = dispatchAssistantPlay(
|
|
1186
|
+
methodLabel = "onPlayFromSearchRequest",
|
|
1187
|
+
awaitSlot = { browseCallbacks.awaitPlayFromSearchRequestCallback() }
|
|
1188
|
+
) { browseCallbacks.playFromSearchRequestCallback()?.invoke(request) }
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* Dispatch a browse-leaf-tap [mediaId] (Auto / Wear OS browse-row
|
|
1192
|
+
* tap) to the JS-registered `onPlayFromIdRequest` handler. Same
|
|
1193
|
+
* cold-start race-handling + queue-sync flow as
|
|
1194
|
+
* [dispatchPlayFromSearchToJs].
|
|
1195
|
+
*/
|
|
1196
|
+
internal fun dispatchPlayFromIdToJs(
|
|
1197
|
+
mediaId: String
|
|
1198
|
+
): ListenableFuture<List<MediaItem>> = dispatchAssistantPlay(
|
|
1199
|
+
methodLabel = "onPlayFromIdRequest",
|
|
1200
|
+
awaitSlot = { browseCallbacks.awaitPlayFromIdRequestCallback() }
|
|
1201
|
+
) { browseCallbacks.playFromIdRequestCallback()?.invoke(mediaId) }
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* Fire-and-forget variant of [dispatchPlayFromIdToJs]: invoke the
|
|
1205
|
+
* JS callback for its `setQueue + play` side effect and discard the
|
|
1206
|
+
* resolved track list. Used by the browse-leaf tap
|
|
1207
|
+
* (`onSetMediaItems` mediaId path) where the JS handler is the
|
|
1208
|
+
* authoritative queue writer; calling the full sync variant would
|
|
1209
|
+
* trigger [TrackPlayer.applyResolvedAssistantQueue] which resets
|
|
1210
|
+
* `currentTrackIndex` to 0 and breaks skip-previous / skip-next
|
|
1211
|
+
* after a tap at any index > 0.
|
|
1212
|
+
*
|
|
1213
|
+
* Carries the same cold-start race handling as the sync variant —
|
|
1214
|
+
* the JS callback may not be registered when the OS hands off the
|
|
1215
|
+
* tap, so the dispatch waits up to [HEADLESS_DISPATCH_TIMEOUT_MS]
|
|
1216
|
+
* for `playFromIdRequestCallback()` to return non-null before
|
|
1217
|
+
* timing out. Errors are logged + swallowed; the JS side is the
|
|
1218
|
+
* surface for user-facing failure handling.
|
|
1219
|
+
*/
|
|
1220
|
+
internal fun dispatchPlayFromIdToJsFireOnly(mediaId: String) {
|
|
1221
|
+
callbackDispatchScope.launch {
|
|
1222
|
+
try {
|
|
1223
|
+
kotlinx.coroutines.withTimeoutOrNull(HEADLESS_DISPATCH_TIMEOUT_MS) {
|
|
1224
|
+
val promise: Promise<Promise<Array<TrackItem>>>? =
|
|
1225
|
+
browseCallbacks.playFromIdRequestCallback()?.invoke(mediaId)
|
|
1226
|
+
?: run {
|
|
1227
|
+
browseCallbacks.awaitPlayFromIdRequestCallback()
|
|
1228
|
+
browseCallbacks.playFromIdRequestCallback()?.invoke(mediaId)
|
|
1229
|
+
}
|
|
1230
|
+
promise?.await()?.await()
|
|
1231
|
+
}
|
|
1232
|
+
} catch (t: Throwable) {
|
|
1233
|
+
android.util.Log.w(
|
|
1234
|
+
LOG_TAG,
|
|
1235
|
+
"onPlayFromIdRequest (fire-only) threw — discarding",
|
|
1236
|
+
t
|
|
1237
|
+
)
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
/** Dispatch an unknown-parent browse drilldown to the JS-registered
|
|
1243
|
+
* `onBrowseRequest` handler. Called by [PlaybackServiceCallback]
|
|
1244
|
+
* when `onGetChildren` fires for a parentId that the cached
|
|
1245
|
+
* snapshot does not contain. */
|
|
1246
|
+
internal fun dispatchBrowseRequestToJs(
|
|
1247
|
+
parentId: String
|
|
1248
|
+
): ListenableFuture<List<BrowseItem>> {
|
|
1249
|
+
val future =
|
|
1250
|
+
SettableFuture.create<List<BrowseItem>>()
|
|
1251
|
+
val job = callbackDispatchScope.launch {
|
|
1252
|
+
val items: List<BrowseItem> = try {
|
|
1253
|
+
kotlinx.coroutines.withTimeoutOrNull(HEADLESS_DISPATCH_TIMEOUT_MS) {
|
|
1254
|
+
val promise: Promise<Promise<Array<BrowseItem>>>? =
|
|
1255
|
+
browseCallbacks.browseRequestCallback()?.invoke(parentId)
|
|
1256
|
+
?: run {
|
|
1257
|
+
browseCallbacks.awaitBrowseRequestCallback()
|
|
1258
|
+
browseCallbacks.browseRequestCallback()?.invoke(parentId)
|
|
1259
|
+
}
|
|
1260
|
+
promise?.await()?.await()?.toList() ?: emptyList()
|
|
1261
|
+
} ?: emptyList()
|
|
1262
|
+
} catch (t: Throwable) {
|
|
1263
|
+
android.util.Log.w(
|
|
1264
|
+
LOG_TAG,
|
|
1265
|
+
"onBrowseRequest('$parentId') threw — surfacing empty children",
|
|
1266
|
+
t
|
|
1267
|
+
)
|
|
1268
|
+
emptyList()
|
|
1269
|
+
}
|
|
1270
|
+
future.set(items)
|
|
1271
|
+
}
|
|
1272
|
+
// Cancel the launched dispatch when the controller drops the
|
|
1273
|
+
// future (Media3 cancels in-flight resolution on disconnect).
|
|
1274
|
+
// Without this the coroutine would still hold the JS Promise
|
|
1275
|
+
// chain alive for a controller that has already given up.
|
|
1276
|
+
future.addListener(
|
|
1277
|
+
Runnable { if (future.isCancelled) job.cancel() },
|
|
1278
|
+
MoreExecutors.directExecutor()
|
|
1279
|
+
)
|
|
1280
|
+
return future
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
private inline fun dispatchAssistantPlay(
|
|
1284
|
+
methodLabel: String,
|
|
1285
|
+
crossinline awaitSlot: suspend () -> Unit,
|
|
1286
|
+
crossinline invoke: () -> Promise<Promise<Array<TrackItem>>>?
|
|
1287
|
+
): ListenableFuture<List<MediaItem>> {
|
|
1288
|
+
val future =
|
|
1289
|
+
SettableFuture.create<List<MediaItem>>()
|
|
1290
|
+
val job = callbackDispatchScope.launch {
|
|
1291
|
+
val tracks: List<TrackItem> = try {
|
|
1292
|
+
kotlinx.coroutines.withTimeoutOrNull(HEADLESS_DISPATCH_TIMEOUT_MS) {
|
|
1293
|
+
// [invoke] reads the registry slot; on null we suspend on
|
|
1294
|
+
// [awaitSlot] (the slot's StateFlow) until a registration
|
|
1295
|
+
// lands. The first non-null read after that wins.
|
|
1296
|
+
val promise = invoke() ?: run {
|
|
1297
|
+
awaitSlot()
|
|
1298
|
+
invoke()
|
|
1299
|
+
}
|
|
1300
|
+
promise?.await()?.await()?.toList() ?: emptyList()
|
|
1301
|
+
} ?: emptyList()
|
|
1302
|
+
} catch (t: Throwable) {
|
|
1303
|
+
android.util.Log.w(
|
|
1304
|
+
LOG_TAG,
|
|
1305
|
+
"$methodLabel threw — surfacing empty result",
|
|
1306
|
+
t
|
|
1307
|
+
)
|
|
1308
|
+
emptyList()
|
|
1309
|
+
}
|
|
1310
|
+
// Sync the lib's authoritative tracks state to the resolved
|
|
1311
|
+
// list. Media3's framework calls `player.setMediaItems(items, ...)`
|
|
1312
|
+
// with whatever this future resolves to, so we MUST return the
|
|
1313
|
+
// real items — returning emptyList here would wipe the queue
|
|
1314
|
+
// immediately after Media3 installs it.
|
|
1315
|
+
val items = applyAssistantQueueOnMain(tracks)
|
|
1316
|
+
future.set(items)
|
|
1317
|
+
}
|
|
1318
|
+
// Cancel the launched dispatch when the controller drops the
|
|
1319
|
+
// future (Media3 cancels in-flight resolution when the controller
|
|
1320
|
+
// disconnects). Without this the coroutine would still run +
|
|
1321
|
+
// mutate the lib's queue state for a controller that gave up.
|
|
1322
|
+
future.addListener(
|
|
1323
|
+
Runnable {
|
|
1324
|
+
if (future.isCancelled) job.cancel()
|
|
1325
|
+
},
|
|
1326
|
+
MoreExecutors.directExecutor()
|
|
1327
|
+
)
|
|
1328
|
+
return future
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
/**
|
|
1332
|
+
* Fire the consumer's `onCarConnect` callback once it's registered.
|
|
1333
|
+
* The FIRST automotive controller's [onConnect] races the JS-bridge
|
|
1334
|
+
* boot — calling [browseCallbacks.carConnectCallback] synchronously
|
|
1335
|
+
* returns `null` until [registerPlaybackService] has run. Suspends
|
|
1336
|
+
* on the slot's StateFlow up to [HEADLESS_DISPATCH_TIMEOUT_MS];
|
|
1337
|
+
* warns on timeout (consumer didn't wire `onCarConnect`).
|
|
1338
|
+
*/
|
|
1339
|
+
internal fun dispatchCarConnect() {
|
|
1340
|
+
callbackDispatchScope.launch {
|
|
1341
|
+
val callback = kotlinx.coroutines.withTimeoutOrNull(
|
|
1342
|
+
HEADLESS_DISPATCH_TIMEOUT_MS
|
|
1343
|
+
) {
|
|
1344
|
+
browseCallbacks.awaitCarConnectCallback()
|
|
1345
|
+
}
|
|
1346
|
+
if (callback == null) {
|
|
1347
|
+
android.util.Log.w(
|
|
1348
|
+
LOG_TAG,
|
|
1349
|
+
"onCarConnect — no JS callback registered after " +
|
|
1350
|
+
"${HEADLESS_DISPATCH_TIMEOUT_MS}ms (consumer did not wire it?)"
|
|
1351
|
+
)
|
|
1352
|
+
return@launch
|
|
1353
|
+
}
|
|
1354
|
+
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Main) {
|
|
1355
|
+
callback()
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
private suspend fun applyAssistantQueueOnMain(
|
|
1361
|
+
tracks: List<TrackItem>
|
|
1362
|
+
): List<MediaItem> {
|
|
1363
|
+
if (tracks.isEmpty()) return emptyList()
|
|
1364
|
+
val player = TrackPlayer.current ?: return emptyList()
|
|
1365
|
+
return kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Main) {
|
|
1366
|
+
player.applyResolvedAssistantQueue(tracks)
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
/**
|
|
1371
|
+
* Boot the JS task IF a headless-eligible controller has connected
|
|
1372
|
+
* AND no JS task is already running. Idempotent — the
|
|
1373
|
+
* `HeadlessJsMediaService.initialized` latch + the wake-intent
|
|
1374
|
+
* action gate prevent re-entrancy.
|
|
1375
|
+
*/
|
|
1376
|
+
internal fun selfWakeForHeadlessClient(packageName: String) {
|
|
1377
|
+
// Skip the wake-intent self-start when the JS bridge is already
|
|
1378
|
+
// alive. The headless-wake path is for the cold-start case where
|
|
1379
|
+
// the OS has bound our service from a backgrounded / killed app
|
|
1380
|
+
// and we need a JS context to dispatch into. Firing startService
|
|
1381
|
+
// while a foreground JS context is running races with the live
|
|
1382
|
+
// React instance and on observed devices (Pixel 6a, Android 14)
|
|
1383
|
+
// tears down the bridge — playback stops, the UI hangs, the
|
|
1384
|
+
// process eventually errors out.
|
|
1385
|
+
//
|
|
1386
|
+
// Detection: read the ReactApplication's current React context
|
|
1387
|
+
// directly. This signals "JS bridge is alive" much earlier than
|
|
1388
|
+
// checking `browseCallbacks.carConnectCallback()` — the bridge
|
|
1389
|
+
// exists hundreds of milliseconds before the consumer's
|
|
1390
|
+
// `registerPlaybackService` call lands. gearhead's first
|
|
1391
|
+
// `onConnect` arrives in that window, so the callback-based
|
|
1392
|
+
// guard misses it (logged as the bind/unbind retry storm).
|
|
1393
|
+
if (hasLiveReactContext()) {
|
|
1394
|
+
android.util.Log.i(
|
|
1395
|
+
LOG_TAG,
|
|
1396
|
+
"selfWakeForHeadlessClient($packageName) skipped — React context already alive"
|
|
1397
|
+
)
|
|
1398
|
+
return
|
|
1399
|
+
}
|
|
1400
|
+
// Defence-in-depth: even if the context guard misses, rate-limit
|
|
1401
|
+
// self-wake to one per 30s. gearhead's retry-on-error loop fires
|
|
1402
|
+
// `onConnect` several times per second when our `onConnect` /
|
|
1403
|
+
// `onGetLibraryRoot` returns the wrong shape; without this gate,
|
|
1404
|
+
// each retry queues another `startService` → JS-task spawn,
|
|
1405
|
+
// amplifying the failure cascade.
|
|
1406
|
+
val now = System.currentTimeMillis()
|
|
1407
|
+
val last = lastSelfWakeAtMs
|
|
1408
|
+
if (last > 0 && now - last < SELF_WAKE_MIN_INTERVAL_MS) {
|
|
1409
|
+
android.util.Log.i(
|
|
1410
|
+
LOG_TAG,
|
|
1411
|
+
"selfWakeForHeadlessClient($packageName) rate-limited (${now - last}ms since last)"
|
|
1412
|
+
)
|
|
1413
|
+
return
|
|
1414
|
+
}
|
|
1415
|
+
lastSelfWakeAtMs = now
|
|
1416
|
+
android.util.Log.i(LOG_TAG, "selfWakeForHeadlessClient($packageName)")
|
|
1417
|
+
val intent = Intent(this, this::class.java).apply {
|
|
1418
|
+
action = ACTION_HEADLESS_WAKE
|
|
1419
|
+
putExtra(EXTRA_WAKE_PACKAGE, packageName)
|
|
1420
|
+
}
|
|
1421
|
+
try {
|
|
1422
|
+
// Plain `startService` rather than the foreground variant —
|
|
1423
|
+
// the wake intent's job is to fire `onStartCommand` so
|
|
1424
|
+
// [getTaskConfig] returns a non-null config + the JS context
|
|
1425
|
+
// boots. Foreground promotion is handled later by Media3's
|
|
1426
|
+
// own notification flow when playback actually begins. Using
|
|
1427
|
+
// `startForegroundService` here would oblige us to call
|
|
1428
|
+
// `startForeground` within ~5 s on Android 14+, but we have
|
|
1429
|
+
// nothing to post yet.
|
|
1430
|
+
startService(intent)
|
|
1431
|
+
} catch (e: IllegalStateException) {
|
|
1432
|
+
// Android 12+ throws when a service start happens off the
|
|
1433
|
+
// privileged window ("Not allowed to start service Intent…:
|
|
1434
|
+
// app is in background"). The service is already alive
|
|
1435
|
+
// (the controller bound to it before this ran), so JS never
|
|
1436
|
+
// wakes + the controller times out. Log so the failure is
|
|
1437
|
+
// visible in the bug report.
|
|
1438
|
+
android.util.Log.w(
|
|
1439
|
+
LOG_TAG,
|
|
1440
|
+
"selfWakeForHeadlessClient($packageName) blocked by background-start rules",
|
|
1441
|
+
e
|
|
1442
|
+
)
|
|
1443
|
+
} catch (e: SecurityException) {
|
|
1444
|
+
// OEM-skinned devices occasionally tighten the permission
|
|
1445
|
+
// surface for self-start; same fallback as the IllegalState
|
|
1446
|
+
// case above.
|
|
1447
|
+
android.util.Log.w(
|
|
1448
|
+
LOG_TAG,
|
|
1449
|
+
"selfWakeForHeadlessClient($packageName) denied by SecurityException",
|
|
1450
|
+
e
|
|
1451
|
+
)
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
override fun getTaskConfig(intent: Intent?): com.facebook.react.jstasks.HeadlessJsTaskConfig? {
|
|
1456
|
+
val action = intent?.action ?: return null
|
|
1457
|
+
if (action !in HEADLESS_WAKE_ACTIONS) return null
|
|
1458
|
+
// Robolectric / non-ReactApplication hosts cannot spawn a JS
|
|
1459
|
+
// task — short-circuit here so `Arguments.createMap()` never
|
|
1460
|
+
// has to load the React native libs that aren't on the test
|
|
1461
|
+
// classpath.
|
|
1462
|
+
if ((application as? com.facebook.react.ReactApplication) == null) return null
|
|
1463
|
+
// Belt-and-braces: if a wake intent somehow lands while a React
|
|
1464
|
+
// context is already alive (foreground app, prior wake already
|
|
1465
|
+
// ran the task to completion + the context survived), reject the
|
|
1466
|
+
// spawn. Returning a non-null config queues a second JS task that
|
|
1467
|
+
// would race the live context.
|
|
1468
|
+
if (hasLiveReactContext()) {
|
|
1469
|
+
android.util.Log.i(
|
|
1470
|
+
LOG_TAG,
|
|
1471
|
+
"getTaskConfig skipping $action — React context already alive"
|
|
1472
|
+
)
|
|
1473
|
+
return null
|
|
1474
|
+
}
|
|
1475
|
+
val data = com.facebook.react.bridge.Arguments.createMap().apply {
|
|
1476
|
+
putString(EXTRA_WAKE_ACTION, action)
|
|
1477
|
+
intent.getStringExtra(EXTRA_WAKE_PACKAGE)?.let {
|
|
1478
|
+
putString(EXTRA_WAKE_PACKAGE, it)
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
return com.facebook.react.jstasks.HeadlessJsTaskConfig(
|
|
1482
|
+
HEADLESS_TASK_NAME,
|
|
1483
|
+
data,
|
|
1484
|
+
0,
|
|
1485
|
+
true
|
|
1486
|
+
)
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
override fun onHeadlessJsTaskFinish(taskId: Int) {
|
|
1490
|
+
activeTasks.remove(taskId)
|
|
1491
|
+
// A media service stays alive while the player has items —
|
|
1492
|
+
// browse/voice callbacks remain dispatchable as long as the
|
|
1493
|
+
// session is up. But a cold-wake that finished without any
|
|
1494
|
+
// playback (e.g. JS booted, dispatch resolved empty) leaves
|
|
1495
|
+
// the service in foreground-eligible-but-idle state which
|
|
1496
|
+
// Android 14+ treats as an orphan; tear it down.
|
|
1497
|
+
//
|
|
1498
|
+
// Deferred 5s grace window: the JS-side cold-start path runs
|
|
1499
|
+
// `bootstrap.ts` as a bundle-load side effect AFTER the
|
|
1500
|
+
// headless task body resolves. That side effect calls
|
|
1501
|
+
// `configure()` + `installCarService()` which push the browse
|
|
1502
|
+
// snapshot + bind controllers. Tearing the service down
|
|
1503
|
+
// immediately on task-finish races that side-effect chain —
|
|
1504
|
+
// the service stops just as the consumer's bootstrap is wiring
|
|
1505
|
+
// up. The 5s window lets that chain complete; the re-check at
|
|
1506
|
+
// fire time bails if any of the relevant signals (media,
|
|
1507
|
+
// active tasks, connected controllers) have arrived.
|
|
1508
|
+
if (activeTasks.isEmpty() && !hasMediaQueued()) {
|
|
1509
|
+
scheduleIdleStopSelfCheck()
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
private val stopSelfHandler =
|
|
1514
|
+
android.os.Handler(android.os.Looper.getMainLooper())
|
|
1515
|
+
private val stopSelfRunnable = Runnable {
|
|
1516
|
+
if (
|
|
1517
|
+
activeTasks.isEmpty() &&
|
|
1518
|
+
!hasMediaQueued() &&
|
|
1519
|
+
!hasConnectedControllers()
|
|
1520
|
+
) {
|
|
1521
|
+
android.util.Log.i(
|
|
1522
|
+
LOG_TAG,
|
|
1523
|
+
"Idle grace window elapsed; calling stopSelf()"
|
|
1524
|
+
)
|
|
1525
|
+
stopSelf()
|
|
1526
|
+
} else {
|
|
1527
|
+
android.util.Log.i(
|
|
1528
|
+
LOG_TAG,
|
|
1529
|
+
"Idle grace window re-check: service has active surface, " +
|
|
1530
|
+
"leaving alive (activeTasks=${activeTasks.size}, " +
|
|
1531
|
+
"hasMedia=${hasMediaQueued()}, " +
|
|
1532
|
+
"controllers=${mediaSession?.connectedControllers?.size ?: 0})"
|
|
1533
|
+
)
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
private fun scheduleIdleStopSelfCheck() {
|
|
1538
|
+
stopSelfHandler.removeCallbacks(stopSelfRunnable)
|
|
1539
|
+
stopSelfHandler.postDelayed(stopSelfRunnable, IDLE_STOP_GRACE_MS)
|
|
1540
|
+
android.util.Log.i(
|
|
1541
|
+
LOG_TAG,
|
|
1542
|
+
"Scheduling idle stopSelf check in ${IDLE_STOP_GRACE_MS}ms"
|
|
1543
|
+
)
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
/**
|
|
1547
|
+
* True when the active session's player has at least one media
|
|
1548
|
+
* item queued. Read by [onHeadlessJsTaskFinish] to decide whether
|
|
1549
|
+
* to keep the service alive after the JS task body completes.
|
|
1550
|
+
*/
|
|
1551
|
+
private fun hasMediaQueued(): Boolean =
|
|
1552
|
+
(mediaSession?.player?.mediaItemCount ?: 0) > 0
|
|
1553
|
+
|
|
1554
|
+
/**
|
|
1555
|
+
* True when any automotive / Wear / Assistant controller is bound
|
|
1556
|
+
* to the active session. Read by the deferred stopSelf check so a
|
|
1557
|
+
* controller that connected during the grace window keeps the
|
|
1558
|
+
* service alive.
|
|
1559
|
+
*/
|
|
1560
|
+
private fun hasConnectedControllers(): Boolean =
|
|
1561
|
+
(mediaSession?.connectedControllers?.isNotEmpty() ?: false)
|
|
1562
|
+
|
|
1563
|
+
/**
|
|
1564
|
+
* Fire `MediaLibrarySession.notifyChildrenChanged` for every
|
|
1565
|
+
* `parentId` in [parentIds] PLUS every parent the
|
|
1566
|
+
* [pendingBrowseRequests] queue accumulated while the snapshot
|
|
1567
|
+
* was empty. Invoked by `TrackPlayer.setBrowseSnapshot` after a
|
|
1568
|
+
* fresh snapshot lands. Caller is on main; the session's own
|
|
1569
|
+
* thread-safety contract handles the controller iteration.
|
|
1570
|
+
*
|
|
1571
|
+
* Draining the pending queue here closes the cold-start race —
|
|
1572
|
+
* a controller that fetched children before any snapshot existed
|
|
1573
|
+
* gets re-notified once data is available, so its UI replaces
|
|
1574
|
+
* the empty placeholder with the real items.
|
|
1575
|
+
*/
|
|
1576
|
+
internal fun notifyChildrenChangedForParents(parentIds: Set<String>) {
|
|
1577
|
+
val drained = pendingBrowseRequests.drainAll()
|
|
1578
|
+
val all = if (drained.isEmpty()) parentIds else parentIds + drained
|
|
1579
|
+
val session = mediaSession ?: return
|
|
1580
|
+
for (controller in session.connectedControllers) {
|
|
1581
|
+
for (parentId in all) {
|
|
1582
|
+
// Pass count=Int.MAX_VALUE so the controller refetches the
|
|
1583
|
+
// whole subtree under the parent — the snapshot's section
|
|
1584
|
+
// sizes drive the actual returned count.
|
|
1585
|
+
session.notifyChildrenChanged(controller, parentId, Int.MAX_VALUE, null)
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
/**
|
|
1591
|
+
* Fire `notifySearchResultChanged` for [query] across every
|
|
1592
|
+
* connected controller after [searchResultCache] is populated.
|
|
1593
|
+
*/
|
|
1594
|
+
private fun notifySearchResultChanged(query: String, count: Int) {
|
|
1595
|
+
val session = mediaSession ?: return
|
|
1596
|
+
for (controller in session.connectedControllers) {
|
|
1597
|
+
session.notifySearchResultChanged(controller, query, count, null)
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
/**
|
|
1602
|
+
* Translate a [SkipCapability] into the [Player.Commands] set that
|
|
1603
|
+
* controllers should see. The cap reflects repeat-mode + queue
|
|
1604
|
+
* position rather than Media3's timeline-derived default, so
|
|
1605
|
+
* `repeat=queue` at the last index correctly leaves skip-next
|
|
1606
|
+
* enabled; only the four skip commands flip with the cap. Every
|
|
1607
|
+
* other command (play/pause, seek, set speed, etc.) is preserved
|
|
1608
|
+
* from `DEFAULT_PLAYER_COMMANDS`.
|
|
1609
|
+
*/
|
|
1610
|
+
internal fun playerCommandsFor(
|
|
1611
|
+
capability: SkipCapability,
|
|
1612
|
+
hasMediaItems: Boolean
|
|
1613
|
+
): Player.Commands {
|
|
1614
|
+
val builder = MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
|
|
1615
|
+
if (!hasMediaItems) {
|
|
1616
|
+
// Empty queue → strip every command that contributes to the
|
|
1617
|
+
// legacy `PlaybackStateCompat` action mask's PLAY-related bits.
|
|
1618
|
+
// Android Auto's dashboard-tap launch heuristic reads that mask:
|
|
1619
|
+
// STATE_NONE alone is not enough — the action mask must also
|
|
1620
|
+
// carry no ACTION_PLAY / ACTION_PLAY_PAUSE / ACTION_PREPARE.
|
|
1621
|
+
// Once both are zero, AA lands on the Library/Browse surface
|
|
1622
|
+
// instead of "Now Playing — To play something, tap the icon at
|
|
1623
|
+
// the top left."
|
|
1624
|
+
builder.remove(Player.COMMAND_PLAY_PAUSE)
|
|
1625
|
+
builder.remove(Player.COMMAND_PREPARE)
|
|
1626
|
+
builder.remove(Player.COMMAND_STOP)
|
|
1627
|
+
builder.remove(Player.COMMAND_SEEK_BACK)
|
|
1628
|
+
builder.remove(Player.COMMAND_SEEK_FORWARD)
|
|
1629
|
+
builder.remove(Player.COMMAND_SEEK_TO_DEFAULT_POSITION)
|
|
1630
|
+
builder.remove(Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)
|
|
1631
|
+
builder.remove(Player.COMMAND_SEEK_TO_MEDIA_ITEM)
|
|
1632
|
+
builder.remove(Player.COMMAND_SET_SHUFFLE_MODE)
|
|
1633
|
+
builder.remove(Player.COMMAND_SET_REPEAT_MODE)
|
|
1634
|
+
builder.remove(Player.COMMAND_SET_SPEED_AND_PITCH)
|
|
1635
|
+
}
|
|
1636
|
+
if (!capability.canSkipNext) {
|
|
1637
|
+
builder.remove(Player.COMMAND_SEEK_TO_NEXT)
|
|
1638
|
+
builder.remove(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
|
|
1639
|
+
}
|
|
1640
|
+
if (!capability.canSkipPrevious) {
|
|
1641
|
+
builder.remove(Player.COMMAND_SEEK_TO_PREVIOUS)
|
|
1642
|
+
builder.remove(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
|
|
1643
|
+
}
|
|
1644
|
+
return builder.build()
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
/**
|
|
1648
|
+
* Hook the [PlaybackCallback] consults before letting Media3 route
|
|
1649
|
+
* a controller-initiated command to the bound player. Calls run on
|
|
1650
|
+
* the service main looper. Skip-next / skip-previous return true
|
|
1651
|
+
* when the implementation has handled the skip itself, so the
|
|
1652
|
+
* default routing is suppressed; the transport hook fires for play /
|
|
1653
|
+
* pause / seek / setPlaybackSpeed and is purely a side-channel
|
|
1654
|
+
* notification (no return value).
|
|
1655
|
+
*/
|
|
1656
|
+
internal interface MediaSessionCommandHandler {
|
|
1657
|
+
/** Stamps `system` on the state-change channel for the upcoming
|
|
1658
|
+
* play / pause / seek / setPlaybackSpeed default routing. */
|
|
1659
|
+
fun onTransportCommand()
|
|
1660
|
+
/** Returns true if the lib intercepted the skip and the default
|
|
1661
|
+
* routing should be suppressed. */
|
|
1662
|
+
fun onSkipToNextCommand(): Boolean
|
|
1663
|
+
/** Returns true if the lib intercepted the skip and the default
|
|
1664
|
+
* routing should be suppressed. */
|
|
1665
|
+
fun onSkipToPreviousCommand(): Boolean
|
|
1666
|
+
|
|
1667
|
+
/**
|
|
1668
|
+
* Returns the lib's current authoritative skip capability —
|
|
1669
|
+
* driven by repeat-mode + queue position + queue size on the
|
|
1670
|
+
* `TrackPlayer` side. Read by [PlaybackCallback.onConnect] and
|
|
1671
|
+
* [LocalBinder.refreshAvailableCommands] so the available
|
|
1672
|
+
* `Player.Commands` reflect what skip arithmetic actually allows.
|
|
1673
|
+
*/
|
|
1674
|
+
fun getCachedSkipCapability(): SkipCapability
|
|
1675
|
+
|
|
1676
|
+
// The following are invoked when a raw `KEYCODE_MEDIA_*` lands via
|
|
1677
|
+
// either the `onMediaButtonEvent` callback or `onStartCommand` —
|
|
1678
|
+
// there is no Media3 default routing to fall through to in that
|
|
1679
|
+
// case, so the handler executes the action itself.
|
|
1680
|
+
|
|
1681
|
+
/** Start playback with `system` reason stamped. */
|
|
1682
|
+
fun onMediaPlay()
|
|
1683
|
+
/** Pause playback with `system` reason stamped. */
|
|
1684
|
+
fun onMediaPause()
|
|
1685
|
+
/** Toggle play/pause based on the current player state, with
|
|
1686
|
+
* `system` reason stamped. */
|
|
1687
|
+
fun onMediaTogglePlayPause()
|
|
1688
|
+
/** Queue-aware skip-next with `system` reason stamped. */
|
|
1689
|
+
fun onMediaSkipNext()
|
|
1690
|
+
/** Queue-aware skip-previous with `system` reason stamped. */
|
|
1691
|
+
fun onMediaSkipPrevious()
|
|
1692
|
+
|
|
1693
|
+
/**
|
|
1694
|
+
* Surface a granular Media3 audio-focus event on the JS `onError`
|
|
1695
|
+
* channel. The lib does NOT change player response from here —
|
|
1696
|
+
* Media3's `setAudioAttributes(handleAudioFocus = true)` keeps
|
|
1697
|
+
* driving pause / duck / auto-resume. The handler just emits the
|
|
1698
|
+
* typed [PlaybackError] so JS consumers can render UX
|
|
1699
|
+
* (interruption banner, resume-prompt, etc.) without re-deriving
|
|
1700
|
+
* the focus kind from the playback-state stream.
|
|
1701
|
+
*/
|
|
1702
|
+
fun onAudioFocusEvent(mapping: AudioFocusErrorMapping.Mapping)
|
|
1703
|
+
|
|
1704
|
+
/**
|
|
1705
|
+
* Surface an `AUDIO_BECOMING_NOISY` event on the JS `onError`
|
|
1706
|
+
* channel. Fires on headphone unplug / A2DP disconnect / USB DAC
|
|
1707
|
+
* removal. The lib does NOT pause the player here —
|
|
1708
|
+
* `setHandleAudioBecomingNoisy(true)` on the bound player already
|
|
1709
|
+
* routes the broadcast through Media3's own pause logic. The
|
|
1710
|
+
* handler just emits the typed [PlaybackError] so JS consumers
|
|
1711
|
+
* can render UX (e.g. "Paused — headphones disconnected").
|
|
1712
|
+
*/
|
|
1713
|
+
fun onBecomingNoisy()
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
}
|