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,1343 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// Dual-`AVPlayer` engine implementing equal-power crossfade at
|
|
5
|
+
/// track boundaries.
|
|
6
|
+
///
|
|
7
|
+
/// Two `AVPlayer` instances rotate roles per fade: the leading
|
|
8
|
+
/// leg is currently audible; the standby leg prerolls the next
|
|
9
|
+
/// queue item ahead of the fade window and ramps up from silence
|
|
10
|
+
/// as the leading ramps down.
|
|
11
|
+
///
|
|
12
|
+
/// Architecture (per platform research):
|
|
13
|
+
/// - Single-item per `AVPlayer` (NOT `AVQueuePlayer`) so each
|
|
14
|
+
/// leg's `AVMutableAudioMixInputParameters.setVolumeRamp` is
|
|
15
|
+
/// independently scheduled. The lib-side queue model on
|
|
16
|
+
/// `TrackPlayer` is the canonical position; the engine
|
|
17
|
+
/// advances the standby leg's currentItem to the next queue
|
|
18
|
+
/// position ahead of fade-start.
|
|
19
|
+
/// - Equal-power curve approximated via N short linear
|
|
20
|
+
/// `setVolumeRamp` sub-ramps. `setVolumeRamp` is linear-only
|
|
21
|
+
/// per the AVFoundation API; ~50 sub-ramps over a fade window
|
|
22
|
+
/// is auditorily indistinguishable from true `cos(t·π/2)` /
|
|
23
|
+
/// `sin(t·π/2)`.
|
|
24
|
+
/// - Standby preroll via `AVPlayer.preroll(atRate: 1.0)` ~500ms
|
|
25
|
+
/// before fade-start so the audio output graph is hot when
|
|
26
|
+
/// its volume ramp begins.
|
|
27
|
+
/// - Backward seek through a scheduled ramp window corrupts the
|
|
28
|
+
/// mix per Wade Tregaskis's `setVolumeRamp` findings —
|
|
29
|
+
/// recovery is `audioMix = nil` + `volume = userVolume`.
|
|
30
|
+
/// - Per-boundary route check via `OutputRouteMonitor`: if the
|
|
31
|
+
/// active route is `.airPlay` / `.carAudio`, the boundary
|
|
32
|
+
/// hard-cuts (no fade) for that transition. Mid-fade route
|
|
33
|
+
/// change cancels the fade synchronously.
|
|
34
|
+
///
|
|
35
|
+
/// Runtime fade execution (multi-second `setVolumeRamp` schedule
|
|
36
|
+
/// + standby promotion to leading at fade-end) is exercised at
|
|
37
|
+
/// the demo-app integration layer; the engine's deterministic
|
|
38
|
+
/// primitives (curve math, sub-ramp count, route classification,
|
|
39
|
+
/// boundary fade-vs-cut decision) are unit-tested.
|
|
40
|
+
final class CrossfadeEngine: PlaybackEngine {
|
|
41
|
+
|
|
42
|
+
// MARK: - Construction
|
|
43
|
+
|
|
44
|
+
/// User-set fade duration captured at construction time. Both
|
|
45
|
+
/// `AVAudioMix` ramp scheduling and the role-swap timer
|
|
46
|
+
/// coordinate against this value; runtime duration changes
|
|
47
|
+
/// require a fresh engine instance via `handoff`.
|
|
48
|
+
private let crossfadeDurationSeconds: TimeInterval
|
|
49
|
+
|
|
50
|
+
/// Pre-fade preroll lead so the standby leg's audio output
|
|
51
|
+
/// graph is hot when its first ramp starts. 500ms matches the
|
|
52
|
+
/// platform research recommendation.
|
|
53
|
+
static let prerollLeadSeconds: TimeInterval = 0.5
|
|
54
|
+
|
|
55
|
+
/// Equal-power approximation step count. ~50 sub-ramps over a
|
|
56
|
+
/// 6-second fade is below the audibility threshold for the
|
|
57
|
+
/// difference between staircased and continuous cos/sin.
|
|
58
|
+
static let subRampCount: Int = 50
|
|
59
|
+
|
|
60
|
+
/// Volume multiplier applied to both legs during the fade.
|
|
61
|
+
/// Tracked so manual `setVolume(v)` survives a subsequent
|
|
62
|
+
/// fade cancel / handoff.
|
|
63
|
+
private var userVolume: Float = 1.0
|
|
64
|
+
|
|
65
|
+
/// Tracked so the user's playback speed survives a crossfade
|
|
66
|
+
/// transition: the standby leg prerolls/plays at the configured
|
|
67
|
+
/// rate and is re-set to this when promoted to leading. Without it
|
|
68
|
+
/// every fade resets the rate to 1.0.
|
|
69
|
+
private var userPlaybackSpeed: Float = 1.0
|
|
70
|
+
|
|
71
|
+
/// Persisted pitch-correction mode, stamped as `audioTimePitchAlgorithm`
|
|
72
|
+
/// on both legs' items. The resolved algorithm depends on
|
|
73
|
+
/// `userPlaybackSpeed` (the intended rate), not the live leg rate
|
|
74
|
+
/// (0 while paused / on the silent standby).
|
|
75
|
+
private var pitchCorrectionMode: PitchCorrectionMode = .voice
|
|
76
|
+
|
|
77
|
+
weak var delegate: PlaybackEngineDelegate?
|
|
78
|
+
|
|
79
|
+
/// Owns the route monitor instead of relying on a shared one
|
|
80
|
+
/// — the engine starts/stops monitoring with its own
|
|
81
|
+
/// lifecycle.
|
|
82
|
+
private let routeMonitor: OutputRouteMonitor
|
|
83
|
+
|
|
84
|
+
// MARK: - Players
|
|
85
|
+
|
|
86
|
+
private let playerA: AVPlayer
|
|
87
|
+
private let playerB: AVPlayer
|
|
88
|
+
|
|
89
|
+
/// `true` when [leadingPlayer] is `playerA` (the engine's
|
|
90
|
+
/// first-built player). Flips on every fade-complete role
|
|
91
|
+
/// swap. Read by the test surface to verify role-swap
|
|
92
|
+
/// arithmetic without driving a real fade.
|
|
93
|
+
private(set) var currentRoleIsA: Bool = true
|
|
94
|
+
|
|
95
|
+
/// Leading leg — currently audible.
|
|
96
|
+
var leadingPlayer: AVPlayer { currentRoleIsA ? playerA : playerB }
|
|
97
|
+
|
|
98
|
+
/// Standby leg — prerolls the next queue item ahead of fade-start.
|
|
99
|
+
var standbyPlayer: AVPlayer { currentRoleIsA ? playerB : playerA }
|
|
100
|
+
|
|
101
|
+
// MARK: - Queue model
|
|
102
|
+
|
|
103
|
+
/// Engine-internal queue snapshot — caller sets via
|
|
104
|
+
/// `setItems`; engine advances `currentIndex` on user skip /
|
|
105
|
+
/// auto-advance. Each `AVPlayer` instance holds at most one
|
|
106
|
+
/// item from this list at a time.
|
|
107
|
+
private var queue: [AVPlayerItem] = []
|
|
108
|
+
private var currentIndex: Int = -1
|
|
109
|
+
|
|
110
|
+
/// Active repeat mode. AVPlayer has no native repeat, so the engine
|
|
111
|
+
/// implements repeat-one itself: it suppresses the look-ahead fade
|
|
112
|
+
/// and loops the current item on natural end (see `maybeArmCrossfade`
|
|
113
|
+
/// + `handleItemDidPlayToEnd`). `.queue` wrap is driven by TrackPlayer
|
|
114
|
+
/// rebuilding the queue, same as the gapless engine.
|
|
115
|
+
private var repeatMode: RepeatMode = .off
|
|
116
|
+
|
|
117
|
+
/// `true` while a fade ramp is scheduled / in flight.
|
|
118
|
+
private var fadeInFlight: Bool = false
|
|
119
|
+
|
|
120
|
+
/// Periodic-time-observer + the AVPlayer it was registered
|
|
121
|
+
/// against. `AVPlayer.removeTimeObserver` raises
|
|
122
|
+
/// `NSInvalidArgumentException` when called on a different
|
|
123
|
+
/// instance than the one that issued the token, so the player
|
|
124
|
+
/// is captured alongside the token. Removed on pause / stop /
|
|
125
|
+
/// seek / skip / role-swap / release; re-installed on play.
|
|
126
|
+
private var armingObserver: (player: AVPlayer, token: Any)?
|
|
127
|
+
|
|
128
|
+
/// `true` after [armCrossfade] has prerolled standby for the
|
|
129
|
+
/// upcoming fade.
|
|
130
|
+
private var standbyArmed: Bool = false
|
|
131
|
+
|
|
132
|
+
/// Wall-clock task that fires `completeCrossfade` after the
|
|
133
|
+
/// fade duration. Stored so `cancelFadeInternal` /
|
|
134
|
+
/// `release` can cancel it before a stale completion runs
|
|
135
|
+
/// against a fresh fade.
|
|
136
|
+
private var fadeCompletionTask: Task<Void, Never>?
|
|
137
|
+
|
|
138
|
+
/// Audio-mix provider supplied by `Equalizer` (and future
|
|
139
|
+
/// `Visualizer`). Per-AVPlayerItem `AVAudioMix` is built
|
|
140
|
+
/// per-item via the provider's `audioMix(for:)`.
|
|
141
|
+
private var mixProvider: AudioMixProvider?
|
|
142
|
+
|
|
143
|
+
/// Resolves a track's source URL to its lookahead-cache local file
|
|
144
|
+
/// URL when fully cached, else nil. Supplied by `TrackPlayer` (reads
|
|
145
|
+
/// `LookaheadCache.cachedURL(forUrl:)` lazily so it always sees the
|
|
146
|
+
/// current cache). The engine re-resolves at every late item attach
|
|
147
|
+
/// (skip cross-attach, fade-arm preroll) so a track that finished
|
|
148
|
+
/// caching AFTER its queue item was first built from the remote URL
|
|
149
|
+
/// is played from the local file — instead of re-loading the remote
|
|
150
|
+
/// asset, whose `.tracks` round-trip can fail the now-playing format
|
|
151
|
+
/// extractor.
|
|
152
|
+
private var cachedURLResolver: ((String) -> URL?)?
|
|
153
|
+
|
|
154
|
+
/// Released-once flag so `release()` is idempotent.
|
|
155
|
+
private var released: Bool = false
|
|
156
|
+
|
|
157
|
+
/// One-shot KVO on the standby `AVPlayer.status`. Set during
|
|
158
|
+
/// [armCrossfade] when `replaceCurrentItem` was just called and
|
|
159
|
+
/// the asset hasn't reached `.readyToPlay` yet — `preroll(atRate:)`
|
|
160
|
+
/// raises `NSInternalInconsistencyException` if invoked before
|
|
161
|
+
/// the status flip per `AVPlayer.h`. Invalidated on
|
|
162
|
+
/// [cancelFadeInternal] / [release] / when the readiness fires.
|
|
163
|
+
private var standbyReadyObserver: NSKeyValueObservation?
|
|
164
|
+
|
|
165
|
+
/// Wall-clock fallback used when standby never reaches
|
|
166
|
+
/// `.readyToPlay` (asset load failure / network error). Lets the
|
|
167
|
+
/// arm path bail cleanly instead of stranding `standbyArmed = true`
|
|
168
|
+
/// against a broken standby. 5s covers local + warm cache; for
|
|
169
|
+
/// streaming the consumer would have already failed by then via
|
|
170
|
+
/// the leading-leg error path.
|
|
171
|
+
private var standbyReadyTimeout: DispatchWorkItem?
|
|
172
|
+
|
|
173
|
+
/// Generation counter incremented at every [awaitStandbyReady]
|
|
174
|
+
/// entry. Closures captured by the KVO observer + the timeout
|
|
175
|
+
/// `DispatchWorkItem` carry their generation value — if a new
|
|
176
|
+
/// arm cycle increments the counter while a stale main-async
|
|
177
|
+
/// hop is pending, the gate inside the closure short-circuits.
|
|
178
|
+
/// Defends against a narrow race where the original arm's
|
|
179
|
+
/// `.readyToPlay` callback fires AFTER `cancelFadeInternal` +
|
|
180
|
+
/// a fresh `armCrossfade` re-flipped `standbyArmed` to true.
|
|
181
|
+
private var standbyReadyGen: Int = 0
|
|
182
|
+
|
|
183
|
+
// MARK: - Engine event observers
|
|
184
|
+
|
|
185
|
+
/// `\.timeControlStatus` KVO on the leading leg + the player it
|
|
186
|
+
/// was registered against. Re-installed on every role swap.
|
|
187
|
+
/// Fires `engineStateMaybeChanged` + `engineIsPlayingChanged`.
|
|
188
|
+
private var leadingTimeControlObserver: NSKeyValueObservation?
|
|
189
|
+
|
|
190
|
+
/// `\.currentItem.tracks` KVO on the leading leg's currentItem.
|
|
191
|
+
/// Fires `engineActiveItemFormatChanged` once tracks populate.
|
|
192
|
+
/// Re-installed on role swap and on `setItems`.
|
|
193
|
+
private var leadingItemTracksObserver: NSKeyValueObservation?
|
|
194
|
+
|
|
195
|
+
/// `\.currentItem.status` KVO on the leading leg's currentItem.
|
|
196
|
+
/// Routes `.failed` through `engineDidFailWithError`.
|
|
197
|
+
private var leadingItemStatusObserver: NSKeyValueObservation?
|
|
198
|
+
|
|
199
|
+
/// Block-based `NotificationCenter` observer tokens for the
|
|
200
|
+
/// AVPlayerItem lifecycle notifications. Filtered inside the
|
|
201
|
+
/// block to leading-leg only. Removed in `release`.
|
|
202
|
+
private var itemDidPlayToEndToken: NSObjectProtocol?
|
|
203
|
+
private var itemFailedToPlayToEndToken: NSObjectProtocol?
|
|
204
|
+
|
|
205
|
+
// MARK: - Init
|
|
206
|
+
|
|
207
|
+
init(crossfadeDurationSeconds: TimeInterval) {
|
|
208
|
+
precondition(
|
|
209
|
+
crossfadeDurationSeconds >= 0,
|
|
210
|
+
"crossfadeDurationSeconds must be non-negative"
|
|
211
|
+
)
|
|
212
|
+
self.crossfadeDurationSeconds = crossfadeDurationSeconds
|
|
213
|
+
|
|
214
|
+
let p1 = AVPlayer()
|
|
215
|
+
p1.automaticallyWaitsToMinimizeStalling = true
|
|
216
|
+
p1.volume = userVolume
|
|
217
|
+
let p2 = AVPlayer()
|
|
218
|
+
p2.automaticallyWaitsToMinimizeStalling = true
|
|
219
|
+
p2.volume = 0 // standby starts silent
|
|
220
|
+
|
|
221
|
+
self.playerA = p1
|
|
222
|
+
self.playerB = p2
|
|
223
|
+
|
|
224
|
+
let monitor = OutputRouteMonitor()
|
|
225
|
+
self.routeMonitor = monitor
|
|
226
|
+
monitor.start()
|
|
227
|
+
monitor.onRouteBecameNonMixable = { [weak self] in
|
|
228
|
+
// Mid-fade route change: cancel the in-flight fade so iOS
|
|
229
|
+
// doesn't arbitrarily silence one of the two players. The
|
|
230
|
+
// outgoing leg snaps to userVolume; the standby drops its
|
|
231
|
+
// item.
|
|
232
|
+
DispatchQueue.main.async { [weak self] in
|
|
233
|
+
self?.cancelFade()
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Lib-wide notifications fire for any AVPlayerItem; the
|
|
238
|
+
// closure filters by `object === leadingPlayer.currentItem`
|
|
239
|
+
// so events on the standby leg's preroll item don't surface.
|
|
240
|
+
itemDidPlayToEndToken = NotificationCenter.default.addObserver(
|
|
241
|
+
forName: .AVPlayerItemDidPlayToEndTime,
|
|
242
|
+
object: nil,
|
|
243
|
+
queue: .main
|
|
244
|
+
) { [weak self] note in
|
|
245
|
+
self?.handleItemDidPlayToEnd(note)
|
|
246
|
+
}
|
|
247
|
+
itemFailedToPlayToEndToken = NotificationCenter.default.addObserver(
|
|
248
|
+
forName: .AVPlayerItemFailedToPlayToEndTime,
|
|
249
|
+
object: nil,
|
|
250
|
+
queue: .main
|
|
251
|
+
) { [weak self] note in
|
|
252
|
+
self?.handleItemFailedToPlayToEnd(note)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// iOS 26+ AVRoutingPlaybackArbiter would let the engine name
|
|
256
|
+
// the preferred participant for non-mixable routes (AirPlay
|
|
257
|
+
// 2 / wireless CarPlay) so iOS doesn't arbitrarily silence
|
|
258
|
+
// one of two playing AVPlayer instances during the
|
|
259
|
+
// engagement transient. The current iOS SDK in this build
|
|
260
|
+
// ships AVRoutingPlaybackArbiter but the
|
|
261
|
+
// `preferredParticipantForNonMixableAudioRoutes` property
|
|
262
|
+
// marker is unavailable on iOS at the SDK header level —
|
|
263
|
+
// tracked as a follow-up once the SDK exposes it on iOS or
|
|
264
|
+
// an alternative API surface lands.
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/// No-op placeholder — see init comment for the iOS 26+
|
|
268
|
+
/// AVRoutingPlaybackArbiter follow-up.
|
|
269
|
+
private func updateRoutingArbiter() {
|
|
270
|
+
// Intentional no-op pending SDK availability.
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// MARK: - Lifecycle
|
|
274
|
+
|
|
275
|
+
func prepare(
|
|
276
|
+
items: [AVPlayerItem],
|
|
277
|
+
startIndex: Int,
|
|
278
|
+
startPositionSeconds: TimeInterval
|
|
279
|
+
) async {
|
|
280
|
+
setItems(items, startIndex: startIndex, startPositionSeconds: startPositionSeconds)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
func release() {
|
|
284
|
+
guard !released else { return }
|
|
285
|
+
released = true
|
|
286
|
+
// Release runs as part of an engine swap or destroy; the new
|
|
287
|
+
// engine has already taken over the canonical active-track
|
|
288
|
+
// state, so a mid-fade revert emit here would surface a
|
|
289
|
+
// spurious onTrackChange to the OUTGOING leg of a fade that
|
|
290
|
+
// the consumer no longer cares about.
|
|
291
|
+
cancelFadeInternal(emitRevert: false)
|
|
292
|
+
stopArmingObserver()
|
|
293
|
+
teardownLeadingObservers()
|
|
294
|
+
if let token = itemDidPlayToEndToken {
|
|
295
|
+
NotificationCenter.default.removeObserver(token)
|
|
296
|
+
itemDidPlayToEndToken = nil
|
|
297
|
+
}
|
|
298
|
+
if let token = itemFailedToPlayToEndToken {
|
|
299
|
+
NotificationCenter.default.removeObserver(token)
|
|
300
|
+
itemFailedToPlayToEndToken = nil
|
|
301
|
+
}
|
|
302
|
+
routeMonitor.stop()
|
|
303
|
+
leadingPlayer.replaceCurrentItem(with: nil)
|
|
304
|
+
standbyPlayer.replaceCurrentItem(with: nil)
|
|
305
|
+
queue.removeAll()
|
|
306
|
+
currentIndex = -1
|
|
307
|
+
mixProvider = nil
|
|
308
|
+
delegate = nil
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// MARK: - Queue mutation
|
|
312
|
+
|
|
313
|
+
func setItems(
|
|
314
|
+
_ items: [AVPlayerItem],
|
|
315
|
+
startIndex: Int,
|
|
316
|
+
startPositionSeconds: TimeInterval
|
|
317
|
+
) {
|
|
318
|
+
cancelFadeInternal()
|
|
319
|
+
queue = items
|
|
320
|
+
teardownLeadingObservers()
|
|
321
|
+
if items.isEmpty || startIndex < 0 || startIndex >= items.count {
|
|
322
|
+
currentIndex = -1
|
|
323
|
+
leadingPlayer.replaceCurrentItem(with: nil)
|
|
324
|
+
standbyPlayer.replaceCurrentItem(with: nil)
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
currentIndex = startIndex
|
|
328
|
+
let activeItem = items[startIndex]
|
|
329
|
+
applyMixIfNeeded(to: activeItem)
|
|
330
|
+
applyPitchAlgorithm(to: activeItem)
|
|
331
|
+
leadingPlayer.replaceCurrentItem(with: activeItem)
|
|
332
|
+
standbyPlayer.replaceCurrentItem(with: nil)
|
|
333
|
+
if startPositionSeconds > 0 {
|
|
334
|
+
activeItem.seek(
|
|
335
|
+
to: CMTime(seconds: startPositionSeconds, preferredTimescale: 600),
|
|
336
|
+
completionHandler: nil
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
installLeadingObservers()
|
|
340
|
+
updateRoutingArbiter()
|
|
341
|
+
delegate?.engineDidTransitionTrack(self)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
@discardableResult
|
|
345
|
+
func insertItems(
|
|
346
|
+
_ items: [AVPlayerItem],
|
|
347
|
+
after: AVPlayerItem?
|
|
348
|
+
) -> Bool {
|
|
349
|
+
if items.isEmpty { return true }
|
|
350
|
+
// Queue mutation invalidates the standby leg's pre-loaded
|
|
351
|
+
// future. Cancel any in-flight fade before mutating so the
|
|
352
|
+
// standby's stale state doesn't race a swap against the
|
|
353
|
+
// post-mutation index space.
|
|
354
|
+
cancelFadeInternal()
|
|
355
|
+
let insertOffset: Int
|
|
356
|
+
if let after, let idx = queue.firstIndex(where: { $0 === after }) {
|
|
357
|
+
insertOffset = idx + 1
|
|
358
|
+
} else if after == nil {
|
|
359
|
+
insertOffset = queue.count
|
|
360
|
+
} else {
|
|
361
|
+
// Reference item not in the engine's queue — fall back to
|
|
362
|
+
// a full rebuild signal so the caller can call setItems.
|
|
363
|
+
return false
|
|
364
|
+
}
|
|
365
|
+
queue.insert(contentsOf: items, at: insertOffset)
|
|
366
|
+
return true
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
func remove(_ item: AVPlayerItem) {
|
|
370
|
+
guard let idx = queue.firstIndex(where: { $0 === item }) else { return }
|
|
371
|
+
cancelFadeInternal()
|
|
372
|
+
queue.remove(at: idx)
|
|
373
|
+
if idx == currentIndex {
|
|
374
|
+
// Removing the leading leg's current item — auto-advance
|
|
375
|
+
// to whatever follows or drain.
|
|
376
|
+
if currentIndex >= queue.count {
|
|
377
|
+
currentIndex = queue.count - 1
|
|
378
|
+
}
|
|
379
|
+
if currentIndex < 0 {
|
|
380
|
+
leadingPlayer.replaceCurrentItem(with: nil)
|
|
381
|
+
} else {
|
|
382
|
+
let next = queue[currentIndex]
|
|
383
|
+
applyMixIfNeeded(to: next)
|
|
384
|
+
applyPitchAlgorithm(to: next)
|
|
385
|
+
leadingPlayer.replaceCurrentItem(with: next)
|
|
386
|
+
}
|
|
387
|
+
} else if idx < currentIndex {
|
|
388
|
+
currentIndex -= 1
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
func removeAllItems() {
|
|
393
|
+
cancelFadeInternal()
|
|
394
|
+
queue.removeAll()
|
|
395
|
+
currentIndex = -1
|
|
396
|
+
leadingPlayer.replaceCurrentItem(with: nil)
|
|
397
|
+
standbyPlayer.replaceCurrentItem(with: nil)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
func canInsert(_ item: AVPlayerItem, after: AVPlayerItem?) -> Bool {
|
|
401
|
+
if let after {
|
|
402
|
+
return queue.contains(where: { $0 === after })
|
|
403
|
+
}
|
|
404
|
+
return true // append-at-end always permitted
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// MARK: - Transport
|
|
408
|
+
|
|
409
|
+
func play() {
|
|
410
|
+
// Resume at the user's speed (AVPlayer.play() resets rate to 1.0).
|
|
411
|
+
leadingPlayer.rate = userPlaybackSpeed
|
|
412
|
+
startArmingObserver()
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
func pause() {
|
|
416
|
+
cancelFadeInternal()
|
|
417
|
+
stopArmingObserver()
|
|
418
|
+
leadingPlayer.pause()
|
|
419
|
+
standbyPlayer.pause()
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
func stop() {
|
|
423
|
+
cancelFadeInternal()
|
|
424
|
+
stopArmingObserver()
|
|
425
|
+
leadingPlayer.pause()
|
|
426
|
+
standbyPlayer.pause()
|
|
427
|
+
// Match GaplessEngine.stop(): pause + seek the CURRENT (leading) item to 0
|
|
428
|
+
// and KEEP it. Replacing it with nil leaves no currentItem — so position
|
|
429
|
+
// never resets to observe, the periodic time observer never fires the
|
|
430
|
+
// post-stop progress tick consumers wait for, and getState() drops to NONE
|
|
431
|
+
// instead of PAUSED. Drain only the standby (prerolled next-track) leg;
|
|
432
|
+
// it's stale once stopped and re-arms on the next play.
|
|
433
|
+
leadingPlayer.currentItem?.seek(to: .zero, completionHandler: nil)
|
|
434
|
+
standbyPlayer.replaceCurrentItem(with: nil)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
func seek(toIndex index: Int, position: TimeInterval) {
|
|
438
|
+
guard index >= 0, index < queue.count else { return }
|
|
439
|
+
// Capture playing intent before the fade-cancel: a cross-attach
|
|
440
|
+
// (replaceCurrentItem) drops the leading player to rate 0, so the
|
|
441
|
+
// new leg must be resumed at the user's speed below to match the
|
|
442
|
+
// gapless skip (which restores rate across its queue rebuild).
|
|
443
|
+
let wasPlaying = isPlaying
|
|
444
|
+
// Seek is itself a track-change: the engineDidTransitionTrack
|
|
445
|
+
// fire at the end of this method is the authoritative active-
|
|
446
|
+
// track flip for the new index. Suppress the cancel-revert
|
|
447
|
+
// emit so a mid-fade seek doesn't dispatch an intermediate
|
|
448
|
+
// engineCrossfadeDidCancel(revertedIndex: outgoing) event
|
|
449
|
+
// before the seek's own track-change lands.
|
|
450
|
+
cancelFadeInternal(emitRevert: false)
|
|
451
|
+
teardownLeadingObservers()
|
|
452
|
+
currentIndex = index
|
|
453
|
+
let existing = queue[index]
|
|
454
|
+
// Same-instance no-op: target is already leadingPlayer's
|
|
455
|
+
// currentItem (seek-within-current-track, or seekTo right
|
|
456
|
+
// after setItems). Calling replaceCurrentItem with the same
|
|
457
|
+
// instance triggers a redundant KVO cascade and delays
|
|
458
|
+
// duration re-propagation by seconds; skip the re-attach.
|
|
459
|
+
if leadingPlayer.currentItem === existing {
|
|
460
|
+
if position > 0 {
|
|
461
|
+
existing.seek(
|
|
462
|
+
to: CMTime(seconds: position, preferredTimescale: 600),
|
|
463
|
+
completionHandler: nil
|
|
464
|
+
)
|
|
465
|
+
} else {
|
|
466
|
+
// Same-instance keeps any prior playhead; force-reset to
|
|
467
|
+
// zero to honour the seek contract.
|
|
468
|
+
existing.seek(to: .zero, completionHandler: nil)
|
|
469
|
+
}
|
|
470
|
+
standbyArmed = false
|
|
471
|
+
installLeadingObservers()
|
|
472
|
+
updateRoutingArbiter()
|
|
473
|
+
delegate?.engineDidTransitionTrack(self)
|
|
474
|
+
return
|
|
475
|
+
}
|
|
476
|
+
// Cross-attach case: target is associated with another
|
|
477
|
+
// AVPlayer (typically standbyPlayer post fade-arm). Rebuild
|
|
478
|
+
// as a fresh AVPlayerItem on the existing asset.
|
|
479
|
+
// AVPlayer.replaceCurrentItem(with:) raises NSException
|
|
480
|
+
// (uncatchable in Swift → process abort) if the item is still
|
|
481
|
+
// associated with another AVPlayer; AVPlayerItem.player
|
|
482
|
+
// releases asynchronously after the previous player calls
|
|
483
|
+
// replaceCurrentItem(with: nil) and there's no synchronous
|
|
484
|
+
// way to wait for the release, no public API to inspect the
|
|
485
|
+
// association. Mirror AVQueueBuilder.makeItem's construction
|
|
486
|
+
// (`automaticallyLoadedAssetKeys` + buffer pin) and carry
|
|
487
|
+
// queueItemId across so TrackPlayer.matchTrackIndex can still
|
|
488
|
+
// resolve the new item back to its queue position.
|
|
489
|
+
let target = AVPlayerItem(
|
|
490
|
+
// Prefer the lookahead cache's local file when the track has
|
|
491
|
+
// cached since the queue item was built from its remote URL —
|
|
492
|
+
// otherwise this fresh item re-loads the remote asset and the
|
|
493
|
+
// `.tracks` round-trip can fail the now-playing format extractor.
|
|
494
|
+
asset: cacheResolvedAsset(for: existing),
|
|
495
|
+
// Mirror AVQueueBuilder.makeItem fully — including "tracks". A forward
|
|
496
|
+
// skip to a cached track rebuilds the incoming leg here; without the
|
|
497
|
+
// tracks key auto-loading, the `.new`-only tracks KVO never fires for
|
|
498
|
+
// the fresh item and the now-playing format never resolves.
|
|
499
|
+
automaticallyLoadedAssetKeys: ["playable", "duration", "tracks"]
|
|
500
|
+
)
|
|
501
|
+
target.preferredForwardBufferDuration = 0.0
|
|
502
|
+
target.queueItemId = existing.queueItemId
|
|
503
|
+
target.sourceURL = existing.sourceURL
|
|
504
|
+
// Replace the queue slot with the rebuilt item — any cached
|
|
505
|
+
// AVPlayerItem reference held by a future callsite (e.g. an
|
|
506
|
+
// armCrossfade re-arming on this index) must read queue[index]
|
|
507
|
+
// afresh; the prior `existing` instance is now orphaned and
|
|
508
|
+
// its prior-player association will release asynchronously.
|
|
509
|
+
queue[index] = target
|
|
510
|
+
applyMixIfNeeded(to: target)
|
|
511
|
+
applyPitchAlgorithm(to: target)
|
|
512
|
+
leadingPlayer.replaceCurrentItem(with: target)
|
|
513
|
+
if position > 0 {
|
|
514
|
+
target.seek(
|
|
515
|
+
to: CMTime(seconds: position, preferredTimescale: 600),
|
|
516
|
+
completionHandler: nil
|
|
517
|
+
)
|
|
518
|
+
}
|
|
519
|
+
if wasPlaying {
|
|
520
|
+
// replaceCurrentItem leaves the player at rate 0 — resume at the
|
|
521
|
+
// user's speed (mirrors play() / completeCrossfade) so a skip while
|
|
522
|
+
// playing at a non-default speed doesn't fall back to 1.0x.
|
|
523
|
+
leadingPlayer.rate = userPlaybackSpeed
|
|
524
|
+
}
|
|
525
|
+
standbyArmed = false
|
|
526
|
+
installLeadingObservers()
|
|
527
|
+
updateRoutingArbiter()
|
|
528
|
+
delegate?.engineDidTransitionTrack(self)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
func skipToNext() {
|
|
532
|
+
let next = currentIndex + 1
|
|
533
|
+
if next < queue.count {
|
|
534
|
+
seek(toIndex: next, position: 0)
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
func skipToPrevious() {
|
|
539
|
+
let prev = currentIndex - 1
|
|
540
|
+
if prev >= 0 {
|
|
541
|
+
seek(toIndex: prev, position: 0)
|
|
542
|
+
} else {
|
|
543
|
+
leadingPlayer.currentItem?.seek(to: .zero, completionHandler: nil)
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// MARK: - Config
|
|
548
|
+
|
|
549
|
+
func setVolume(_ volume: Float) {
|
|
550
|
+
userVolume = volume
|
|
551
|
+
leadingPlayer.volume = volume
|
|
552
|
+
// Standby stays silent outside fade window.
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
func setPlaybackSpeed(_ speed: Float) {
|
|
556
|
+
userPlaybackSpeed = speed
|
|
557
|
+
leadingPlayer.rate = speed
|
|
558
|
+
// Crossing the 1.0 boundary changes the resolved algorithm; restamp
|
|
559
|
+
// both legs (standby prerolls at this rate).
|
|
560
|
+
reapplyPitchToCurrentLegs()
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
func seedPlaybackSpeed(_ speed: Float) {
|
|
564
|
+
// Persist only; `play()` applies it. Does not touch the leg rate,
|
|
565
|
+
// so it won't start a paused engine.
|
|
566
|
+
userPlaybackSpeed = speed
|
|
567
|
+
reapplyPitchToCurrentLegs()
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
func setRepeatMode(_ mode: RepeatMode) {
|
|
571
|
+
// `.queue` wrap is still driven by TrackPlayer rebuilding the queue
|
|
572
|
+
// (as on gapless). `.track` (repeat-one) is handled here because
|
|
573
|
+
// AVPlayer has no native repeat: the engine suppresses the fade and
|
|
574
|
+
// loops the current item on natural end.
|
|
575
|
+
repeatMode = mode
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// MARK: - State
|
|
579
|
+
|
|
580
|
+
/// During a crossfade the conceptual "active" track flips to the
|
|
581
|
+
/// standby leg the moment the fade begins (matches the JS-facing
|
|
582
|
+
/// `onTrackChange` + lock-screen / Now Playing flip the delegate
|
|
583
|
+
/// performs in response to `engineCrossfadeDidBegin`). All four
|
|
584
|
+
/// accessors below honour this: when `fadeInFlight`, they read
|
|
585
|
+
/// from `standbyPlayer` (the incoming leg) so `useProgress`,
|
|
586
|
+
/// seek clamps, preserve-current, and index lookups all see the
|
|
587
|
+
/// incoming track. Engine-internal code reaches `leadingPlayer`
|
|
588
|
+
/// / `currentIndex` directly.
|
|
589
|
+
var currentMediaItemIndex: Int {
|
|
590
|
+
if queue.isEmpty { return -1 }
|
|
591
|
+
return fadeInFlight ? currentIndex + 1 : currentIndex
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
var currentPositionSeconds: TimeInterval {
|
|
595
|
+
let player = fadeInFlight ? standbyPlayer : leadingPlayer
|
|
596
|
+
let t = player.currentTime()
|
|
597
|
+
return t.isNumeric ? CMTimeGetSeconds(t) : 0
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
var currentDurationSeconds: TimeInterval {
|
|
601
|
+
let player = fadeInFlight ? standbyPlayer : leadingPlayer
|
|
602
|
+
guard let d = player.currentItem?.duration, d.isNumeric else { return 0 }
|
|
603
|
+
return CMTimeGetSeconds(d)
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
var bufferedPositionSeconds: TimeInterval {
|
|
607
|
+
let player = fadeInFlight ? standbyPlayer : leadingPlayer
|
|
608
|
+
guard let ranges = player.currentItem?.loadedTimeRanges,
|
|
609
|
+
let last = ranges.last?.timeRangeValue else { return 0 }
|
|
610
|
+
return CMTimeGetSeconds(last.start + last.duration)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
var isPlaying: Bool {
|
|
614
|
+
// Reads the leading leg unconditionally (both legs play mid-fade); the
|
|
615
|
+
// `timeControlStatus` accessor below deliberately reads the active leg
|
|
616
|
+
// (standby while fading) to match `currentMediaItem` for state derivation.
|
|
617
|
+
leadingPlayer.timeControlStatus == .playing
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
var timeControlStatus: AVPlayer.TimeControlStatus {
|
|
621
|
+
// Mirror `currentMediaItem`'s leg selection: during a fade the standby
|
|
622
|
+
// (incoming) leg is the conceptually-active track, so its status is the
|
|
623
|
+
// one a consumer's spinner/state should reflect.
|
|
624
|
+
let player = fadeInFlight ? standbyPlayer : leadingPlayer
|
|
625
|
+
return player.timeControlStatus
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
var currentMediaItem: AVPlayerItem? {
|
|
629
|
+
let player = fadeInFlight ? standbyPlayer : leadingPlayer
|
|
630
|
+
return player.currentItem
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
var allMediaItems: [AVPlayerItem] {
|
|
634
|
+
queue
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// MARK: - Effect chain
|
|
638
|
+
|
|
639
|
+
func setAudioMixProvider(_ provider: AudioMixProvider?) {
|
|
640
|
+
mixProvider = provider
|
|
641
|
+
refreshAllItemMixes()
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
func setCachedURLResolver(_ resolver: ((String) -> URL?)?) {
|
|
645
|
+
cachedURLResolver = resolver
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/// The lookahead cache's local file URL for [item]'s source track,
|
|
649
|
+
/// when it has fully cached since the item was built; nil when no
|
|
650
|
+
/// resolver is wired, the item carries no source URL, or the cache
|
|
651
|
+
/// misses. Keyed by the verbatim `sourceURL` the cache was keyed
|
|
652
|
+
/// with — never the asset URL (already `file://` once local).
|
|
653
|
+
private func cachedLocalURL(for item: AVPlayerItem) -> URL? {
|
|
654
|
+
guard let resolver = cachedURLResolver, let url = item.sourceURL else {
|
|
655
|
+
return nil
|
|
656
|
+
}
|
|
657
|
+
return resolver(url)
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/// The asset a queue item should be (re)built from at a late attach:
|
|
661
|
+
/// the lookahead cache's local file when [existing]'s track has
|
|
662
|
+
/// since cached, else [existing]'s own asset. Re-resolving here lets
|
|
663
|
+
/// a forward/back skip to a now-cached track play from local disk
|
|
664
|
+
/// rather than re-loading the remote asset.
|
|
665
|
+
private func cacheResolvedAsset(for existing: AVPlayerItem) -> AVAsset {
|
|
666
|
+
// Reuse the existing asset when it's already this cache file (built
|
|
667
|
+
// cached at queue-build time) — the cross-attach rebuilds the item
|
|
668
|
+
// regardless, so there's nothing to upgrade and a fresh AVURLAsset
|
|
669
|
+
// on the same file would just be a wasted allocation.
|
|
670
|
+
guard let cachedURL = cachedLocalURL(for: existing),
|
|
671
|
+
(existing.asset as? AVURLAsset)?.url != cachedURL else {
|
|
672
|
+
return existing.asset
|
|
673
|
+
}
|
|
674
|
+
return AVURLAsset(url: cachedURL)
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/// If [index]'s queue item was built from a remote URL whose track
|
|
678
|
+
/// has since fully cached, swap the slot for a fresh item backed by
|
|
679
|
+
/// the local cache file (carrying `queueItemId` + `sourceURL` across)
|
|
680
|
+
/// and return it; otherwise return the existing item unchanged. Lets
|
|
681
|
+
/// the fade-arm preroll pick up a track that cached after the queue
|
|
682
|
+
/// was first built so an auto-advance crossfade plays from local disk.
|
|
683
|
+
private func cacheUpgradedItem(at index: Int) -> AVPlayerItem {
|
|
684
|
+
let existing = queue[index]
|
|
685
|
+
// Only rebuild on the genuine remote→cached transition. When the slot
|
|
686
|
+
// was already built from this cache file (cached at queue-build time),
|
|
687
|
+
// reuse the prepared item rather than discarding its buffers for an
|
|
688
|
+
// identical rebuild + re-preroll.
|
|
689
|
+
guard let cachedURL = cachedLocalURL(for: existing),
|
|
690
|
+
(existing.asset as? AVURLAsset)?.url != cachedURL else {
|
|
691
|
+
return existing
|
|
692
|
+
}
|
|
693
|
+
let upgraded = AVPlayerItem(
|
|
694
|
+
asset: AVURLAsset(url: cachedURL),
|
|
695
|
+
automaticallyLoadedAssetKeys: ["playable", "duration", "tracks"]
|
|
696
|
+
)
|
|
697
|
+
upgraded.preferredForwardBufferDuration = 0.0
|
|
698
|
+
upgraded.queueItemId = existing.queueItemId
|
|
699
|
+
upgraded.sourceURL = existing.sourceURL
|
|
700
|
+
queue[index] = upgraded
|
|
701
|
+
return upgraded
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
func refreshAllItemMixes() {
|
|
705
|
+
// Catch-up walk: only fill items that lack an `audioMix`. Items
|
|
706
|
+
// that already carry a tap are left alone (re-assigning on an
|
|
707
|
+
// already-rendering item allocates fresh `MTAudioProcessingTap`
|
|
708
|
+
// + `EQProcessor` cascade resources that AVPlayer silently
|
|
709
|
+
// drops, leaking them to ARC). See `GaplessEngine.refreshAllItemMixes`
|
|
710
|
+
// for the full rationale.
|
|
711
|
+
if let leading = leadingPlayer.currentItem, leading.audioMix == nil {
|
|
712
|
+
leading.audioMix = mixProvider?.audioMix(for: leading)
|
|
713
|
+
}
|
|
714
|
+
if let standby = standbyPlayer.currentItem, standby.audioMix == nil {
|
|
715
|
+
standby.audioMix = mixProvider?.audioMix(for: standby)
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
private func applyMixIfNeeded(to item: AVPlayerItem) {
|
|
720
|
+
guard let provider = mixProvider,
|
|
721
|
+
let mix = provider.audioMix(for: item) else { return }
|
|
722
|
+
item.audioMix = mix
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
func setPitchCorrectionMode(_ mode: PitchCorrectionMode) {
|
|
726
|
+
pitchCorrectionMode = mode
|
|
727
|
+
reapplyPitchToCurrentLegs()
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/// Stamp the resolved algorithm on `item`. Idempotent: a no-op when
|
|
731
|
+
/// the item already carries it, so re-applying on a rendering item
|
|
732
|
+
/// doesn't churn the audio graph.
|
|
733
|
+
private func applyPitchAlgorithm(to item: AVPlayerItem) {
|
|
734
|
+
let algo = PitchAlgorithm.resolve(
|
|
735
|
+
mode: pitchCorrectionMode,
|
|
736
|
+
speed: userPlaybackSpeed
|
|
737
|
+
)
|
|
738
|
+
if item.audioTimePitchAlgorithm != algo {
|
|
739
|
+
item.audioTimePitchAlgorithm = algo
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
private func reapplyPitchToCurrentLegs() {
|
|
744
|
+
if let leading = leadingPlayer.currentItem { applyPitchAlgorithm(to: leading) }
|
|
745
|
+
if let standby = standbyPlayer.currentItem { applyPitchAlgorithm(to: standby) }
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// MARK: - Handoff
|
|
749
|
+
|
|
750
|
+
func handoff(to nextEngine: PlaybackEngine, atTrackBoundary: Bool) async {
|
|
751
|
+
// Engine handoff (gapless ↔ crossfade) is driven by
|
|
752
|
+
// TrackPlayer.swift's mode-switch path which holds the
|
|
753
|
+
// canonical queue model. The engine's own `release()` is
|
|
754
|
+
// the caller's responsibility AFTER the swap.
|
|
755
|
+
return
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// MARK: - Equal-power gain math
|
|
759
|
+
|
|
760
|
+
/// Equal-power gain pair for fade progress `t ∈ [0, 1]`.
|
|
761
|
+
/// `(outgoing, incoming)` — sum-of-squares stays at 1.0
|
|
762
|
+
/// across the ramp so perceived loudness is constant.
|
|
763
|
+
static func equalPowerGains(t: Float) -> (Float, Float) {
|
|
764
|
+
let clamped = max(0, min(1, t))
|
|
765
|
+
let angle = clamped * Float.pi / 2
|
|
766
|
+
return (cos(angle), sin(angle))
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/// Approximate the equal-power curve via [subRampCount]
|
|
770
|
+
/// short linear segments. Returns the gain pair at each
|
|
771
|
+
/// step's start. Caller schedules each segment via
|
|
772
|
+
/// `setVolumeRamp(fromStartVolume:toEndVolume:timeRange:)`.
|
|
773
|
+
///
|
|
774
|
+
/// `setVolumeRamp` is linear-only per the AVFoundation API;
|
|
775
|
+
/// staircasing the cos/sin curve into N segments is the
|
|
776
|
+
/// supported approximation.
|
|
777
|
+
static func equalPowerSubRamps(
|
|
778
|
+
durationSeconds: TimeInterval,
|
|
779
|
+
steps: Int = subRampCount
|
|
780
|
+
) -> [(timeRange: CMTimeRange, outgoing: (start: Float, end: Float), incoming: (start: Float, end: Float))] {
|
|
781
|
+
guard steps > 0, durationSeconds > 0 else { return [] }
|
|
782
|
+
let stepSeconds = durationSeconds / Double(steps)
|
|
783
|
+
var ramps: [(CMTimeRange, (Float, Float), (Float, Float))] = []
|
|
784
|
+
for i in 0 ..< steps {
|
|
785
|
+
let tStart = Float(i) / Float(steps)
|
|
786
|
+
let tEnd = Float(i + 1) / Float(steps)
|
|
787
|
+
let (outStart, inStart) = equalPowerGains(t: tStart)
|
|
788
|
+
let (outEnd, inEnd) = equalPowerGains(t: tEnd)
|
|
789
|
+
let cmStart = CMTime(seconds: Double(i) * stepSeconds, preferredTimescale: 600)
|
|
790
|
+
let cmEnd = CMTime(seconds: Double(i + 1) * stepSeconds, preferredTimescale: 600)
|
|
791
|
+
let range = CMTimeRange(start: cmStart, end: cmEnd)
|
|
792
|
+
ramps.append((range, (outStart, outEnd), (inStart, inEnd)))
|
|
793
|
+
}
|
|
794
|
+
return ramps
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/// Auto-clamp the configured fade duration so the ramp never
|
|
798
|
+
/// overflows either leg's track length. Mirrors the Android
|
|
799
|
+
/// `effectiveCrossfadeMs`.
|
|
800
|
+
func effectiveCrossfadeSeconds(
|
|
801
|
+
leadingDurationSeconds: TimeInterval,
|
|
802
|
+
standbyDurationSeconds: TimeInterval
|
|
803
|
+
) -> TimeInterval {
|
|
804
|
+
let configured = crossfadeDurationSeconds
|
|
805
|
+
let leadingHalf = leadingDurationSeconds > 0
|
|
806
|
+
? leadingDurationSeconds / 2 : configured
|
|
807
|
+
let standbyHalf = standbyDurationSeconds > 0
|
|
808
|
+
? standbyDurationSeconds / 2 : configured
|
|
809
|
+
return max(0, min(min(configured, leadingHalf), standbyHalf))
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// MARK: - Fade cancel
|
|
813
|
+
|
|
814
|
+
/// Cancel any in-flight fade — strip the volume ramp from
|
|
815
|
+
/// both legs' current `audioMix`, snap leading to userVolume,
|
|
816
|
+
/// drop the standby's currentItem so its preroll bytes are
|
|
817
|
+
/// released. Internal entry point used by `pause` / `seek` /
|
|
818
|
+
/// `setItems` and by the route-monitor non-mixable callback.
|
|
819
|
+
func cancelFade() {
|
|
820
|
+
cancelFadeInternal()
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// MARK: - Boundary arming
|
|
824
|
+
|
|
825
|
+
/// Boundary-arming periodic time observer. Watches the leading
|
|
826
|
+
/// leg's time at 200ms cadence; when within
|
|
827
|
+
/// `effectiveCrossfadeSeconds + prerollLeadSeconds` of track end,
|
|
828
|
+
/// prerolls standby + schedules the equal-power ramp on both
|
|
829
|
+
/// legs' `AVMutableAudioMixInputParameters`.
|
|
830
|
+
///
|
|
831
|
+
/// Cancellation: `pause` / `stop` / `seek` / `skipToNext` /
|
|
832
|
+
/// `skipToPrevious` / `release` all remove the observer. `play`
|
|
833
|
+
/// re-installs it.
|
|
834
|
+
private func startArmingObserver() {
|
|
835
|
+
if armingObserver != nil { return }
|
|
836
|
+
let player = leadingPlayer
|
|
837
|
+
let interval = CMTime(seconds: 0.2, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
|
|
838
|
+
let token = player.addPeriodicTimeObserver(
|
|
839
|
+
forInterval: interval,
|
|
840
|
+
queue: .main
|
|
841
|
+
) { [weak self] _ in
|
|
842
|
+
self?.maybeArmCrossfade()
|
|
843
|
+
}
|
|
844
|
+
armingObserver = (player, token)
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
private func stopArmingObserver() {
|
|
848
|
+
if let observer = armingObserver {
|
|
849
|
+
observer.player.removeTimeObserver(observer.token)
|
|
850
|
+
}
|
|
851
|
+
armingObserver = nil
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
private func maybeArmCrossfade() {
|
|
855
|
+
if standbyArmed { return }
|
|
856
|
+
if fadeInFlight { return }
|
|
857
|
+
if !routeMonitor.isCurrentRouteMixable { return } // hard-cut on AirPlay/CarPlay
|
|
858
|
+
if crossfadeDurationSeconds <= 0 { return }
|
|
859
|
+
// Repeat-one loops the current item in place (see
|
|
860
|
+
// `handleItemDidPlayToEnd`); never arm a look-ahead fade, which
|
|
861
|
+
// would advance past the track the user asked to repeat.
|
|
862
|
+
if repeatMode == .track { return }
|
|
863
|
+
if currentIndex < 0 || currentIndex >= queue.count - 1 { return } // last track or empty
|
|
864
|
+
guard let leading = leadingPlayer.currentItem else { return }
|
|
865
|
+
let duration = leading.duration
|
|
866
|
+
if !duration.isNumeric { return }
|
|
867
|
+
let durationSeconds = CMTimeGetSeconds(duration)
|
|
868
|
+
if durationSeconds <= 0 { return }
|
|
869
|
+
let position = CMTimeGetSeconds(leading.currentTime())
|
|
870
|
+
// Standby duration unknown until prepared — pass 0 so
|
|
871
|
+
// effectiveCrossfadeSeconds falls through to the configured
|
|
872
|
+
// value with leading-half clamp only.
|
|
873
|
+
let effective = effectiveCrossfadeSeconds(
|
|
874
|
+
leadingDurationSeconds: durationSeconds,
|
|
875
|
+
standbyDurationSeconds: 0
|
|
876
|
+
)
|
|
877
|
+
let armThreshold = durationSeconds - effective - Self.prerollLeadSeconds
|
|
878
|
+
if position < armThreshold { return }
|
|
879
|
+
standbyArmed = true
|
|
880
|
+
armCrossfade(durationSeconds: effective)
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/// Preload the next queue item onto standby + schedule the
|
|
884
|
+
/// equal-power N-sub-ramp `setVolumeRamp` schedule on both legs'
|
|
885
|
+
/// `AVMutableAudioMixInputParameters`. Caller (the periodic
|
|
886
|
+
/// observer) has already validated route + index + arming state.
|
|
887
|
+
///
|
|
888
|
+
/// `AVPlayer.preroll(atRate:)` has TWO documented preconditions
|
|
889
|
+
/// per Apple's `AVPlayer.h`:
|
|
890
|
+
/// 1. `AVPlayer.status == .readyToPlay`
|
|
891
|
+
/// 2. `automaticallyWaitsToMinimizeStalling == false`
|
|
892
|
+
///
|
|
893
|
+
/// Violating either raises `NSInternalInconsistencyException`
|
|
894
|
+
/// synchronously. After `replaceCurrentItem` the status flip is
|
|
895
|
+
/// asynchronous — the asset has to load tracks/duration. KVO-
|
|
896
|
+
/// wait until the status reaches `.readyToPlay` before invoking
|
|
897
|
+
/// `preroll`; `runFade` chains through the readiness callback so
|
|
898
|
+
/// the timing semantics (preroll → ramp schedule → wall-clock
|
|
899
|
+
/// completion) are preserved. The pre-attach `seek(to: .zero)`
|
|
900
|
+
/// on the prior implementation was redundant — `replaceCurrentItem`
|
|
901
|
+
/// resets the item's playhead — and was masking the readiness
|
|
902
|
+
/// race because seek-on-detached-item is a documented no-op on
|
|
903
|
+
/// modern iOS.
|
|
904
|
+
private func armCrossfade(durationSeconds: TimeInterval) {
|
|
905
|
+
let nextIdx = currentIndex + 1
|
|
906
|
+
guard nextIdx < queue.count else {
|
|
907
|
+
standbyArmed = false
|
|
908
|
+
return
|
|
909
|
+
}
|
|
910
|
+
// Re-resolve against the cache: a track that finished caching after
|
|
911
|
+
// the queue was built is swapped for its local file here so the
|
|
912
|
+
// auto-advance fade plays from local disk, not the remote URL.
|
|
913
|
+
let nextItem = cacheUpgradedItem(at: nextIdx)
|
|
914
|
+
applyMixIfNeeded(to: nextItem)
|
|
915
|
+
applyPitchAlgorithm(to: nextItem)
|
|
916
|
+
let standby = standbyPlayer
|
|
917
|
+
standby.replaceCurrentItem(with: nextItem)
|
|
918
|
+
let priorAutoStall = standby.automaticallyWaitsToMinimizeStalling
|
|
919
|
+
awaitStandbyReady(standby) { [weak self] in
|
|
920
|
+
guard let self else { return }
|
|
921
|
+
if self.released || !self.standbyArmed { return }
|
|
922
|
+
// Explicit `tracks` key load on the freshly-replaced standby
|
|
923
|
+
// asset BEFORE preroll. `AVPlayerItem.status == .readyToPlay`
|
|
924
|
+
// does NOT guarantee the asset's tracks key is loaded —
|
|
925
|
+
// that's a separate async load. `runFade` requires the
|
|
926
|
+
// tracks to be present so it can attach the volume-ramp
|
|
927
|
+
// audioMix to the standby item.
|
|
928
|
+
let nextAsset = nextItem.asset
|
|
929
|
+
Task { @MainActor [weak self] in
|
|
930
|
+
do {
|
|
931
|
+
_ = try await nextAsset.load(.tracks)
|
|
932
|
+
} catch {
|
|
933
|
+
self?.cancelFadeInternal()
|
|
934
|
+
return
|
|
935
|
+
}
|
|
936
|
+
guard let self else { return }
|
|
937
|
+
if self.released || !self.standbyArmed { return }
|
|
938
|
+
standby.automaticallyWaitsToMinimizeStalling = false
|
|
939
|
+
// Await preroll via a checked continuation so the rest of
|
|
940
|
+
// the chain stays on @MainActor. Spawning a fresh Task
|
|
941
|
+
// inside `preroll`'s @escaping completion would create a
|
|
942
|
+
// @Sendable-closure-captures-non-Sendable-self warning
|
|
943
|
+
// because preroll's callback queue is unspecified.
|
|
944
|
+
_ = await withCheckedContinuation { (cont: CheckedContinuation<Bool, Never>) in
|
|
945
|
+
standby.preroll(atRate: 1.0) { ready in
|
|
946
|
+
cont.resume(returning: ready)
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
if self.released || !self.standbyArmed { return }
|
|
950
|
+
// Best-effort restore: cancel/release paths drop the
|
|
951
|
+
// standby item entirely, so a missed restore here is
|
|
952
|
+
// bounded by the next replaceCurrentItem.
|
|
953
|
+
standby.automaticallyWaitsToMinimizeStalling = priorAutoStall
|
|
954
|
+
self.runFade(durationSeconds: durationSeconds)
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/// One-shot KVO on `standby.status`. Fires [ready] once status
|
|
960
|
+
/// reaches `.readyToPlay`; fires immediately on the calling
|
|
961
|
+
/// runloop turn if already `.readyToPlay`. On `.failed` the
|
|
962
|
+
/// callback fires too — caller must check `released` /
|
|
963
|
+
/// `standbyArmed` before acting. Backed by a 5s wall-clock
|
|
964
|
+
/// timeout that calls `cancelFadeInternal` if the status never
|
|
965
|
+
/// flips (stuck-load defence).
|
|
966
|
+
private func awaitStandbyReady(_ player: AVPlayer, ready: @escaping () -> Void) {
|
|
967
|
+
standbyReadyObserver?.invalidate()
|
|
968
|
+
standbyReadyObserver = nil
|
|
969
|
+
standbyReadyTimeout?.cancel()
|
|
970
|
+
standbyReadyTimeout = nil
|
|
971
|
+
standbyReadyGen &+= 1
|
|
972
|
+
let myGen = standbyReadyGen
|
|
973
|
+
|
|
974
|
+
if player.status == .readyToPlay {
|
|
975
|
+
ready()
|
|
976
|
+
return
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
standbyReadyObserver = player.observe(\.status, options: [.new]) { [weak self] obs, _ in
|
|
980
|
+
let s = obs.status
|
|
981
|
+
// `.unknown` is the start state — wait for the first real flip.
|
|
982
|
+
guard s == .readyToPlay || s == .failed else { return }
|
|
983
|
+
DispatchQueue.main.async { [weak self] in
|
|
984
|
+
guard let self else { return }
|
|
985
|
+
// Cross-cycle race guard: if a new armCrossfade cycle
|
|
986
|
+
// started after this observer queued its main-async hop,
|
|
987
|
+
// the generation counter has advanced — this fire belongs
|
|
988
|
+
// to a stale standby and must not run `ready()` against
|
|
989
|
+
// the new arm's state.
|
|
990
|
+
if self.standbyReadyGen != myGen { return }
|
|
991
|
+
self.standbyReadyObserver?.invalidate()
|
|
992
|
+
self.standbyReadyObserver = nil
|
|
993
|
+
self.standbyReadyTimeout?.cancel()
|
|
994
|
+
self.standbyReadyTimeout = nil
|
|
995
|
+
if s == .readyToPlay {
|
|
996
|
+
ready()
|
|
997
|
+
} else {
|
|
998
|
+
// Standby load failed — drop the arm cleanly so the
|
|
999
|
+
// leading leg keeps playing through to its natural end.
|
|
1000
|
+
self.cancelFadeInternal()
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
let timeout = DispatchWorkItem { [weak self] in
|
|
1006
|
+
guard let self else { return }
|
|
1007
|
+
// Same cross-cycle guard for the timeout path.
|
|
1008
|
+
if self.standbyReadyGen != myGen { return }
|
|
1009
|
+
self.standbyReadyObserver?.invalidate()
|
|
1010
|
+
self.standbyReadyObserver = nil
|
|
1011
|
+
self.standbyReadyTimeout = nil
|
|
1012
|
+
self.cancelFadeInternal()
|
|
1013
|
+
}
|
|
1014
|
+
standbyReadyTimeout = timeout
|
|
1015
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0, execute: timeout)
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/// Input parameters to drive the fade ramp on [item]. Reuses the item's
|
|
1019
|
+
/// existing tap-bearing parameters when present so the EQ / visualizer /
|
|
1020
|
+
/// ReplayGain tap survives the fade; otherwise a bare ramp-only object
|
|
1021
|
+
/// keyed to [track]. Never re-runs the mix provider — that allocates a
|
|
1022
|
+
/// fresh MTAudioProcessingTap on an already-rendering item and leaks it
|
|
1023
|
+
/// (see refreshAllItemMixes).
|
|
1024
|
+
private func fadeParams(
|
|
1025
|
+
for item: AVPlayerItem,
|
|
1026
|
+
track: AVAssetTrack
|
|
1027
|
+
) -> AVMutableAudioMixInputParameters {
|
|
1028
|
+
if let existing = item.audioMix?.inputParameters.first
|
|
1029
|
+
as? AVMutableAudioMixInputParameters {
|
|
1030
|
+
return existing
|
|
1031
|
+
}
|
|
1032
|
+
return AVMutableAudioMixInputParameters(track: track)
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/// Schedule the equal-power N-sub-ramp `setVolumeRamp` schedule
|
|
1036
|
+
/// on both legs' currentItem `audioMix.inputParameters`. Caller
|
|
1037
|
+
/// has already prerolled standby. The fade runs against
|
|
1038
|
+
/// asset-time on each item; the post-fade swap is scheduled via
|
|
1039
|
+
/// a Task that sleeps for `durationSeconds` then promotes
|
|
1040
|
+
/// standby to leading.
|
|
1041
|
+
private func runFade(durationSeconds: TimeInterval) {
|
|
1042
|
+
if released { return }
|
|
1043
|
+
guard let leading = leadingPlayer.currentItem,
|
|
1044
|
+
let standby = standbyPlayer.currentItem else { return }
|
|
1045
|
+
|
|
1046
|
+
// BOTH sides must produce a valid AVAssetTrack; if either is
|
|
1047
|
+
// empty the fade can't ramp evenly and the user hears a hard
|
|
1048
|
+
// transition. armCrossfade has already awaited the standby
|
|
1049
|
+
// asset's tracks key load, so empty here is a real error —
|
|
1050
|
+
// fail loud, cancel cleanly, log via NSLog so the issue isn't
|
|
1051
|
+
// silent (the prior implementation's `if let` guard silently
|
|
1052
|
+
// skipped, which is exactly the failure mode this guard
|
|
1053
|
+
// exists to surface).
|
|
1054
|
+
// Read tracks via the AVPlayerItem.tracks chain — populated
|
|
1055
|
+
// at .readyToPlay and sync-accessible. Avoids the deprecated
|
|
1056
|
+
// sync `asset.tracks(withMediaType:)` (replaced by async
|
|
1057
|
+
// `loadTracks(withMediaType:)` in macOS 13 / iOS 16). The
|
|
1058
|
+
// standby asset's tracks key was already async-loaded in
|
|
1059
|
+
// armCrossfade so the chain is guaranteed populated by here.
|
|
1060
|
+
let leadingTrack = leading.tracks
|
|
1061
|
+
.first(where: { $0.assetTrack?.mediaType == .audio })?.assetTrack
|
|
1062
|
+
let standbyTrack = standby.tracks
|
|
1063
|
+
.first(where: { $0.assetTrack?.mediaType == .audio })?.assetTrack
|
|
1064
|
+
guard let leadingTrack, let standbyTrack else {
|
|
1065
|
+
NSLog("[RNQP-CROSSFADE] runFade aborting — missing audio track (leading=%@ standby=%@); cancelling fade",
|
|
1066
|
+
leadingTrack == nil ? "MISSING" : "ok",
|
|
1067
|
+
standbyTrack == nil ? "MISSING" : "ok")
|
|
1068
|
+
cancelFadeInternal()
|
|
1069
|
+
return
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
fadeInFlight = true
|
|
1073
|
+
|
|
1074
|
+
let leadingFadeStart = leading.currentTime()
|
|
1075
|
+
let ramps = Self.equalPowerSubRamps(durationSeconds: durationSeconds)
|
|
1076
|
+
|
|
1077
|
+
// Reuse each leg's EXISTING tap-bearing input parameters so the EQ /
|
|
1078
|
+
// visualizer / ReplayGain MTAudioProcessingTap keeps processing through
|
|
1079
|
+
// the fade window — the volume ramp and the tap coexist on one
|
|
1080
|
+
// AVMutableAudioMixInputParameters (the tap is pre-effects, so it sees
|
|
1081
|
+
// the track audio and the ramp scales the tapped output). A fresh tap is
|
|
1082
|
+
// never allocated here — re-running the provider on an already-rendering
|
|
1083
|
+
// item leaks tap resources (see refreshAllItemMixes). Falls back to a
|
|
1084
|
+
// bare ramp-only params object only when the leg carries no tap (no
|
|
1085
|
+
// EQ/viz/RG consumer, HLS, or tracks not yet loaded).
|
|
1086
|
+
let leadingParams = fadeParams(for: leading, track: leadingTrack)
|
|
1087
|
+
for ramp in ramps {
|
|
1088
|
+
let absStart = leadingFadeStart + ramp.timeRange.start
|
|
1089
|
+
let timeRange = CMTimeRange(start: absStart, duration: ramp.timeRange.duration)
|
|
1090
|
+
leadingParams.setVolumeRamp(
|
|
1091
|
+
fromStartVolume: ramp.outgoing.start,
|
|
1092
|
+
toEndVolume: ramp.outgoing.end,
|
|
1093
|
+
timeRange: timeRange
|
|
1094
|
+
)
|
|
1095
|
+
}
|
|
1096
|
+
let leadingMix = AVMutableAudioMix()
|
|
1097
|
+
leadingMix.inputParameters = [leadingParams]
|
|
1098
|
+
leading.audioMix = leadingMix
|
|
1099
|
+
|
|
1100
|
+
// Standby ramp anchored at asset-time zero, NOT
|
|
1101
|
+
// `standby.currentTime()`. After `replaceCurrentItem` +
|
|
1102
|
+
// `preroll(atRate:)` the standby's currentTime is unstable
|
|
1103
|
+
// across iOS versions until first decode pull lands; using
|
|
1104
|
+
// kCMTimeZero is the only reliable anchor for the head of a
|
|
1105
|
+
// freshly-attached AVPlayerItem.
|
|
1106
|
+
let standbyParams = fadeParams(for: standby, track: standbyTrack)
|
|
1107
|
+
for ramp in ramps {
|
|
1108
|
+
let timeRange = CMTimeRange(
|
|
1109
|
+
start: ramp.timeRange.start, duration: ramp.timeRange.duration
|
|
1110
|
+
)
|
|
1111
|
+
standbyParams.setVolumeRamp(
|
|
1112
|
+
fromStartVolume: ramp.incoming.start,
|
|
1113
|
+
toEndVolume: ramp.incoming.end,
|
|
1114
|
+
timeRange: timeRange
|
|
1115
|
+
)
|
|
1116
|
+
}
|
|
1117
|
+
let standbyMix = AVMutableAudioMix()
|
|
1118
|
+
standbyMix.inputParameters = [standbyParams]
|
|
1119
|
+
standby.audioMix = standbyMix
|
|
1120
|
+
|
|
1121
|
+
standbyPlayer.volume = userVolume // ramp inside the mix handles the actual envelope
|
|
1122
|
+
standbyPlayer.play()
|
|
1123
|
+
|
|
1124
|
+
// Active-track flip: the standby leg is now audibly playing
|
|
1125
|
+
// (volume 0 → ramping up). Notify the delegate so JS-facing
|
|
1126
|
+
// `onTrackChange` + `MPNowPlayingInfoCenter` retarget to the
|
|
1127
|
+
// incoming track immediately, not at fade-end. The accessor
|
|
1128
|
+
// group above (`currentMediaItem*`, `currentPositionSeconds`,
|
|
1129
|
+
// etc.) also flips at this point because `fadeInFlight = true`
|
|
1130
|
+
// is already set above; the delegate sees consistent state
|
|
1131
|
+
// across the callback.
|
|
1132
|
+
let incomingIdx = currentIndex + 1
|
|
1133
|
+
delegate?.engineCrossfadeDidBegin(self, incomingIndex: incomingIdx)
|
|
1134
|
+
|
|
1135
|
+
// Post-fade swap scheduled in wall-clock — the asset-time-keyed
|
|
1136
|
+
// ramps complete in this many seconds regardless of pause
|
|
1137
|
+
// because pausing also halts the asset clock. The Task handle
|
|
1138
|
+
// is stored so a subsequent `cancelFadeInternal` /
|
|
1139
|
+
// `release` can cancel it; without cancellation a stale
|
|
1140
|
+
// completion can run against a fresh fade after the user
|
|
1141
|
+
// pauses+resumes within one boundary window.
|
|
1142
|
+
fadeCompletionTask?.cancel()
|
|
1143
|
+
let durationNs = UInt64(durationSeconds * 1_000_000_000)
|
|
1144
|
+
fadeCompletionTask = Task { @MainActor [weak self] in
|
|
1145
|
+
do {
|
|
1146
|
+
try await Task.sleep(nanoseconds: durationNs)
|
|
1147
|
+
} catch {
|
|
1148
|
+
return // cancelled
|
|
1149
|
+
}
|
|
1150
|
+
self?.completeCrossfade()
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
/// Promote standby to leading at fade-end. Drops the old
|
|
1155
|
+
/// leading's currentItem; advances `currentIndex`; resets
|
|
1156
|
+
/// arming markers so the new leading can re-arm for its own
|
|
1157
|
+
/// next-track boundary; fires `engineDidTransitionTrack` so
|
|
1158
|
+
/// `TrackPlayer` can refresh JS-facing state.
|
|
1159
|
+
///
|
|
1160
|
+
/// Observer cleanup runs BEFORE the role flip so
|
|
1161
|
+
/// `removeTimeObserver` is called against the player that
|
|
1162
|
+
/// issued the token; reinstall runs AFTER the flip so the new
|
|
1163
|
+
/// observer attaches to the new leading. The fade ramp's
|
|
1164
|
+
/// audioMix wiped any provider-supplied EQ params on the new
|
|
1165
|
+
/// leading's currentItem — re-apply via `applyMixIfNeeded`
|
|
1166
|
+
/// after stripping the ramp mix.
|
|
1167
|
+
private func completeCrossfade() {
|
|
1168
|
+
if released { return }
|
|
1169
|
+
if !fadeInFlight { return }
|
|
1170
|
+
fadeInFlight = false
|
|
1171
|
+
fadeCompletionTask = nil
|
|
1172
|
+
stopArmingObserver()
|
|
1173
|
+
teardownLeadingObservers()
|
|
1174
|
+
let oldLeading = leadingPlayer
|
|
1175
|
+
oldLeading.pause()
|
|
1176
|
+
oldLeading.replaceCurrentItem(with: nil)
|
|
1177
|
+
oldLeading.volume = userVolume
|
|
1178
|
+
// The fade ramp's final tick already settled the standby (about
|
|
1179
|
+
// to be promoted to leading) at userVolume; no explicit write
|
|
1180
|
+
// needed across the toggle.
|
|
1181
|
+
currentRoleIsA.toggle()
|
|
1182
|
+
if let newLeading = leadingPlayer.currentItem {
|
|
1183
|
+
newLeading.audioMix = nil
|
|
1184
|
+
applyMixIfNeeded(to: newLeading)
|
|
1185
|
+
}
|
|
1186
|
+
// The promoted standby was prerolled + played at rate 1.0; restore
|
|
1187
|
+
// the user's playback speed so it survives the transition.
|
|
1188
|
+
leadingPlayer.rate = userPlaybackSpeed
|
|
1189
|
+
standbyArmed = false
|
|
1190
|
+
currentIndex += 1
|
|
1191
|
+
startArmingObserver()
|
|
1192
|
+
installLeadingObservers()
|
|
1193
|
+
updateRoutingArbiter()
|
|
1194
|
+
delegate?.engineDidTransitionTrack(self)
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
private func cancelFadeInternal(emitRevert: Bool = true) {
|
|
1198
|
+
fadeCompletionTask?.cancel()
|
|
1199
|
+
fadeCompletionTask = nil
|
|
1200
|
+
standbyReadyObserver?.invalidate()
|
|
1201
|
+
standbyReadyObserver = nil
|
|
1202
|
+
standbyReadyTimeout?.cancel()
|
|
1203
|
+
standbyReadyTimeout = nil
|
|
1204
|
+
// Capture pre-state: if a fade was in flight we already fired
|
|
1205
|
+
// `engineCrossfadeDidBegin` for `currentIndex + 1` — the
|
|
1206
|
+
// delegate believes the incoming is active. We must fire
|
|
1207
|
+
// `engineCrossfadeDidCancel(revertedIndex: currentIndex)` so
|
|
1208
|
+
// the delegate reverts active-track state back to the leading
|
|
1209
|
+
// leg. `emitRevert: false` is for callers that will immediately
|
|
1210
|
+
// set a new active track themselves (e.g. seek to a different
|
|
1211
|
+
// index) — emitting the intermediate revert would surface a
|
|
1212
|
+
// spurious track-change to the outgoing leg before the caller's
|
|
1213
|
+
// own track-change.
|
|
1214
|
+
let wasMidFade = fadeInFlight
|
|
1215
|
+
let leadingIdx = currentIndex
|
|
1216
|
+
fadeInFlight = false
|
|
1217
|
+
standbyArmed = false
|
|
1218
|
+
// Backward seek through a scheduled ramp window leaves the
|
|
1219
|
+
// mix in a wonky state per Wade Tregaskis — strip the mix
|
|
1220
|
+
// and re-apply the EQ-only mix to recover.
|
|
1221
|
+
if let leading = leadingPlayer.currentItem {
|
|
1222
|
+
leading.audioMix = nil
|
|
1223
|
+
applyMixIfNeeded(to: leading)
|
|
1224
|
+
}
|
|
1225
|
+
if let standby = standbyPlayer.currentItem {
|
|
1226
|
+
standby.audioMix = nil
|
|
1227
|
+
}
|
|
1228
|
+
leadingPlayer.volume = userVolume
|
|
1229
|
+
standbyPlayer.volume = 0
|
|
1230
|
+
standbyPlayer.replaceCurrentItem(with: nil)
|
|
1231
|
+
if wasMidFade && emitRevert {
|
|
1232
|
+
delegate?.engineCrossfadeDidCancel(self, revertedIndex: leadingIdx)
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// MARK: - Engine event observer install/teardown
|
|
1237
|
+
|
|
1238
|
+
/// Install KVO on the leading leg's `timeControlStatus` (drives
|
|
1239
|
+
/// `engineStateMaybeChanged` + `engineIsPlayingChanged`) and on
|
|
1240
|
+
/// the leading leg's currentItem `tracks` / `status` (drives
|
|
1241
|
+
/// format-ready + failed-to-load delegate fires). Called whenever
|
|
1242
|
+
/// the leading leg or its currentItem changes — i.e. from
|
|
1243
|
+
/// [setItems], [seek], and [completeCrossfade] after the role
|
|
1244
|
+
/// flip. Idempotent — teardown runs first inside.
|
|
1245
|
+
private func installLeadingObservers() {
|
|
1246
|
+
teardownLeadingObservers()
|
|
1247
|
+
let player = leadingPlayer
|
|
1248
|
+
leadingTimeControlObserver = player.observe(
|
|
1249
|
+
\.timeControlStatus, options: [.new]
|
|
1250
|
+
) { [weak self] obs, _ in
|
|
1251
|
+
let isPlayingNow = obs.timeControlStatus == .playing
|
|
1252
|
+
DispatchQueue.main.async {
|
|
1253
|
+
guard let self else { return }
|
|
1254
|
+
self.delegate?.engineStateMaybeChanged(self, reason: nil)
|
|
1255
|
+
self.delegate?.engineIsPlayingChanged(self, isPlaying: isPlayingNow)
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if let item = player.currentItem {
|
|
1259
|
+
leadingItemTracksObserver = item.observe(
|
|
1260
|
+
\.tracks, options: [.new]
|
|
1261
|
+
) { [weak self] obs, _ in
|
|
1262
|
+
// tracks fires on every mutation including transient empty
|
|
1263
|
+
// states during seek; only fire format-changed when the
|
|
1264
|
+
// tracks array is populated so the gapless KVO path's
|
|
1265
|
+
// dedup contract is matched.
|
|
1266
|
+
guard !obs.tracks.isEmpty else { return }
|
|
1267
|
+
DispatchQueue.main.async {
|
|
1268
|
+
guard let self else { return }
|
|
1269
|
+
self.delegate?.engineActiveItemFormatChanged(self)
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
// Bootstrap: `.new`-only KVO won't fire when the item's tracks are
|
|
1273
|
+
// ALREADY populated at install time — a cached / preloaded asset, e.g. a
|
|
1274
|
+
// skip to a lookahead-cached track. Without this the format-change never
|
|
1275
|
+
// propagates and the now-playing format stays nil. Mirrors what
|
|
1276
|
+
// `.initial` would have delivered.
|
|
1277
|
+
if !item.tracks.isEmpty {
|
|
1278
|
+
DispatchQueue.main.async { [weak self] in
|
|
1279
|
+
guard let self else { return }
|
|
1280
|
+
self.delegate?.engineActiveItemFormatChanged(self)
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
leadingItemStatusObserver = item.observe(
|
|
1284
|
+
\.status, options: [.new]
|
|
1285
|
+
) { [weak self] obs, _ in
|
|
1286
|
+
guard obs.status == .failed else { return }
|
|
1287
|
+
let underlying = obs.error ?? NSError(
|
|
1288
|
+
domain: "CrossfadeEngine", code: -1, userInfo: nil
|
|
1289
|
+
)
|
|
1290
|
+
DispatchQueue.main.async {
|
|
1291
|
+
guard let self else { return }
|
|
1292
|
+
self.delegate?.engineDidFailWithError(self, error: underlying)
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
private func teardownLeadingObservers() {
|
|
1299
|
+
leadingTimeControlObserver?.invalidate()
|
|
1300
|
+
leadingTimeControlObserver = nil
|
|
1301
|
+
leadingItemTracksObserver?.invalidate()
|
|
1302
|
+
leadingItemTracksObserver = nil
|
|
1303
|
+
leadingItemStatusObserver?.invalidate()
|
|
1304
|
+
leadingItemStatusObserver = nil
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
private func handleItemDidPlayToEnd(_ note: Notification) {
|
|
1308
|
+
guard let item = note.object as? AVPlayerItem,
|
|
1309
|
+
item === leadingPlayer.currentItem else { return }
|
|
1310
|
+
delegate?.engineDidPlayItemToEnd(self)
|
|
1311
|
+
// Repeat-one: loop the current item in place instead of advancing
|
|
1312
|
+
// or ending the queue. The fade is suppressed under `.track`, so
|
|
1313
|
+
// the item reaches its true end here; seek to zero + play replays
|
|
1314
|
+
// it, matching the gapless engine's repeat-track replay. Applies on
|
|
1315
|
+
// every track (incl. the last), so it must precede the queue-end check.
|
|
1316
|
+
if repeatMode == .track, !fadeInFlight, currentIndex >= 0 {
|
|
1317
|
+
leadingPlayer.seek(to: .zero)
|
|
1318
|
+
leadingPlayer.play()
|
|
1319
|
+
return
|
|
1320
|
+
}
|
|
1321
|
+
// Queue-end detection: leading played its last item to end
|
|
1322
|
+
// AND no fade is in flight (mid-fade end is normal — the
|
|
1323
|
+
// standby is about to be promoted, not the queue ending).
|
|
1324
|
+
// [currentIndex] is still pre-swap during a fade; gating on
|
|
1325
|
+
// [fadeInFlight] avoids firing enginePlaybackEnded one item
|
|
1326
|
+
// too early when the penultimate track ends naturally during
|
|
1327
|
+
// its outgoing fade.
|
|
1328
|
+
if !fadeInFlight,
|
|
1329
|
+
currentIndex >= 0,
|
|
1330
|
+
currentIndex >= queue.count - 1 {
|
|
1331
|
+
delegate?.enginePlaybackEnded(self)
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
private func handleItemFailedToPlayToEnd(_ note: Notification) {
|
|
1336
|
+
guard let item = note.object as? AVPlayerItem,
|
|
1337
|
+
item === leadingPlayer.currentItem else { return }
|
|
1338
|
+
let underlying = (note.userInfo?[AVPlayerItemFailedToPlayToEndTimeErrorKey] as? Error)
|
|
1339
|
+
?? item.error
|
|
1340
|
+
?? NSError(domain: "CrossfadeEngine", code: -2, userInfo: nil)
|
|
1341
|
+
delegate?.engineDidFailWithError(self, error: underlying)
|
|
1342
|
+
}
|
|
1343
|
+
}
|