     public func vungleAdPlayabilityUpdate(_ isAdPlayable: Bool, adId: String?, error: Error?) {
         _logger.debug("\(#function): playable = \(isAdPlayable) id = \(adId ?? "") reason \(error?.localizedDescription ?? "")")
         if let adId = adId {
-            if _loadedAdIds.contains(adId) {
+            if isAdPlayable {
+                _loadedAdIds.insert(adId)
+            }
+            if _loadingAdIds.contains(adId) {
                 assert(isAdPlayable)
+                _loadingAdIds.remove(adId)
                 _bridge.callCpp(kOnLoaded, EEJsonUtils.convertDictionary(toString: [
                     "ad_id": adId
                 ]))

    func loadRewardedAd(_ adId: String) {
        Thread.runOnMainThread {
            do {
                try self._sdk.loadPlacement(withID: adId)
            } catch {
                self._bridge.callCpp(kOnFailedToLoad, EEJsonUtils.convertDictionary(toString: [
                    "ad_id": adId,
                    "message": error.localizedDescription


    public func vungleDidCloseAd(forPlacementID adId: String) {
        _logger.debug("\(#function): \(adId)")
        _loadedAdIds.remove(adId)
        _bridge.callCpp(kOnClosed, EEJsonUtils.convertDictionary(toString: [
            "ad_id": adId,
            "rewarded": _rewarded
