diff --git a/CHANGELOG.md b/CHANGELOG.md index 95ff3a5fc..d9d1d043d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- Fixed a memory leak on iOS, where the presentationModeManager was holding a strong reference to the fullscreen's target and return views +- Fixed an issue on iOS where the destruction of the THEOplayerView was not always propagated correctly over the iOS Bridge, resulting in an occasional memory leak. + ## [8.11.1] - 24-12-18 ### Fixed diff --git a/example/src/App.tsx b/example/src/App.tsx index 17696e5d5..2e88ea532 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -83,7 +83,7 @@ export default function App() { player.source = SOURCES[0].source; - player.backgroundAudioConfiguration = { enabled: true }; + player.backgroundAudioConfiguration = { enabled: true, shouldResumeAfterInterruption: true }; player.pipConfiguration = { startsAutomatically: true }; console.log('THEOplayer is ready'); diff --git a/ios/THEOplayerRCTBridge.m b/ios/THEOplayerRCTBridge.m index 5f795882c..a59bb8d13 100644 --- a/ios/THEOplayerRCTBridge.m +++ b/ios/THEOplayerRCTBridge.m @@ -112,8 +112,6 @@ @interface RCT_EXTERN_REMAP_MODULE(THEORCTPlayerModule, THEOplayerRCTPlayerAPI, RCT_EXTERN_METHOD(setTextTrackStyle:(nonnull NSNumber *)node textTrackStyle:(NSDictionary)textTrackStyle) -RCT_EXTERN_METHOD(destroyPlayer:(nonnull NSNumber *)node); - @end // ---------------------------------------------------------------------------- diff --git a/ios/THEOplayerRCTPlayerAPI.swift b/ios/THEOplayerRCTPlayerAPI.swift index 9d2eff052..789176d97 100644 --- a/ios/THEOplayerRCTPlayerAPI.swift +++ b/ios/THEOplayerRCTPlayerAPI.swift @@ -356,14 +356,4 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule { } } } - - @objc(destroyPlayer:) - func destroyPlayer(_ node: NSNumber) -> Void { - DispatchQueue.main.async { - if let theView = self.bridge.uiManager.view(forReactTag: node) as? THEOplayerRCTView { - theView.destroyPlayer() - } - } - } - } diff --git a/ios/THEOplayerRCTView.swift b/ios/THEOplayerRCTView.swift index a3c0738da..9c5ef2e77 100644 --- a/ios/THEOplayerRCTView.swift +++ b/ios/THEOplayerRCTView.swift @@ -9,15 +9,16 @@ public class THEOplayerRCTView: UIView { public private(set) var player: THEOplayer? public private(set) var mainEventHandler: THEOplayerRCTMainEventHandler public private(set) var broadcastEventHandler: THEOplayerRCTBroadcastEventHandler - let theoPlayerViewController = UIViewController() var textTrackEventHandler: THEOplayerRCTTextTrackEventHandler var mediaTrackEventHandler: THEOplayerRCTMediaTrackEventHandler var metadataTrackEventHandler: THEOplayerRCTSideloadedMetadataTrackEventHandler var adEventHandler: THEOplayerRCTAdsEventHandler var castEventHandler: THEOplayerRCTCastEventHandler var presentationModeManager: THEOplayerRCTPresentationModeManager + var backgroundAudioManager: THEOplayerRCTBackgroundAudioManager var nowPlayingManager: THEOplayerRCTNowPlayingManager var remoteCommandsManager: THEOplayerRCTRemoteCommandsManager + var pipManager: THEOplayerRCTPipManager var pipControlsManager: THEOplayerRCTPipControlsManager var adsConfig = AdsConfig() @@ -36,8 +37,8 @@ public class THEOplayerRCTView: UIView { } var backgroundAudioConfig = BackgroundAudioConfig() { didSet { - self.updateInterruptionNotifications() - self.updateAVAudioSessionMode() + self.backgroundAudioManager.updateInterruptionNotifications() + self.backgroundAudioManager.updateAVAudioSessionMode() } } @@ -61,8 +62,10 @@ public class THEOplayerRCTView: UIView { self.adEventHandler = THEOplayerRCTAdsEventHandler() self.castEventHandler = THEOplayerRCTCastEventHandler() self.presentationModeManager = THEOplayerRCTPresentationModeManager() + self.backgroundAudioManager = THEOplayerRCTBackgroundAudioManager() self.nowPlayingManager = THEOplayerRCTNowPlayingManager() self.remoteCommandsManager = THEOplayerRCTRemoteCommandsManager() + self.pipManager = THEOplayerRCTPipManager() self.pipControlsManager = THEOplayerRCTPipControlsManager() super.init(frame: .zero) @@ -71,20 +74,32 @@ public class THEOplayerRCTView: UIView { required init?(coder aDecoder: NSCoder) { fatalError("[NATIVE] init(coder:) has not been implemented") } + + deinit { + self.mainEventHandler.destroy() + self.textTrackEventHandler.destroy() + self.mediaTrackEventHandler.destroy() + self.adEventHandler.destroy() + self.castEventHandler.destroy() + self.nowPlayingManager.destroy() + self.remoteCommandsManager.destroy() + self.pipManager.destroy() + self.pipControlsManager.destroy() + self.presentationModeManager.destroy() + self.backgroundAudioManager.destroy() + + self.destroyBackgroundAudio() + self.player?.removeAllIntegrations() + self.player?.destroy() + self.player = nil + if DEBUG_THEOPLAYER_INTERACTION { PrintUtils.printLog(logText: "[NATIVE] THEOplayer instance destroyed.") } + } override public func layoutSubviews() { super.layoutSubviews() if let player = self.player { player.frame = self.frame player.autoresizingMask = [.flexibleBottomMargin, .flexibleHeight, .flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleWidth] - - // wrap theoPlayerViewController around the view - if theoPlayerViewController.parent == nil, - let parentViewController = self.findViewController() { - parentViewController.addChild(self.theoPlayerViewController) - self.theoPlayerViewController.didMove(toParent: parentViewController) - self.theoPlayerViewController.view = self - } } } @@ -97,12 +112,14 @@ public class THEOplayerRCTView: UIView { self.mainEventHandler.setPlayer(player) self.textTrackEventHandler.setPlayer(player) self.mediaTrackEventHandler.setPlayer(player) - self.presentationModeManager.setPlayer(player, view: self) self.adEventHandler.setPlayer(player) self.castEventHandler.setPlayer(player) self.nowPlayingManager.setPlayer(player) self.remoteCommandsManager.setPlayer(player) self.pipControlsManager.setPlayer(player) + self.presentationModeManager.setPlayer(player, view: self) + self.backgroundAudioManager.setPlayer(player, view: self) + self.pipManager.setView(view: self) // Attach player to view player.addAsSubview(of: self) } @@ -137,28 +154,6 @@ public class THEOplayerRCTView: UIView { return self.player } - // MARK: - Destroy Player - - public func destroyPlayer() { - self.mainEventHandler.destroy() - self.textTrackEventHandler.destroy() - self.mediaTrackEventHandler.destroy() - self.adEventHandler.destroy() - self.castEventHandler.destroy() - self.nowPlayingManager.destroy() - self.remoteCommandsManager.destroy() - self.pipControlsManager.destroy() - - self.destroyBackgroundAudio() - self.player?.removeAllIntegrations() - self.player?.destroy() - self.player = nil - if DEBUG_THEOPLAYER_INTERACTION { PrintUtils.printLog(logText: "[NATIVE] THEOplayer instance destroyed.") } - - self.theoPlayerViewController.view = nil - self.theoPlayerViewController.removeFromParent() - } - func processMetadataTracks(metadataTrackDescriptions: [TextTrackDescription]?) { THEOplayerRCTSideloadedMetadataProcessor.loadTrackInfoFromTrackDescriptions(metadataTrackDescriptions) { tracksInfo in self.mainEventHandler.setLoadedMetadataTracksInfo(tracksInfo) diff --git a/ios/THEOplayerRCTViewManager.swift b/ios/THEOplayerRCTViewManager.swift index 227314582..20b605151 100644 --- a/ios/THEOplayerRCTViewManager.swift +++ b/ios/THEOplayerRCTViewManager.swift @@ -16,13 +16,4 @@ class THEOplayerRCTViewManager: RCTViewManager { override class func requiresMainQueueSetup() -> Bool { return true } - - @objc func destroy(_ node: NSNumber) { - DispatchQueue.main.async { - let theView = self.bridge.uiManager.view( - forReactTag: node - ) as! THEOplayerRCTView - theView.destroyPlayer() - } - } } diff --git a/ios/backgroundAudio/THEOplayerRCTBackgroundAudioManager.swift b/ios/backgroundAudio/THEOplayerRCTBackgroundAudioManager.swift new file mode 100644 index 000000000..147a1973a --- /dev/null +++ b/ios/backgroundAudio/THEOplayerRCTBackgroundAudioManager.swift @@ -0,0 +1,105 @@ +// TTHEOplayerRCTBackgroundAudioManager.swift + +import Foundation +import THEOplayerSDK +import AVFAudio +import AVKit + +struct BackgroundAudioConfig { + var enabled: Bool = false + var shouldResumeAfterInterruption: Bool = false + var audioSessionMode: AVAudioSession.Mode = .moviePlayback +} + +class THEOplayerRCTBackgroundAudioManager: NSObject, BackgroundPlaybackDelegate { + // MARK: Members + private weak var player: THEOplayer? + private weak var view: THEOplayerRCTView? + + // MARK: - player setup / breakdown + func setPlayer(_ player: THEOplayer, view: THEOplayerRCTView?) { + self.player = player + self.view = view + } + + // MARK: - destruction + func destroy() { + self.cancelInterruptionNotifications() + } + + // MARK: - logic + func shouldContinueAudioPlaybackInBackground() -> Bool { + if let view = self.view { + view.nowPlayingManager.updateNowPlaying() + return view.backgroundAudioConfig.enabled + } + return false + } + + func cancelInterruptionNotifications() { + NotificationCenter.default.removeObserver(self, + name: AVAudioSession.interruptionNotification, + object: AVAudioSession.sharedInstance()) + } + + func updateInterruptionNotifications() { + guard let view = self.view else { return } + + // Get the default notification center instance. + if view.backgroundAudioConfig.shouldResumeAfterInterruption { + NotificationCenter.default.addObserver(self, + selector: #selector(handleInterruption), + name: AVAudioSession.interruptionNotification, + object: AVAudioSession.sharedInstance()) + } else { + NotificationCenter.default.removeObserver(self, + name: AVAudioSession.interruptionNotification, + object: AVAudioSession.sharedInstance()) + } + } + + func updateAVAudioSessionMode() { + guard let view = self.view else { return } + + do { + THEOplayer.automaticallyManageAudioSession = (view.backgroundAudioConfig.audioSessionMode == .moviePlayback) + try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: view.backgroundAudioConfig.audioSessionMode) + if view.backgroundAudioConfig.audioSessionMode != .moviePlayback { + if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[NATIVE] AVAudioSession mode updated to \(view.backgroundAudioConfig.audioSessionMode.rawValue)") } + } + } catch { + if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[NATIVE] Unable to update AVAudioSession mode to \(view.backgroundAudioConfig.audioSessionMode.rawValue): \(error)") } + } + } + + @objc func handleInterruption(notification: Notification) { + guard let userInfo = notification.userInfo, + let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, + let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { + return + } + + // Switch over the interruption type. + switch type { + case .began: + // An interruption began. Update the UI as necessary. + if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[NATIVE] An interruption began")} + case .ended: + // An interruption ended. Resume playback, if appropriate. + if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[NATIVE] An interruption ended")} + guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return } + let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue) + if options.contains(.shouldResume) { + // An interruption ended. Resume playback. + if let player = self.player { + if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[NATIVE] Ended interruption should resume playback => play()")} + player.play() + } + } else { + // An interruption ended. Don't resume playback. + if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[NATIVE] Ended interruption should not resume playback.")} + } + default: () + } + } +} diff --git a/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift b/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift index 4d2ff6728..ff0039c43 100644 --- a/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift +++ b/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift @@ -2,96 +2,25 @@ import Foundation import THEOplayerSDK -import AVFAudio -import AVKit - -struct BackgroundAudioConfig { - var enabled: Bool = false - var shouldResumeAfterInterruption: Bool = false - var audioSessionMode: AVAudioSession.Mode = .moviePlayback -} - -extension THEOplayerRCTView: BackgroundPlaybackDelegate { +extension THEOplayerRCTView { func initBackgroundAudio() { - self.player?.backgroundPlaybackDelegate = self - } - - func destroyBackgroundAudio() { guard let player = self.player else { return } - player.backgroundPlaybackDelegate = DefaultBackgroundPlaybackDelegate() - NotificationCenter.default.removeObserver(self, - name: AVAudioSession.interruptionNotification, - object: AVAudioSession.sharedInstance()) - } - - public func shouldContinueAudioPlaybackInBackground() -> Bool { - // Make sure to go to the background with updated NowPlayingInfo - self.nowPlayingManager.updateNowPlaying() - - return self.backgroundAudioConfig.enabled - } - - func updateInterruptionNotifications() { - // Get the default notification center instance. - if self.backgroundAudioConfig.shouldResumeAfterInterruption { - NotificationCenter.default.addObserver(self, - selector: #selector(handleInterruption), - name: AVAudioSession.interruptionNotification, - object: AVAudioSession.sharedInstance()) - } else { - NotificationCenter.default.removeObserver(self, - name: AVAudioSession.interruptionNotification, - object: AVAudioSession.sharedInstance()) - } - } - - func updateAVAudioSessionMode() { - do { - THEOplayer.automaticallyManageAudioSession = (self.backgroundAudioConfig.audioSessionMode == .moviePlayback) - try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: self.backgroundAudioConfig.audioSessionMode) - if self.backgroundAudioConfig.audioSessionMode != .moviePlayback { - print("[NATIVE] AVAudioSession mode updated to \(self.backgroundAudioConfig.audioSessionMode.rawValue)") - } - } catch { - print("[NATIVE] Unable to update AVAudioSession mode to \(self.backgroundAudioConfig.audioSessionMode.rawValue): ", error) - } + player.backgroundPlaybackDelegate = self.backgroundAudioManager } - @objc func handleInterruption(notification: Notification) { - guard let userInfo = notification.userInfo, - let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, - let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { + func destroyBackgroundAudio() { + guard let player = self.player else { return } - - // Switch over the interruption type. - switch type { - case .began: - // An interruption began. Update the UI as necessary. - if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] An interruption began")} - case .ended: - // An interruption ended. Resume playback, if appropriate. - if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] An interruption ended")} - guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return } - let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue) - if options.contains(.shouldResume) { - // An interruption ended. Resume playback. - if let player = self.player { - if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] Ended interruption should resume playback => play()")} - player.play() - } - } else { - // An interruption ended. Don't resume playback. - if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] Ended interruption should not resume playback.")} - } - default: () - } + player.backgroundPlaybackDelegate = DefaultBackgroundPlaybackDelegate() } } struct DefaultBackgroundPlaybackDelegate: BackgroundPlaybackDelegate { - func shouldContinueAudioPlaybackInBackground() -> Bool { false } + func shouldContinueAudioPlaybackInBackground() -> Bool { + return false + } } diff --git a/ios/pip/THEOplayerRCTPipManager.swift b/ios/pip/THEOplayerRCTPipManager.swift new file mode 100644 index 000000000..6203470e3 --- /dev/null +++ b/ios/pip/THEOplayerRCTPipManager.swift @@ -0,0 +1,34 @@ +// TTHEOplayerRCTPipManager.swift + +import Foundation +import AVKit +import THEOplayerSDK + +class THEOplayerRCTPipManager: NSObject, AVPictureInPictureControllerDelegate { + + // MARK: Members + private weak var view: THEOplayerRCTView? + + // MARK: - player setup / breakdown + func setView(view: THEOplayerRCTView?) { + self.view = view + } + + func destroy() {} + + // MARK: - AVPictureInPictureControllerDelegate + @available(tvOS 14.0, *) + public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + if let view = self.view { + view.presentationModeManager.presentationModeContext.pipContext = .PIP_CLOSED + view.pipControlsManager.willStartPip() + } + } + + @available(tvOS 14.0, *) + public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) { + if let view = self.view { + view.presentationModeManager.presentationModeContext.pipContext = .PIP_RESTORED + } + } +} diff --git a/ios/pip/THEOplayerRCTView+PipConfig.swift b/ios/pip/THEOplayerRCTView+PipConfig.swift index 459a29c3c..2b3009d86 100644 --- a/ios/pip/THEOplayerRCTView+PipConfig.swift +++ b/ios/pip/THEOplayerRCTView+PipConfig.swift @@ -8,7 +8,7 @@ struct PipConfig { var canStartPictureInPictureAutomaticallyFromInline: Bool = false } -extension THEOplayerRCTView: AVPictureInPictureControllerDelegate { +extension THEOplayerRCTView { func playerPipConfiguration() -> PiPConfiguration { let builder = PiPConfigurationBuilder() @@ -25,20 +25,9 @@ extension THEOplayerRCTView: AVPictureInPictureControllerDelegate { if let player = self.player, var pipController = player.pip { if #available(iOS 14.0, tvOS 14.0, *) { - pipController.nativePictureInPictureDelegate = self + pipController.nativePictureInPictureDelegate = self.pipManager } } } - - // MARK: - AVPictureInPictureControllerDelegate - @available(tvOS 14.0, *) - public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { - self.presentationModeManager.presentationModeContext.pipContext = .PIP_CLOSED - self.pipControlsManager.willStartPip() - } - - @available(tvOS 14.0, *) - public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) { - self.presentationModeManager.presentationModeContext.pipContext = .PIP_RESTORED - } } + diff --git a/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift b/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift index 4b9471ac5..22f425c9b 100644 --- a/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift +++ b/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift @@ -11,9 +11,11 @@ public class THEOplayerRCTPresentationModeManager { var presentationModeContext = THEOplayerRCTPresentationModeContext() private var presentationMode: THEOplayerSDK.PresentationMode = .inline private var rnInlineMode: THEOplayerSDK.PresentationMode = .inline // while native player is inline, RN player can be inline or fullsceen + private var movingChildVCs: [UIViewController] = [] // list of playerView's child VCs that need to be reparented while moving the playerView + - private var containerView: UIView? // view containing the playerView and it's siblings (e.g. UI) - private var inlineParentView: UIView? // target view for inline representation + private weak var containerView: UIView? // view containing the playerView and it's siblings (e.g. UI) + private weak var inlineParentView: UIView? // target view for inline representation // MARK: Events var onNativePresentationModeChange: RCTDirectEventBlock? @@ -25,6 +27,7 @@ public class THEOplayerRCTPresentationModeManager { func destroy() { // dettach listeners self.dettachListeners() + self.clearMovingVCs() } // MARK: - player setup / breakdown @@ -37,22 +40,35 @@ public class THEOplayerRCTPresentationModeManager { } // MARK: - logic - - private func moveView(_ movingView: UIView, to targetView: UIView) { - guard let theoPlayerViewController = (self.view as? THEOplayerRCTView)?.theoPlayerViewController else { return } + private func storeMovingVCs(for view: UIView) { + if let viewController = view.findViewController() { + viewController.children.forEach { childVC in + self.movingChildVCs.append(childVC) + } + } + } - // detach the viewController from its parent - theoPlayerViewController.removeFromParent() + private func clearMovingVCs() { + self.movingChildVCs = [] + } + + private func moveView(_ movingView: UIView, to targetView: UIView) { + // detach the moving viewControllers from their parent + self.movingChildVCs.forEach { movedVC in + movedVC.removeFromParent() + } // move the actual view movingView.removeFromSuperview() targetView.addSubview(movingView) targetView.bringSubviewToFront(movingView) - // attach the viewController to its new parent + // attach the moving viewControllers to their new parent if let targetViewController = targetView.findViewController() { - targetViewController.addChild(theoPlayerViewController) - theoPlayerViewController.didMove(toParent: targetViewController) + self.movingChildVCs.forEach { movedVC in + targetViewController.addChild(movedVC) + movedVC.didMove(toParent: targetViewController) + } } } @@ -63,6 +79,7 @@ public class THEOplayerRCTPresentationModeManager { // move the player if let containerView = self.containerView, let fullscreenParentView = self.view?.findParentViewOfType(RCTRootContentView.self) { + self.storeMovingVCs(for: containerView) self.moveView(containerView, to: fullscreenParentView) // start hiding home indicator @@ -79,6 +96,7 @@ public class THEOplayerRCTPresentationModeManager { if let containerView = self.containerView, let inlineParentView = self.inlineParentView { self.moveView(containerView, to: inlineParentView) + self.clearMovingVCs() } self.rnInlineMode = .inline } diff --git a/src/api/player/THEOplayer.ts b/src/api/player/THEOplayer.ts index 4811c1313..eab3f017f 100644 --- a/src/api/player/THEOplayer.ts +++ b/src/api/player/THEOplayer.ts @@ -72,11 +72,6 @@ export interface THEOplayer extends EventDispatcher { */ pause(): void; - /** - * destroy the player. - */ - destroy(): void; - /** * Whether the player is paused. */ diff --git a/src/internal/THEOplayerView.tsx b/src/internal/THEOplayerView.tsx index 1dfaf696d..2f0165c2e 100644 --- a/src/internal/THEOplayerView.tsx +++ b/src/internal/THEOplayerView.tsx @@ -144,7 +144,7 @@ export class THEOplayerView extends PureComponent im } } - destroy(): void { - if (Platform.OS === 'ios') { - NativePlayerModule.destroyPlayer(this._view.nativeHandle); - } - } - public get version(): PlayerVersion { return this._playerVersion; }