From 29c63094807b20a475cea07a00f47c09294dd3f0 Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Mon, 16 Feb 2026 22:50:37 +0100 Subject: [PATCH 1/3] [iOS][core] Move FormatterTests to correct location (#43181) [skip ci] --- packages/expo-modules-core/{ => ios/Tests}/FormatterTests.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/expo-modules-core/{ => ios/Tests}/FormatterTests.swift (100%) diff --git a/packages/expo-modules-core/FormatterTests.swift b/packages/expo-modules-core/ios/Tests/FormatterTests.swift similarity index 100% rename from packages/expo-modules-core/FormatterTests.swift rename to packages/expo-modules-core/ios/Tests/FormatterTests.swift From dcdde8bc5423fe2e490f0b41380634f1608cba9d Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Mon, 16 Feb 2026 22:51:06 +0100 Subject: [PATCH 2/3] [iOS][core] Remove some unused code (#42949) --- packages/expo-modules-core/CHANGELOG.md | 1 + .../ios/Core/AppContext.swift | 29 +------------- .../Functions/AsyncFunctionDefinition.swift | 2 - .../ios/Core/ModuleHolder.swift | 17 -------- .../ios/Core/Views/ExpoView.swift | 39 ------------------- .../Views/SwiftUI/SwiftUIHostingView.swift | 6 --- .../Views/SwiftUI/SwiftUIViewDefinition.swift | 4 -- .../ios/Core/Views/ViewDefinition.swift | 5 --- .../ios/EXLegacyExpoViewProtocol.h | 13 ------- .../expo-modules-core/ios/ExpoModulesCore.h | 2 - .../ios/Fabric/ExpoFabricView.swift | 4 -- .../ios/Fabric/ExpoFabricViewObjC.h | 12 +----- .../ios/Fabric/ExpoFabricViewObjC.mm | 4 -- .../ios/Legacy/EXBridgeModule.h | 20 ---------- .../Legacy/Services/EXReactNativeAdapter.h | 3 +- 15 files changed, 5 insertions(+), 156 deletions(-) delete mode 100644 packages/expo-modules-core/ios/EXLegacyExpoViewProtocol.h delete mode 100644 packages/expo-modules-core/ios/Legacy/EXBridgeModule.h diff --git a/packages/expo-modules-core/CHANGELOG.md b/packages/expo-modules-core/CHANGELOG.md index 31c7be4e4f082c..502e581ebddea6 100644 --- a/packages/expo-modules-core/CHANGELOG.md +++ b/packages/expo-modules-core/CHANGELOG.md @@ -20,6 +20,7 @@ ### 💡 Others - Removed needless warning when `NativeModulesProxy` is absent. ([#43020](https://github.com/expo/expo/pull/43020) by [@tsapeta](https://github.com/tsapeta)) +- [iOS] Removed some unused code. ([#42949](https://github.com/expo/expo/pull/42949) by [@tsapeta](https://github.com/tsapeta)) ## 55.0.8 — 2026-02-08 diff --git a/packages/expo-modules-core/ios/Core/AppContext.swift b/packages/expo-modules-core/ios/Core/AppContext.swift index 1cae68687e6c4f..450f56827b8014 100644 --- a/packages/expo-modules-core/ios/Core/AppContext.swift +++ b/packages/expo-modules-core/ios/Core/AppContext.swift @@ -122,14 +122,10 @@ public final class AppContext: NSObject, @unchecked Sendable { */ @objc public var appIdentifier: String? { - #if RCT_NEW_ARCH_ENABLED guard let moduleRegistry = reactBridge?.moduleRegistry else { return nil } return "\(abs(ObjectIdentifier(moduleRegistry).hashValue))" - #else - return nil - #endif } /** @@ -364,27 +360,6 @@ public final class AppContext: NSObject, @unchecked Sendable { return moduleRegistry.get(moduleHolderForName: moduleName)?.javaScriptObject } - /** - Returns an array of event names supported by all Swift modules. - */ - @objc - public func getSupportedEvents() -> [String] { - return moduleRegistry.reduce(into: [String]()) { events, holder in - events.append(contentsOf: holder.definition.eventNames) - } - } - - /** - Modifies listeners count for module with given name. Depending on the listeners count, - `onStartObserving` and `onStopObserving` are called. - */ - @objc - public func modifyEventListenersCount(_ moduleName: String, count: Int) { - moduleRegistry - .get(moduleHolderForName: moduleName)? - .modifyListenersCount(count) - } - /** Asynchronously calls module's function with given arguments. */ @@ -479,19 +454,17 @@ public final class AppContext: NSObject, @unchecked Sendable { /** Registers native views defined by registered native modules. - - Note: It should stay private as `registerNativeModules` should be the only call site. Works only with the New Architecture. + - Note: It should stay private as `registerNativeModules` should be the only call site. - Todo: `RCTComponentViewFactory` is thread-safe, so this function should be as well. */ @MainActor private func registerNativeViews() { -#if RCT_NEW_ARCH_ENABLED for holder in moduleRegistry { for (key, viewDefinition) in holder.definition.views { let viewModule = ViewModuleWrapper(holder, viewDefinition, isDefaultModuleView: key == DEFAULT_MODULE_VIEW) ExpoFabricView.registerComponent(viewModule, appContext: self) } } -#endif } // MARK: - Runtime diff --git a/packages/expo-modules-core/ios/Core/Functions/AsyncFunctionDefinition.swift b/packages/expo-modules-core/ios/Core/Functions/AsyncFunctionDefinition.swift index 8f0efd9fe70c90..b1498b03428f91 100644 --- a/packages/expo-modules-core/ios/Core/Functions/AsyncFunctionDefinition.swift +++ b/packages/expo-modules-core/ios/Core/Functions/AsyncFunctionDefinition.swift @@ -138,7 +138,6 @@ public class AsyncFunctionDefinition: AnyAsyncFu let maxRetryCount = 3 queue.async { -#if RCT_NEW_ARCH_ENABLED // Checks if this is a view function unregistered in the view registry. The check can be performed from the main thread only. if retryCount < maxRetryCount, let viewTag = arguments.first as? Int, @@ -150,7 +149,6 @@ public class AsyncFunctionDefinition: AnyAsyncFu self.dispatchOnQueueUntilViewRegisters(appContext: appContext, arguments: arguments, queue: queue, retryCount: retryCount + 1, block) return } -#endif // Schedule the block as normal. block() } diff --git a/packages/expo-modules-core/ios/Core/ModuleHolder.swift b/packages/expo-modules-core/ios/Core/ModuleHolder.swift index 00348bc25001c6..63329be7d8f4a0 100644 --- a/packages/expo-modules-core/ios/Core/ModuleHolder.swift +++ b/packages/expo-modules-core/ios/Core/ModuleHolder.swift @@ -135,23 +135,6 @@ public final class ModuleHolder { } } - // MARK: JavaScript events - - /** - Modifies module's listeners count and calls `onStartObserving` or `onStopObserving` accordingly. - */ - func modifyListenersCount(_ count: Int) { - guard let appContext else { - return - } - if count > 0 && listenersCount == 0 { - definition.functions["startObserving"]?.call(withArguments: [], appContext: appContext) - } else if count < 0 && listenersCount + count <= 0 { - definition.functions["stopObserving"]?.call(withArguments: [], appContext: appContext) - } - listenersCount = max(0, listenersCount + count) - } - // MARK: Deallocation deinit { diff --git a/packages/expo-modules-core/ios/Core/Views/ExpoView.swift b/packages/expo-modules-core/ios/Core/Views/ExpoView.swift index dda145104b6e60..8efea77558880a 100644 --- a/packages/expo-modules-core/ios/Core/Views/ExpoView.swift +++ b/packages/expo-modules-core/ios/Core/Views/ExpoView.swift @@ -2,43 +2,4 @@ import React -#if RCT_NEW_ARCH_ENABLED public typealias ExpoView = ExpoFabricView -#else -/** - The view that extends `RCTView` which handles some styles (e.g. borders) and accessibility. - Inherit from `ExpoView` to keep this behavior and let your view use the associated `AppContext`. - */ -open class ExpoClassicView: RCTView, AnyExpoView { - /** - A weak pointer to the associated `AppContext`. - */ - public private(set) weak var appContext: AppContext? - - /** - The required initializer that receives an instance of `AppContext`. - Override it if the subclassing view needs to do something during initialization. - */ - required public init(appContext: AppContext? = nil) { - self.appContext = appContext - super.init(frame: .zero) - } - - // Mark the required init as unavailable so that subclasses can avoid overriding it. - @available(*, unavailable) - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public func updateProps(_ rawProps: [String: Any]) { - // Stub function – it's not used on the old architecture and non-SwiftUI views - } - - public func supportsProp(withName name: String) -> Bool { - // Stub function – it's not used on the old architecture and non-SwiftUI views - return false - } -} - -public typealias ExpoView = ExpoClassicView -#endif diff --git a/packages/expo-modules-core/ios/Core/Views/SwiftUI/SwiftUIHostingView.swift b/packages/expo-modules-core/ios/Core/Views/SwiftUI/SwiftUIHostingView.swift index a055159f33e9d8..f473415a82c0da 100644 --- a/packages/expo-modules-core/ios/Core/Views/SwiftUI/SwiftUIHostingView.swift +++ b/packages/expo-modules-core/ios/Core/Views/SwiftUI/SwiftUIHostingView.swift @@ -82,15 +82,11 @@ extension ExpoSwiftUI { super.init(appContext: appContext) shadowNodeProxy.setViewSize = { size in - #if RCT_NEW_ARCH_ENABLED self.setViewSize(size) - #endif } shadowNodeProxy.setStyleSize = { width, height in - #if RCT_NEW_ARCH_ENABLED self.setStyleSize(width, height: height) - #endif } shadowNodeProxy.objectWillChange.send() @@ -161,7 +157,6 @@ extension ExpoSwiftUI { setupHostingViewConstraints() } -#if RCT_NEW_ARCH_ENABLED /** Fabric calls this function when mounting (attaching) a child component view. */ @@ -202,7 +197,6 @@ extension ExpoSwiftUI { props.objectWillChange.send() } } -#endif // RCT_NEW_ARCH_ENABLED /** Setups layout constraints of the hosting controller view to match the layout set by React. diff --git a/packages/expo-modules-core/ios/Core/Views/SwiftUI/SwiftUIViewDefinition.swift b/packages/expo-modules-core/ios/Core/Views/SwiftUI/SwiftUIViewDefinition.swift index c5677e66338c6f..84b5f36f59ad71 100644 --- a/packages/expo-modules-core/ios/Core/Views/SwiftUI/SwiftUIViewDefinition.swift +++ b/packages/expo-modules-core/ios/Core/Views/SwiftUI/SwiftUIViewDefinition.swift @@ -93,7 +93,6 @@ extension ExpoSwiftUI { // In the ideal scenario it would be marked as `@MainActor`, but then `ViewModuleWrapper` // would be incompatible with `RCTViewManager` as it doesn't specify the actor. return MainActor.assumeIsolated { -#if RCT_NEW_ARCH_ENABLED let props = Props() props.appContext = appContext @@ -110,9 +109,6 @@ extension ExpoSwiftUI { // This is supported only on the new architecture, `dispatchEvent` exists only there. props.setUpEvents(view.dispatchEvent(_:payload:)) return AppleView.from(view) -#else - return AppleView.from(UnimplementedExpoView(appContext: appContext, text: "Rendering SwiftUI views is possible only with the New Architecture enabled")) -#endif } } diff --git a/packages/expo-modules-core/ios/Core/Views/ViewDefinition.swift b/packages/expo-modules-core/ios/Core/Views/ViewDefinition.swift index 1fdc8325527214..18904dc3a93573 100644 --- a/packages/expo-modules-core/ios/Core/Views/ViewDefinition.swift +++ b/packages/expo-modules-core/ios/Core/Views/ViewDefinition.swift @@ -56,16 +56,11 @@ public class ViewDefinition: ObjectDefinition, AnyViewDefinition, @unc // would be incompatible with `RCTViewManager` as it doesn't specify the actor. return MainActor.assumeIsolated { if let expoViewType = ViewType.self as? AnyExpoView.Type { -#if RCT_NEW_ARCH_ENABLED if let fabricViewType = ViewType.self as? ExpoFabricView.Type { return AppleView.from(ExpoFabricView.create(viewType: fabricViewType, viewDefinition: self, appContext: appContext)) } -#endif return AppleView.from(expoViewType.init(appContext: appContext)) } - if let legacyViewType = ViewType.self as? EXLegacyExpoViewProtocol.Type { - return AppleView.from(legacyViewType.init(moduleRegistry: appContext.legacyModuleRegistry) as? UIView) - } if let UIViewType = ViewType.self as? UIView.Type { return AppleView.from(UIViewType.init(frame: .zero)) } diff --git a/packages/expo-modules-core/ios/EXLegacyExpoViewProtocol.h b/packages/expo-modules-core/ios/EXLegacyExpoViewProtocol.h deleted file mode 100644 index 00aacdb0772809..00000000000000 --- a/packages/expo-modules-core/ios/EXLegacyExpoViewProtocol.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2022-present 650 Industries. All rights reserved. - -#import - -/** - The protocol required for the Objective-C views to be initialized with the legacy module registry. - - ToDo: Remove once all views are migrated to the new API and Swift. - */ -@protocol EXLegacyExpoViewProtocol - -- (instancetype)initWithModuleRegistry:(nullable EXModuleRegistry *)moduleRegistry; - -@end diff --git a/packages/expo-modules-core/ios/ExpoModulesCore.h b/packages/expo-modules-core/ios/ExpoModulesCore.h index 2490c43ff4eb6c..c90f976ffdb369 100644 --- a/packages/expo-modules-core/ios/ExpoModulesCore.h +++ b/packages/expo-modules-core/ios/ExpoModulesCore.h @@ -8,7 +8,6 @@ #import #import #import -#import #import #import #import @@ -37,7 +36,6 @@ #import #import #import -#import #import #import #import diff --git a/packages/expo-modules-core/ios/Fabric/ExpoFabricView.swift b/packages/expo-modules-core/ios/Fabric/ExpoFabricView.swift index 99b36a1d3a61ef..e0a35d8f683ab4 100644 --- a/packages/expo-modules-core/ios/Fabric/ExpoFabricView.swift +++ b/packages/expo-modules-core/ios/Fabric/ExpoFabricView.swift @@ -1,7 +1,5 @@ // Copyright 2022-present 650 Industries. All rights reserved. -#if RCT_NEW_ARCH_ENABLED - @objc(ExpoFabricView) open class ExpoFabricView: ExpoFabricViewObjC, AnyExpoView { /** @@ -229,5 +227,3 @@ open class ExpoFabricView: ExpoFabricViewObjC, AnyExpoView { } // swiftlint:enable unavailable_function } - -#endif // RCT_NEW_ARCH_ENABLED diff --git a/packages/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.h b/packages/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.h index 0e56f8b33061b0..df6762fea8f6de 100644 --- a/packages/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.h +++ b/packages/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.h @@ -2,8 +2,6 @@ #import -#ifdef RCT_NEW_ARCH_ENABLED - #ifdef __cplusplus #import // Allows non-umbrella since it's coming from React-RCTFabric @@ -13,19 +11,13 @@ #else -// Interface visible in Swift -@interface ExpoFabricViewObjC -@end - -#endif // __cplusplus -#else // Paper - #import +// Interface visible in Swift @interface ExpoFabricViewObjC : RCTView @end -#endif // !RCT_NEW_ARCH_ENABLED +#endif // __cplusplus @class EXAppContext; @class EXViewModuleWrapper; diff --git a/packages/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.mm b/packages/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.mm index bf8ad8c4b25a57..a9d1b453bbf499 100644 --- a/packages/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.mm +++ b/packages/expo-modules-core/ios/Fabric/ExpoFabricViewObjC.mm @@ -1,7 +1,5 @@ // Copyright 2022-present 650 Industries. All rights reserved. -#ifdef RCT_NEW_ARCH_ENABLED - #import #import @@ -216,5 +214,3 @@ + (void)registerComponent:(nonnull EXViewModuleWrapper *)viewModule appContext:( } @end - -#endif // RCT_NEW_ARCH_ENABLED diff --git a/packages/expo-modules-core/ios/Legacy/EXBridgeModule.h b/packages/expo-modules-core/ios/Legacy/EXBridgeModule.h deleted file mode 100644 index 8759bd927d62c2..00000000000000 --- a/packages/expo-modules-core/ios/Legacy/EXBridgeModule.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2018-present 650 Industries. All rights reserved. - -@protocol RCTBridgeModule; -@class RCTBridge; - -// Escape hatch for modules that both have to depend on React Native -// and want to be exported as an internal universal module. -#define EX_RCT_REGISTER_MODULE(external_name) \ - +(const NSString *)moduleName { return @ #external_name; } \ - EX_EXPORT_MODULE_WITH_CUSTOM_LOAD(external_name, \ - RCT_EXTERN void RCTRegisterModule(Class); \ - RCTRegisterModule(self);) - -@protocol EXBridgeModule - -@optional - -- (void)setBridge:(RCTBridge *)bridge; - -@end diff --git a/packages/expo-modules-core/ios/Legacy/Services/EXReactNativeAdapter.h b/packages/expo-modules-core/ios/Legacy/Services/EXReactNativeAdapter.h index cc624be00b8944..d487169802cf34 100644 --- a/packages/expo-modules-core/ios/Legacy/Services/EXReactNativeAdapter.h +++ b/packages/expo-modules-core/ios/Legacy/Services/EXReactNativeAdapter.h @@ -6,9 +6,8 @@ #import #import #import -#import -@interface EXReactNativeAdapter : NSObject +@interface EXReactNativeAdapter : NSObject - (void)setBridge:(RCTBridge *)bridge; From cf6cd0cb8f7ad6cb0bee8b15fceeefed212d9c23 Mon Sep 17 00:00:00 2001 From: Alan Hughes <30924086+alanjhughes@users.noreply.github.com> Date: Mon, 16 Feb 2026 23:37:38 +0000 Subject: [PATCH 3/3] Address review feedback from #43149 (#43177) --- .../expo-audio/build/AudioPlayer.web.d.ts | 3 + .../expo-audio/build/AudioPlayer.web.d.ts.map | 2 +- packages/expo-audio/build/AudioPlayer.web.js | 56 ++++++++++------- .../expo-audio/build/AudioPlayer.web.js.map | 2 +- packages/expo-audio/src/AudioPlayer.web.ts | 62 +++++++++++-------- 5 files changed, 74 insertions(+), 51 deletions(-) diff --git a/packages/expo-audio/build/AudioPlayer.web.d.ts b/packages/expo-audio/build/AudioPlayer.web.d.ts index 7cf9ccc2153182..540ca722185104 100644 --- a/packages/expo-audio/build/AudioPlayer.web.d.ts +++ b/packages/expo-audio/build/AudioPlayer.web.d.ts @@ -17,6 +17,7 @@ export declare class AudioPlayerWeb extends globalThis.expo.SharedObject; setAudioSamplingEnabled(enabled: boolean): void; + private startSampling; + private stopSampling; setPlaybackRate(second: number, pitchCorrectionQuality?: PitchCorrectionQuality): void; remove(): void; setActiveForLockScreen(active: boolean, metadata?: AudioMetadata, options?: AudioLockScreenOptions): void; diff --git a/packages/expo-audio/build/AudioPlayer.web.d.ts.map b/packages/expo-audio/build/AudioPlayer.web.d.ts.map index f589a9a616f295..f4f07abd6925a3 100644 --- a/packages/expo-audio/build/AudioPlayer.web.d.ts.map +++ b/packages/expo-audio/build/AudioPlayer.web.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"AudioPlayer.web.d.ts","sourceRoot":"","sources":["../src/AudioPlayer.web.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,sBAAsB,EACvB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAI/D,qBAAa,cACX,SAAQ,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAChD,YAAW,WAAW;gBAEV,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,kBAAuB;IASjE,EAAE,EAAE,MAAM,CAAY;IACtB,wBAAwB,UAAS;IACjC,WAAW,UAAS;IACpB,kBAAkB,UAAS;IAE3B,OAAO,CAAC,GAAG,CAAqB;IAChC,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,QAAQ,CAAO;IACvB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAC,CAAkC;IACtD,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,UAAU,CAA4C;IAC9D,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,eAAe,CAAS;IAEhC,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,OAAO,EAEvB;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,YAAY,CAAC,KAAK,EAAE,MAAM,EAE7B;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM,EAEvB;IAED,IAAI,aAAa,IAAI,WAAW,CAE/B;IAED,IAAI,IAAI,IAAI;IAKZ,KAAK,IAAI,IAAI;IAKb,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IA+B5B,MAAM,CACV,OAAO,EAAE,MAAM,EACf,qBAAqB,CAAC,EAAE,MAAM,EAC9B,oBAAoB,CAAC,EAAE,MAAM,GAC5B,OAAO,CAAC,IAAI,CAAC;IAIhB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAmE/C,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,sBAAsB,CAAC,EAAE,sBAAsB,GAAG,IAAI;IAOtF,MAAM,IAAI,IAAI;IAyBd,sBAAsB,CACpB,MAAM,EAAE,OAAO,EACf,QAAQ,CAAC,EAAE,aAAa,EACxB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,IAAI;IAQP,wBAAwB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IAIvD,uBAAuB,IAAI,IAAI;IAI/B,cAAc,IAAI,OAAO;IAQzB,mBAAmB,IAAI,gBAAgB;CAqExC"} \ No newline at end of file +{"version":3,"file":"AudioPlayer.web.d.ts","sourceRoot":"","sources":["../src/AudioPlayer.web.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,sBAAsB,EACvB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAI/D,qBAAa,cACX,SAAQ,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAChD,YAAW,WAAW;gBAEV,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,kBAAuB;IASjE,EAAE,EAAE,MAAM,CAAY;IACtB,wBAAwB,UAAS;IACjC,WAAW,UAAS;IACpB,kBAAkB,UAAS;IAE3B,OAAO,CAAC,GAAG,CAAqB;IAChC,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,QAAQ,CAAO;IACvB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAC,CAAkC;IACtD,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,UAAU,CAA4C;IAC9D,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,cAAc,CAA0C;IAEhE,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,OAAO,EAEvB;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,YAAY,CAAC,KAAK,EAAE,MAAM,EAE7B;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM,EAEvB;IAED,IAAI,aAAa,IAAI,WAAW,CAE/B;IAED,IAAI,IAAI,IAAI;IAMZ,KAAK,IAAI,IAAI;IAMb,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IA+B5B,MAAM,CACV,OAAO,EAAE,MAAM,EACf,qBAAqB,CAAC,EAAE,MAAM,EAC9B,oBAAoB,CAAC,EAAE,MAAM,GAC5B,OAAO,CAAC,IAAI,CAAC;IAIhB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAqD/C,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,YAAY;IAOpB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,sBAAsB,CAAC,EAAE,sBAAsB,GAAG,IAAI;IAOtF,MAAM,IAAI,IAAI;IAqBd,sBAAsB,CACpB,MAAM,EAAE,OAAO,EACf,QAAQ,CAAC,EAAE,aAAa,EACxB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,IAAI;IAQP,wBAAwB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IAIvD,uBAAuB,IAAI,IAAI;IAI/B,cAAc,IAAI,OAAO;IAQzB,mBAAmB,IAAI,gBAAgB;CAqExC"} \ No newline at end of file diff --git a/packages/expo-audio/build/AudioPlayer.web.js b/packages/expo-audio/build/AudioPlayer.web.js index 831c472741e550..d06c3f84633ed0 100644 --- a/packages/expo-audio/build/AudioPlayer.web.js +++ b/packages/expo-audio/build/AudioPlayer.web.js @@ -24,6 +24,7 @@ export class AudioPlayerWeb extends globalThis.expo.SharedObject { sourceNode = null; samplingFrameId = null; samplingEnabled = false; + samplingBuffer = null; get playing() { return this.isPlaying; } @@ -69,10 +70,12 @@ export class AudioPlayerWeb extends globalThis.expo.SharedObject { play() { this.media.play(); this.isPlaying = true; + this.startSampling(); } pause() { this.media.pause(); this.isPlaying = false; + this.stopSampling(); } replace(source) { const wasPlaying = this.isPlaying; @@ -119,29 +122,15 @@ export class AudioPlayerWeb extends globalThis.expo.SharedObject { this.sourceNode.disconnect(); this.sourceNode.connect(this.analyser); this.analyser.connect(ctx.destination); - const buffer = new Float32Array(this.analyser.frequencyBinCount); - const sampleLoop = () => { - if (!this.analyser) { - return; - } - if (this.isPlaying) { - this.analyser.getFloatTimeDomainData(buffer); - this.emit(AUDIO_SAMPLE_UPDATE, { - channels: [{ frames: Array.from(buffer) }], - timestamp: this.media.currentTime, - }); - } - this.samplingFrameId = requestAnimationFrame(sampleLoop); - }; - this.samplingFrameId = requestAnimationFrame(sampleLoop); + this.samplingBuffer = new Float32Array(this.analyser.frequencyBinCount); this.samplingEnabled = true; + if (this.isPlaying) { + this.startSampling(); + } this.isAudioSamplingSupported = true; } else { - if (this.samplingFrameId != null) { - cancelAnimationFrame(this.samplingFrameId); - this.samplingFrameId = null; - } + this.stopSampling(); if (this.analyser) { this.analyser.disconnect(); this.analyser = null; @@ -151,9 +140,33 @@ export class AudioPlayerWeb extends globalThis.expo.SharedObject { this.sourceNode.disconnect(); this.sourceNode.connect(ctx.destination); } + this.samplingBuffer = null; this.samplingEnabled = false; } } + startSampling() { + if (!this.samplingEnabled || !this.analyser || !this.samplingBuffer) { + return; + } + const sampleLoop = () => { + if (!this.analyser || !this.samplingBuffer) { + return; + } + this.analyser.getFloatTimeDomainData(this.samplingBuffer); + this.emit(AUDIO_SAMPLE_UPDATE, { + channels: [{ frames: Array.from(this.samplingBuffer) }], + timestamp: this.media.currentTime, + }); + this.samplingFrameId = requestAnimationFrame(sampleLoop); + }; + this.samplingFrameId = requestAnimationFrame(sampleLoop); + } + stopSampling() { + if (this.samplingFrameId != null) { + cancelAnimationFrame(this.samplingFrameId); + this.samplingFrameId = null; + } + } setPlaybackRate(second, pitchCorrectionQuality) { this.media.playbackRate = second; this.shouldCorrectPitch = pitchCorrectionQuality === 'high'; @@ -162,10 +175,7 @@ export class AudioPlayerWeb extends globalThis.expo.SharedObject { } remove() { mediaSessionController.clear(this); - if (this.samplingFrameId != null) { - cancelAnimationFrame(this.samplingFrameId); - this.samplingFrameId = null; - } + this.stopSampling(); if (this.analyser) { this.analyser.disconnect(); this.analyser = null; diff --git a/packages/expo-audio/build/AudioPlayer.web.js.map b/packages/expo-audio/build/AudioPlayer.web.js.map index 35ae2f8b20652d..7397fdb4ea3b6e 100644 --- a/packages/expo-audio/build/AudioPlayer.web.js.map +++ b/packages/expo-audio/build/AudioPlayer.web.js.map @@ -1 +1 @@ -{"version":3,"file":"AudioPlayer.web.js","sourceRoot":"","sources":["../src/AudioPlayer.web.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AudioMetadata,\n AudioPlayerOptions,\n AudioSource,\n AudioStatus,\n PitchCorrectionQuality,\n} from './Audio.types';\nimport { AudioLockScreenOptions } from './AudioConstants';\nimport { AUDIO_SAMPLE_UPDATE, PLAYBACK_STATUS_UPDATE } from './AudioEventKeys';\nimport { AudioPlayer, AudioEvents } from './AudioModule.types';\nimport { getAudioContext, getSourceUri, getStatusFromMedia, nextId } from './AudioUtils.web';\nimport { mediaSessionController } from './MediaSessionController.web';\n\nexport class AudioPlayerWeb\n extends globalThis.expo.SharedObject\n implements AudioPlayer\n{\n constructor(source: AudioSource, options: AudioPlayerOptions = {}) {\n super();\n const { updateInterval = 500, crossOrigin } = options;\n this.src = source;\n this.interval = Math.max(updateInterval, 1);\n this.crossOrigin = crossOrigin;\n this.media = this._createMediaElement();\n }\n\n id: number = nextId();\n isAudioSamplingSupported = false;\n isBuffering = false;\n shouldCorrectPitch = false;\n\n private src: AudioSource = null;\n private media: HTMLAudioElement;\n private interval = 500;\n private isPlaying = false;\n private loaded = false;\n private crossOrigin?: 'anonymous' | 'use-credentials';\n private analyser: AnalyserNode | null = null;\n private sourceNode: MediaElementAudioSourceNode | null = null;\n private samplingFrameId: number | null = null;\n private samplingEnabled = false;\n\n get playing(): boolean {\n return this.isPlaying;\n }\n\n get muted(): boolean {\n return this.media.muted;\n }\n\n set muted(value: boolean) {\n this.media.muted = value;\n }\n\n get loop(): boolean {\n return this.media.loop;\n }\n\n set loop(value: boolean) {\n this.media.loop = value;\n }\n\n get duration(): number {\n return this.media.duration;\n }\n\n get currentTime(): number {\n return this.media.currentTime;\n }\n\n get paused(): boolean {\n return this.media.paused;\n }\n\n get isLoaded(): boolean {\n return this.loaded;\n }\n\n get playbackRate(): number {\n return this.media.playbackRate;\n }\n\n set playbackRate(value: number) {\n this.media.playbackRate = value;\n }\n\n get volume(): number {\n return this.media.volume;\n }\n\n set volume(value: number) {\n this.media.volume = value;\n }\n\n get currentStatus(): AudioStatus {\n return getStatusFromMedia(this.media, this.id);\n }\n\n play(): void {\n this.media.play();\n this.isPlaying = true;\n }\n\n pause(): void {\n this.media.pause();\n this.isPlaying = false;\n }\n\n replace(source: AudioSource): void {\n const wasPlaying = this.isPlaying;\n const wasSampling = this.samplingEnabled;\n const mediaSessionState = mediaSessionController.getActiveState(this);\n\n // we need to remove the current media element and create a new one\n this.remove();\n\n this.src = source;\n this.isPlaying = false;\n this.loaded = false;\n this.media = this._createMediaElement();\n\n if (wasSampling) {\n this.setAudioSamplingEnabled(true);\n }\n\n if (mediaSessionState) {\n mediaSessionController.setActivePlayer(\n this,\n mediaSessionState.metadata ?? undefined,\n mediaSessionState.options ?? undefined\n );\n }\n\n // Resume playback if it was playing before\n if (wasPlaying) {\n this.play();\n }\n }\n\n async seekTo(\n seconds: number,\n toleranceMillisBefore?: number,\n toleranceMillisAfter?: number\n ): Promise {\n this.media.currentTime = seconds;\n }\n\n setAudioSamplingEnabled(enabled: boolean): void {\n if (enabled) {\n if (!this.media.crossOrigin && this._isCrossOrigin()) {\n this.isAudioSamplingSupported = false;\n return;\n }\n\n if (this.analyser) {\n return;\n }\n\n const ctx = getAudioContext();\n if (ctx.state === 'suspended') {\n ctx.resume();\n }\n\n if (!this.sourceNode) {\n this.sourceNode = ctx.createMediaElementSource(this.media);\n }\n\n this.analyser = ctx.createAnalyser();\n this.analyser.fftSize = 2048;\n\n this.sourceNode.disconnect();\n this.sourceNode.connect(this.analyser);\n this.analyser.connect(ctx.destination);\n\n const buffer = new Float32Array(this.analyser.frequencyBinCount);\n\n const sampleLoop = () => {\n if (!this.analyser) {\n return;\n }\n if (this.isPlaying) {\n this.analyser.getFloatTimeDomainData(buffer);\n this.emit(AUDIO_SAMPLE_UPDATE, {\n channels: [{ frames: Array.from(buffer) }],\n timestamp: this.media.currentTime,\n });\n }\n this.samplingFrameId = requestAnimationFrame(sampleLoop);\n };\n this.samplingFrameId = requestAnimationFrame(sampleLoop);\n\n this.samplingEnabled = true;\n this.isAudioSamplingSupported = true;\n } else {\n if (this.samplingFrameId != null) {\n cancelAnimationFrame(this.samplingFrameId);\n this.samplingFrameId = null;\n }\n\n if (this.analyser) {\n this.analyser.disconnect();\n this.analyser = null;\n }\n\n if (this.sourceNode) {\n const ctx = getAudioContext();\n this.sourceNode.disconnect();\n this.sourceNode.connect(ctx.destination);\n }\n\n this.samplingEnabled = false;\n }\n }\n\n setPlaybackRate(second: number, pitchCorrectionQuality?: PitchCorrectionQuality): void {\n this.media.playbackRate = second;\n this.shouldCorrectPitch = pitchCorrectionQuality === 'high';\n this.media.preservesPitch = this.shouldCorrectPitch;\n mediaSessionController.updatePositionState(this);\n }\n\n remove(): void {\n mediaSessionController.clear(this);\n\n if (this.samplingFrameId != null) {\n cancelAnimationFrame(this.samplingFrameId);\n this.samplingFrameId = null;\n }\n\n if (this.analyser) {\n this.analyser.disconnect();\n this.analyser = null;\n }\n\n if (this.sourceNode) {\n this.sourceNode.disconnect();\n this.sourceNode = null;\n }\n\n this.samplingEnabled = false;\n this.media.pause();\n this.media.removeAttribute('src');\n this.media.load();\n getStatusFromMedia(this.media, this.id);\n }\n\n setActiveForLockScreen(\n active: boolean,\n metadata?: AudioMetadata,\n options?: AudioLockScreenOptions\n ): void {\n if (active) {\n mediaSessionController.setActivePlayer(this, metadata, options);\n } else {\n mediaSessionController.clear(this);\n }\n }\n\n updateLockScreenMetadata(metadata: AudioMetadata): void {\n mediaSessionController.updateMetadata(this, metadata);\n }\n\n clearLockScreenControls(): void {\n mediaSessionController.clear(this);\n }\n\n _isCrossOrigin(): boolean {\n try {\n return new URL(this.media.src).origin !== window.location.origin;\n } catch {\n return false;\n }\n }\n\n _createMediaElement(): HTMLAudioElement {\n const newSource = getSourceUri(this.src);\n const media = new Audio(newSource);\n if (this.crossOrigin !== undefined) {\n media.crossOrigin = this.crossOrigin;\n }\n\n let lastEmitTime = 0;\n const intervalSec = this.interval / 1000;\n\n // Throttled status updates based on interval\n media.ontimeupdate = () => {\n const now = media.currentTime;\n // Handle backwards time (loop/seek)\n if (now < lastEmitTime) {\n lastEmitTime = now;\n }\n if (now - lastEmitTime >= intervalSec) {\n lastEmitTime = now;\n this.emit(PLAYBACK_STATUS_UPDATE, getStatusFromMedia(media, this.id));\n mediaSessionController.updatePositionState(this);\n }\n };\n\n media.onplay = () => {\n this.isPlaying = true;\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, {\n ...getStatusFromMedia(media, this.id),\n playing: this.isPlaying,\n });\n mediaSessionController.updatePlaybackState(this);\n mediaSessionController.updatePositionState(this);\n };\n\n media.onpause = () => {\n this.isPlaying = false;\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, {\n ...getStatusFromMedia(media, this.id),\n playing: this.isPlaying,\n });\n mediaSessionController.updatePlaybackState(this);\n mediaSessionController.updatePositionState(this);\n };\n\n media.onseeked = () => {\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, getStatusFromMedia(media, this.id));\n mediaSessionController.updatePositionState(this);\n };\n\n media.onended = () => {\n lastEmitTime = 0;\n mediaSessionController.updatePlaybackState(this);\n };\n\n media.onloadeddata = () => {\n this.loaded = true;\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, {\n ...getStatusFromMedia(media, this.id),\n isLoaded: this.loaded,\n });\n mediaSessionController.updatePositionState(this);\n };\n\n return media;\n }\n}\n"]} \ No newline at end of file +{"version":3,"file":"AudioPlayer.web.js","sourceRoot":"","sources":["../src/AudioPlayer.web.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AudioMetadata,\n AudioPlayerOptions,\n AudioSource,\n AudioStatus,\n PitchCorrectionQuality,\n} from './Audio.types';\nimport { AudioLockScreenOptions } from './AudioConstants';\nimport { AUDIO_SAMPLE_UPDATE, PLAYBACK_STATUS_UPDATE } from './AudioEventKeys';\nimport { AudioPlayer, AudioEvents } from './AudioModule.types';\nimport { getAudioContext, getSourceUri, getStatusFromMedia, nextId } from './AudioUtils.web';\nimport { mediaSessionController } from './MediaSessionController.web';\n\nexport class AudioPlayerWeb\n extends globalThis.expo.SharedObject\n implements AudioPlayer\n{\n constructor(source: AudioSource, options: AudioPlayerOptions = {}) {\n super();\n const { updateInterval = 500, crossOrigin } = options;\n this.src = source;\n this.interval = Math.max(updateInterval, 1);\n this.crossOrigin = crossOrigin;\n this.media = this._createMediaElement();\n }\n\n id: number = nextId();\n isAudioSamplingSupported = false;\n isBuffering = false;\n shouldCorrectPitch = false;\n\n private src: AudioSource = null;\n private media: HTMLAudioElement;\n private interval = 500;\n private isPlaying = false;\n private loaded = false;\n private crossOrigin?: 'anonymous' | 'use-credentials';\n private analyser: AnalyserNode | null = null;\n private sourceNode: MediaElementAudioSourceNode | null = null;\n private samplingFrameId: number | null = null;\n private samplingEnabled = false;\n private samplingBuffer: Float32Array | null = null;\n\n get playing(): boolean {\n return this.isPlaying;\n }\n\n get muted(): boolean {\n return this.media.muted;\n }\n\n set muted(value: boolean) {\n this.media.muted = value;\n }\n\n get loop(): boolean {\n return this.media.loop;\n }\n\n set loop(value: boolean) {\n this.media.loop = value;\n }\n\n get duration(): number {\n return this.media.duration;\n }\n\n get currentTime(): number {\n return this.media.currentTime;\n }\n\n get paused(): boolean {\n return this.media.paused;\n }\n\n get isLoaded(): boolean {\n return this.loaded;\n }\n\n get playbackRate(): number {\n return this.media.playbackRate;\n }\n\n set playbackRate(value: number) {\n this.media.playbackRate = value;\n }\n\n get volume(): number {\n return this.media.volume;\n }\n\n set volume(value: number) {\n this.media.volume = value;\n }\n\n get currentStatus(): AudioStatus {\n return getStatusFromMedia(this.media, this.id);\n }\n\n play(): void {\n this.media.play();\n this.isPlaying = true;\n this.startSampling();\n }\n\n pause(): void {\n this.media.pause();\n this.isPlaying = false;\n this.stopSampling();\n }\n\n replace(source: AudioSource): void {\n const wasPlaying = this.isPlaying;\n const wasSampling = this.samplingEnabled;\n const mediaSessionState = mediaSessionController.getActiveState(this);\n\n // we need to remove the current media element and create a new one\n this.remove();\n\n this.src = source;\n this.isPlaying = false;\n this.loaded = false;\n this.media = this._createMediaElement();\n\n if (wasSampling) {\n this.setAudioSamplingEnabled(true);\n }\n\n if (mediaSessionState) {\n mediaSessionController.setActivePlayer(\n this,\n mediaSessionState.metadata ?? undefined,\n mediaSessionState.options ?? undefined\n );\n }\n\n // Resume playback if it was playing before\n if (wasPlaying) {\n this.play();\n }\n }\n\n async seekTo(\n seconds: number,\n toleranceMillisBefore?: number,\n toleranceMillisAfter?: number\n ): Promise {\n this.media.currentTime = seconds;\n }\n\n setAudioSamplingEnabled(enabled: boolean): void {\n if (enabled) {\n if (!this.media.crossOrigin && this._isCrossOrigin()) {\n this.isAudioSamplingSupported = false;\n return;\n }\n\n if (this.analyser) {\n return;\n }\n\n const ctx = getAudioContext();\n if (ctx.state === 'suspended') {\n ctx.resume();\n }\n\n if (!this.sourceNode) {\n this.sourceNode = ctx.createMediaElementSource(this.media);\n }\n\n this.analyser = ctx.createAnalyser();\n this.analyser.fftSize = 2048;\n\n this.sourceNode.disconnect();\n this.sourceNode.connect(this.analyser);\n this.analyser.connect(ctx.destination);\n\n this.samplingBuffer = new Float32Array(this.analyser.frequencyBinCount);\n this.samplingEnabled = true;\n\n if (this.isPlaying) {\n this.startSampling();\n }\n this.isAudioSamplingSupported = true;\n } else {\n this.stopSampling();\n\n if (this.analyser) {\n this.analyser.disconnect();\n this.analyser = null;\n }\n\n if (this.sourceNode) {\n const ctx = getAudioContext();\n this.sourceNode.disconnect();\n this.sourceNode.connect(ctx.destination);\n }\n\n this.samplingBuffer = null;\n this.samplingEnabled = false;\n }\n }\n\n private startSampling(): void {\n if (!this.samplingEnabled || !this.analyser || !this.samplingBuffer) {\n return;\n }\n const sampleLoop = () => {\n if (!this.analyser || !this.samplingBuffer) {\n return;\n }\n this.analyser.getFloatTimeDomainData(this.samplingBuffer);\n this.emit(AUDIO_SAMPLE_UPDATE, {\n channels: [{ frames: Array.from(this.samplingBuffer) }],\n timestamp: this.media.currentTime,\n });\n this.samplingFrameId = requestAnimationFrame(sampleLoop);\n };\n this.samplingFrameId = requestAnimationFrame(sampleLoop);\n }\n\n private stopSampling(): void {\n if (this.samplingFrameId != null) {\n cancelAnimationFrame(this.samplingFrameId);\n this.samplingFrameId = null;\n }\n }\n\n setPlaybackRate(second: number, pitchCorrectionQuality?: PitchCorrectionQuality): void {\n this.media.playbackRate = second;\n this.shouldCorrectPitch = pitchCorrectionQuality === 'high';\n this.media.preservesPitch = this.shouldCorrectPitch;\n mediaSessionController.updatePositionState(this);\n }\n\n remove(): void {\n mediaSessionController.clear(this);\n this.stopSampling();\n\n if (this.analyser) {\n this.analyser.disconnect();\n this.analyser = null;\n }\n\n if (this.sourceNode) {\n this.sourceNode.disconnect();\n this.sourceNode = null;\n }\n\n this.samplingEnabled = false;\n this.media.pause();\n this.media.removeAttribute('src');\n this.media.load();\n getStatusFromMedia(this.media, this.id);\n }\n\n setActiveForLockScreen(\n active: boolean,\n metadata?: AudioMetadata,\n options?: AudioLockScreenOptions\n ): void {\n if (active) {\n mediaSessionController.setActivePlayer(this, metadata, options);\n } else {\n mediaSessionController.clear(this);\n }\n }\n\n updateLockScreenMetadata(metadata: AudioMetadata): void {\n mediaSessionController.updateMetadata(this, metadata);\n }\n\n clearLockScreenControls(): void {\n mediaSessionController.clear(this);\n }\n\n _isCrossOrigin(): boolean {\n try {\n return new URL(this.media.src).origin !== window.location.origin;\n } catch {\n return false;\n }\n }\n\n _createMediaElement(): HTMLAudioElement {\n const newSource = getSourceUri(this.src);\n const media = new Audio(newSource);\n if (this.crossOrigin !== undefined) {\n media.crossOrigin = this.crossOrigin;\n }\n\n let lastEmitTime = 0;\n const intervalSec = this.interval / 1000;\n\n // Throttled status updates based on interval\n media.ontimeupdate = () => {\n const now = media.currentTime;\n // Handle backwards time (loop/seek)\n if (now < lastEmitTime) {\n lastEmitTime = now;\n }\n if (now - lastEmitTime >= intervalSec) {\n lastEmitTime = now;\n this.emit(PLAYBACK_STATUS_UPDATE, getStatusFromMedia(media, this.id));\n mediaSessionController.updatePositionState(this);\n }\n };\n\n media.onplay = () => {\n this.isPlaying = true;\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, {\n ...getStatusFromMedia(media, this.id),\n playing: this.isPlaying,\n });\n mediaSessionController.updatePlaybackState(this);\n mediaSessionController.updatePositionState(this);\n };\n\n media.onpause = () => {\n this.isPlaying = false;\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, {\n ...getStatusFromMedia(media, this.id),\n playing: this.isPlaying,\n });\n mediaSessionController.updatePlaybackState(this);\n mediaSessionController.updatePositionState(this);\n };\n\n media.onseeked = () => {\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, getStatusFromMedia(media, this.id));\n mediaSessionController.updatePositionState(this);\n };\n\n media.onended = () => {\n lastEmitTime = 0;\n mediaSessionController.updatePlaybackState(this);\n };\n\n media.onloadeddata = () => {\n this.loaded = true;\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, {\n ...getStatusFromMedia(media, this.id),\n isLoaded: this.loaded,\n });\n mediaSessionController.updatePositionState(this);\n };\n\n return media;\n }\n}\n"]} \ No newline at end of file diff --git a/packages/expo-audio/src/AudioPlayer.web.ts b/packages/expo-audio/src/AudioPlayer.web.ts index eb4a0400d142ab..238184a4d7310b 100644 --- a/packages/expo-audio/src/AudioPlayer.web.ts +++ b/packages/expo-audio/src/AudioPlayer.web.ts @@ -39,6 +39,7 @@ export class AudioPlayerWeb private sourceNode: MediaElementAudioSourceNode | null = null; private samplingFrameId: number | null = null; private samplingEnabled = false; + private samplingBuffer: Float32Array | null = null; get playing(): boolean { return this.isPlaying; @@ -99,11 +100,13 @@ export class AudioPlayerWeb play(): void { this.media.play(); this.isPlaying = true; + this.startSampling(); } pause(): void { this.media.pause(); this.isPlaying = false; + this.stopSampling(); } replace(source: AudioSource): void { @@ -172,30 +175,15 @@ export class AudioPlayerWeb this.sourceNode.connect(this.analyser); this.analyser.connect(ctx.destination); - const buffer = new Float32Array(this.analyser.frequencyBinCount); - - const sampleLoop = () => { - if (!this.analyser) { - return; - } - if (this.isPlaying) { - this.analyser.getFloatTimeDomainData(buffer); - this.emit(AUDIO_SAMPLE_UPDATE, { - channels: [{ frames: Array.from(buffer) }], - timestamp: this.media.currentTime, - }); - } - this.samplingFrameId = requestAnimationFrame(sampleLoop); - }; - this.samplingFrameId = requestAnimationFrame(sampleLoop); - + this.samplingBuffer = new Float32Array(this.analyser.frequencyBinCount); this.samplingEnabled = true; + + if (this.isPlaying) { + this.startSampling(); + } this.isAudioSamplingSupported = true; } else { - if (this.samplingFrameId != null) { - cancelAnimationFrame(this.samplingFrameId); - this.samplingFrameId = null; - } + this.stopSampling(); if (this.analyser) { this.analyser.disconnect(); @@ -208,10 +196,36 @@ export class AudioPlayerWeb this.sourceNode.connect(ctx.destination); } + this.samplingBuffer = null; this.samplingEnabled = false; } } + private startSampling(): void { + if (!this.samplingEnabled || !this.analyser || !this.samplingBuffer) { + return; + } + const sampleLoop = () => { + if (!this.analyser || !this.samplingBuffer) { + return; + } + this.analyser.getFloatTimeDomainData(this.samplingBuffer); + this.emit(AUDIO_SAMPLE_UPDATE, { + channels: [{ frames: Array.from(this.samplingBuffer) }], + timestamp: this.media.currentTime, + }); + this.samplingFrameId = requestAnimationFrame(sampleLoop); + }; + this.samplingFrameId = requestAnimationFrame(sampleLoop); + } + + private stopSampling(): void { + if (this.samplingFrameId != null) { + cancelAnimationFrame(this.samplingFrameId); + this.samplingFrameId = null; + } + } + setPlaybackRate(second: number, pitchCorrectionQuality?: PitchCorrectionQuality): void { this.media.playbackRate = second; this.shouldCorrectPitch = pitchCorrectionQuality === 'high'; @@ -221,11 +235,7 @@ export class AudioPlayerWeb remove(): void { mediaSessionController.clear(this); - - if (this.samplingFrameId != null) { - cancelAnimationFrame(this.samplingFrameId); - this.samplingFrameId = null; - } + this.stopSampling(); if (this.analyser) { this.analyser.disconnect();