com.azerion.bluestack 6.0.0-beta.4 → 6.0.0-beta.5
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/CHANGELOG.md +22 -25
- package/Editor/BlueStackDependenciesSource.xml +1 -1
- package/Plugins/Android/bluestack-unity-android-bridge-600.aar +0 -0
- package/Plugins/iOS/BSUBanner.swift +78 -127
- package/Plugins/iOS/BSUInterstitialAd.swift +6 -6
- package/Plugins/iOS/BSUMainThreadDispatcher.swift +25 -0
- package/Plugins/iOS/BSUMainThreadDispatcher.swift.meta +37 -0
- package/Plugins/iOS/BSUNativeAd.swift +14 -6
- package/Plugins/iOS/BSURewardedAd.swift +7 -7
- package/README.md +11 -7
- package/Runtime/API/Banner/AdPosition.cs +2 -7
- package/Runtime/Common/BlueStackSdkInfo.cs +1 -1
- package/Runtime/Platforms/Android/NativeAdClient.cs +28 -10
- package/Runtime/Platforms/Unity/BannerAdClient.cs +6 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [6.0.0-beta.4] - 2026-06-
|
|
3
|
+
## [6.0.0-beta.4] - 2026-06-15
|
|
4
4
|
|
|
5
5
|
### Native SDK upgrades
|
|
6
6
|
|
|
@@ -19,50 +19,50 @@
|
|
|
19
19
|
`BlueStackAds.IsDebugModeEnabled` to read the cached state.
|
|
20
20
|
- `BlueStackAds.IsInitialized` property to check native-side readiness.
|
|
21
21
|
- `InterstitialAd.IsReady` and `RewardedAd.IsReady` properties.
|
|
22
|
-
- `BannerAd` anchor-based positioning
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- `BannerAd` custom screen-space
|
|
22
|
+
- A new `BannerAd` constructor for anchor-based positioning,
|
|
23
|
+
`BannerAd(string placementId, AdSize adSize, Transform anchor, Camera camera = null)`. The banner
|
|
24
|
+
follows the anchor's screen position automatically via the `AdPlacementHandler` component.
|
|
25
|
+
The anchor's screen-space position is treated as the banner's **top-left corner** (matching
|
|
26
|
+
native iOS / Android frame placement).
|
|
27
|
+
- A new `BannerAd` constructor for custom screen-space positioning,
|
|
28
|
+
`BannerAd(string placementId, AdSize adSize, Vector2 position)`.
|
|
28
29
|
The `position` is the banner's **top-left corner** in Unity pixels (bottom-left origin).
|
|
29
|
-
- Optional `useSafeArea` parameter on `BannerAd(string, AdSize, AdPosition, bool useSafeArea = true)`
|
|
30
|
+
- Optional `useSafeArea` parameter on `BannerAd(string placementId, AdSize adSize, AdPosition adPosition, bool useSafeArea = true)`
|
|
30
31
|
to opt out of safe-area insets for sticky Top/Bottom banners. Custom-positioned banners always
|
|
31
32
|
ignore the safe area.
|
|
32
|
-
- Banner masking
|
|
33
|
-
to a UI region. The mask follows the `RectTransform` as it animates or resizes.
|
|
33
|
+
- Banner masking, `BannerAd.SetMask(RectTransform, Camera)` / `BannerAd.RemoveMask()` clip the banner
|
|
34
|
+
to a UI region. The mask follows the `RectTransform` as it animates or resizes via the `AdMaskHandler` component.
|
|
34
35
|
- `BannerAd.OnAdResized` event delivers a `PreferredBannerSize` (`Width`, `Height`) whenever the SDK
|
|
35
36
|
reports a post-load size change.
|
|
36
37
|
- `BannerAd.OnAdLoaded` payload changed to `PreferredBannerSize` carrying the SDK-preferred dimensions
|
|
37
38
|
(was `EventArgs`).
|
|
38
39
|
- `RewardedAd.OnAdRewardEarned` (renamed from `OnRewardEarned`) delivers a `RewardedItem`.
|
|
39
|
-
- `UpdateInterval` and `ForceUpdate` API added to `AdMaskHandler` and `AdPlacementHandler`
|
|
40
40
|
|
|
41
41
|
### Changed
|
|
42
42
|
|
|
43
|
-
- **Breaking
|
|
43
|
+
- **Breaking**: `BlueStackAds.Initialize` signature simplified:
|
|
44
44
|
- Before: `Initialize(string appId, Settings settings, Action<SDKInitializationStatus> sdkCallback, Action<IInitializationStatusClient> adaptersCallback)`
|
|
45
45
|
- After: `Initialize(string appId, Action<InitializationStatus> initializationCompleteAction = null)`
|
|
46
|
-
- The `Settings
|
|
46
|
+
- The `Settings` based debug toggle is replaced by `BlueStackAds.SetDebugMode(bool)`.
|
|
47
47
|
- The separate success/fail `SDKInitializationStatus` callback is removed. The single completion
|
|
48
48
|
callback now delivers `InitializationStatus.AdapterStatusMap` (per-adapter `AdapterStatus`).
|
|
49
49
|
- The initialization callback is now consistently dispatched to the Unity main thread on both
|
|
50
50
|
iOS and Android.
|
|
51
|
-
- **Breaking
|
|
52
|
-
- **Breaking
|
|
51
|
+
- **Breaking**: `RewardedVideoAd` class renamed to `RewardedAd`.
|
|
52
|
+
- **Breaking**: Banner event names renamed:
|
|
53
53
|
- `OnBannerDidLoad` → `OnAdLoaded` (payload changed to `PreferredBannerSize`)
|
|
54
54
|
- `OnBannerDidFailed` → `OnAdFailedToLoad`
|
|
55
55
|
- `OnBannerDisplay` → `OnAdDisplayed`
|
|
56
56
|
- `OnBannerHide` → `OnAdHidden`
|
|
57
57
|
- `OnBannerDidRefresh` → `OnAdRefreshed`
|
|
58
58
|
- `OnBannerDidFailToRefresh` → `OnAdFailedToRefresh`
|
|
59
|
-
- **Breaking
|
|
59
|
+
- **Breaking**: Interstitial event names renamed:
|
|
60
60
|
- `OnInterstitialDidLoaded` → `OnAdLoaded`
|
|
61
61
|
- `OnInterstitialDidFail` → `OnAdFailedToLoad`
|
|
62
62
|
- `OnInterstitialClicked` → `OnAdClicked`
|
|
63
63
|
- `OnInterstitialDidShown` → `OnAdDisplayed`
|
|
64
64
|
- `OnInterstitialDisappear` → `OnAdDismissed`
|
|
65
|
-
- **Breaking
|
|
65
|
+
- **Breaking**: Rewarded event names renamed:
|
|
66
66
|
- `OnRewardedVideoAdLoaded` → `OnAdLoaded`
|
|
67
67
|
- `OnRewardedVideoAdError` → `OnAdFailedToLoad`
|
|
68
68
|
- `OnRewardedVideoAdAppeared` → `OnAdDisplayed`
|
|
@@ -70,30 +70,27 @@
|
|
|
70
70
|
- `OnRewardedVideoAdClosed` → `OnAdDismissed`
|
|
71
71
|
- `OnUserRewardEarned` → `OnAdRewardEarned`
|
|
72
72
|
- To display banner ads, `BannerAd.Show()` must be called after the ad has been loaded.
|
|
73
|
-
- **Breaking
|
|
73
|
+
- **Breaking**: `BannerAd` constructors now take `AdSize` as a mandatory second parameter, and
|
|
74
74
|
`BannerAd.Load` no longer takes one:
|
|
75
75
|
- Before: `new BannerAd(placementId, adPosition); bannerAd.Load(adSize);`
|
|
76
76
|
- After: `new BannerAd(placementId, adSize, adPosition); bannerAd.Load();`
|
|
77
77
|
- Applies to all three constructor shapes (`AdPosition`, `Vector2`, `Transform` anchor).
|
|
78
78
|
- `BannerAd.Load(AdSize)` and `BannerAd.Load(AdSize, Preference)` are replaced by parameterless
|
|
79
|
-
`Load()` and `Load(Preference)
|
|
80
|
-
- **Breaking
|
|
79
|
+
`Load()` and `Load(Preference)`, the ad size is now fixed for the lifetime of the `BannerAd`.
|
|
80
|
+
- **Breaking**: `Preference` class is replaced with `RequestOptions` and made **immutable**.
|
|
81
81
|
- Mutable `Set*` builder methods are replaced by a single optional-named-args constructor:
|
|
82
82
|
`new RequestOptions(age: 25, gender: Gender.Male, location: ..., consentFlag: 1,
|
|
83
83
|
language: "en", keyword: "...", contentUrl: "...")`.
|
|
84
84
|
- A single `RequestOptions` instance is safe to reuse across multiple `Load(...)` calls.
|
|
85
|
-
- **Breaking
|
|
85
|
+
- **Breaking**: `Location` made immutable:
|
|
86
86
|
- Constructor now `new Location(double latitude, double longitude, string provider = NONE_PROVIDER)`
|
|
87
87
|
instead of `new Location(provider)` + mutable `Latitude` / `Longitude` setters.
|
|
88
88
|
- `getProvider()` method replaced by a `Provider` get-only property.
|
|
89
89
|
|
|
90
90
|
### Removed
|
|
91
91
|
|
|
92
|
-
- **Breaking
|
|
92
|
+
- **Breaking**: `Settings` parameter removed from `BlueStackAds.Initialize`. Use `BlueStackAds.SetDebugMode(bool)` instead of
|
|
93
93
|
`new Settings(isDebugModeEnabled: ...)`.
|
|
94
|
-
- **Breaking** — `Preference.Destroy()` removed — the new immutable `RequestOptions` holds no
|
|
95
|
-
native resources on the Unity side; the native intermediate is constructed and released inside
|
|
96
|
-
each `Load(...)` call.
|
|
97
94
|
|
|
98
95
|
## [3.4.1] - 2026-02-27
|
|
99
96
|
|
|
Binary file
|
|
@@ -59,7 +59,7 @@ public class BSUBanner: NSObject, BannerViewDelegate {
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
private func createBannerAd() {
|
|
62
|
-
|
|
62
|
+
BSUMainThreadDispatcher.run {
|
|
63
63
|
self.destroy()
|
|
64
64
|
guard let bannerAdSize = self.bannerAdSize else { return }
|
|
65
65
|
self.bannerAdsFactory = BannerView(adSize: bannerAdSize.size)
|
|
@@ -69,55 +69,31 @@ public class BSUBanner: NSObject, BannerViewDelegate {
|
|
|
69
69
|
// Auto refresh is on by default in the SDK and can be controlled from the console
|
|
70
70
|
// self.bannerAdsFactory?.startAutoRefresh()
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
if Thread.isMainThread {
|
|
74
|
-
initializeBannerFactory()
|
|
75
|
-
} else {
|
|
76
|
-
DispatchQueue.main.async {
|
|
77
|
-
initializeBannerFactory()
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
72
|
}
|
|
81
73
|
|
|
82
74
|
@objc public func loadAd() {
|
|
83
|
-
|
|
84
|
-
self
|
|
85
|
-
} else {
|
|
86
|
-
DispatchQueue.main.async { [weak self] in
|
|
87
|
-
self?.loadBannerAd(requestOptions: nil)
|
|
88
|
-
}
|
|
75
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
76
|
+
self?.loadBannerAd(requestOptions: nil)
|
|
89
77
|
}
|
|
90
78
|
}
|
|
91
79
|
|
|
92
80
|
@objc public func loadAd(withRequestOptions requestOptionsRef: BSUTypeRequestOptionsRef) {
|
|
93
81
|
let bsuOptions = Unmanaged<BSURequestOptions>.fromOpaque(requestOptionsRef).takeUnretainedValue()
|
|
94
82
|
let sdkOptions = bsuOptions.toSDKRequestOptions()
|
|
95
|
-
|
|
96
|
-
self
|
|
97
|
-
} else {
|
|
98
|
-
DispatchQueue.main.async { [weak self] in
|
|
99
|
-
self?.loadBannerAd(requestOptions: sdkOptions)
|
|
100
|
-
}
|
|
83
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
84
|
+
self?.loadBannerAd(requestOptions: sdkOptions)
|
|
101
85
|
}
|
|
102
86
|
}
|
|
103
87
|
|
|
104
88
|
@objc public func show() {
|
|
105
|
-
|
|
106
|
-
showBannerAd()
|
|
107
|
-
} else {
|
|
108
|
-
DispatchQueue.main.async { [weak self] in
|
|
109
|
-
self?.showBannerAd()
|
|
110
|
-
}
|
|
89
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
90
|
+
self?.showBannerAd()
|
|
111
91
|
}
|
|
112
92
|
}
|
|
113
93
|
|
|
114
94
|
@objc public func hide() {
|
|
115
|
-
|
|
116
|
-
hideBannerAd()
|
|
117
|
-
} else {
|
|
118
|
-
DispatchQueue.main.async { [weak self] in
|
|
119
|
-
self?.hideBannerAd()
|
|
120
|
-
}
|
|
95
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
96
|
+
self?.hideBannerAd()
|
|
121
97
|
}
|
|
122
98
|
}
|
|
123
99
|
|
|
@@ -151,8 +127,11 @@ public class BSUBanner: NSObject, BannerViewDelegate {
|
|
|
151
127
|
// Switching from Auto Layout to frame-based positioning
|
|
152
128
|
self.switchPositioningMode()
|
|
153
129
|
} else {
|
|
154
|
-
// Already in custom mode, just update position (fast path)
|
|
155
|
-
|
|
130
|
+
// Already in custom mode, just update position (fast path).
|
|
131
|
+
// Guarded because this path is hit on every position update from Unity.
|
|
132
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
133
|
+
self?.updateCustomPosition()
|
|
134
|
+
}
|
|
156
135
|
}
|
|
157
136
|
}
|
|
158
137
|
|
|
@@ -202,7 +181,7 @@ public class BSUBanner: NSObject, BannerViewDelegate {
|
|
|
202
181
|
self.updateCustomPosition()
|
|
203
182
|
}
|
|
204
183
|
}
|
|
205
|
-
|
|
184
|
+
BSUMainThreadDispatcher.run(work)
|
|
206
185
|
}
|
|
207
186
|
|
|
208
187
|
/// Removes the `MaskView` and moves `bannerContainerView` back to `unityView`.
|
|
@@ -227,7 +206,7 @@ public class BSUBanner: NSObject, BannerViewDelegate {
|
|
|
227
206
|
self.updateCustomPosition()
|
|
228
207
|
}
|
|
229
208
|
}
|
|
230
|
-
|
|
209
|
+
BSUMainThreadDispatcher.run(work)
|
|
231
210
|
}
|
|
232
211
|
|
|
233
212
|
// MARK: - Private Methods
|
|
@@ -248,20 +227,16 @@ public class BSUBanner: NSObject, BannerViewDelegate {
|
|
|
248
227
|
self.updateCustomPosition()
|
|
249
228
|
}
|
|
250
229
|
}
|
|
251
|
-
|
|
230
|
+
BSUMainThreadDispatcher.run(work)
|
|
252
231
|
}
|
|
253
|
-
|
|
232
|
+
|
|
254
233
|
// bannerContainerView Positioning
|
|
255
234
|
// - Auto Layout: Used for .topOfScreen and .bottomOfScreen (set once, iOS manages)
|
|
256
235
|
// - Frame-based: Used for .custom positions (direct frame manipulation for frequent updates)
|
|
257
236
|
|
|
258
237
|
private func switchPositioningMode() {
|
|
259
|
-
|
|
260
|
-
self
|
|
261
|
-
} else {
|
|
262
|
-
DispatchQueue.main.async { [weak self] in
|
|
263
|
-
self?.switchPositioningModeInternal()
|
|
264
|
-
}
|
|
238
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
239
|
+
self?.switchPositioningModeInternal()
|
|
265
240
|
}
|
|
266
241
|
}
|
|
267
242
|
|
|
@@ -277,21 +252,13 @@ public class BSUBanner: NSObject, BannerViewDelegate {
|
|
|
277
252
|
}
|
|
278
253
|
|
|
279
254
|
private func positionBannerView() {
|
|
280
|
-
|
|
281
|
-
|
|
255
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
256
|
+
guard let self = self else { return }
|
|
257
|
+
if self.adPosition == .custom {
|
|
282
258
|
self.updateCustomPosition()
|
|
283
259
|
} else {
|
|
284
260
|
self.updateAutoLayoutPosition()
|
|
285
261
|
}
|
|
286
|
-
} else {
|
|
287
|
-
DispatchQueue.main.async { [weak self] in
|
|
288
|
-
guard let self = self else { return }
|
|
289
|
-
if self.adPosition == .custom {
|
|
290
|
-
self.updateCustomPosition()
|
|
291
|
-
} else {
|
|
292
|
-
self.updateAutoLayoutPosition()
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
262
|
}
|
|
296
263
|
}
|
|
297
264
|
|
|
@@ -510,46 +477,28 @@ public class BSUBanner: NSObject, BannerViewDelegate {
|
|
|
510
477
|
|
|
511
478
|
private func removeBannerView() {
|
|
512
479
|
guard let _ = self.bannerView else { return }
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
} else {
|
|
518
|
-
DispatchQueue.main.async { [weak self] in
|
|
519
|
-
guard let strongSelf = self else { return }
|
|
520
|
-
strongSelf.bannerView?.removeFromSuperview()
|
|
521
|
-
strongSelf.bannerView = nil
|
|
522
|
-
}
|
|
480
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
481
|
+
guard let strongSelf = self else { return }
|
|
482
|
+
strongSelf.bannerView?.removeFromSuperview()
|
|
483
|
+
strongSelf.bannerView = nil
|
|
523
484
|
}
|
|
524
485
|
}
|
|
525
486
|
|
|
526
487
|
private func removeBannerContainerView() {
|
|
527
488
|
guard let _ = self.bannerContainerView else { return }
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
} else {
|
|
533
|
-
DispatchQueue.main.async { [weak self] in
|
|
534
|
-
guard let strongSelf = self else { return }
|
|
535
|
-
strongSelf.bannerContainerView?.removeFromSuperview()
|
|
536
|
-
strongSelf.bannerContainerView = nil
|
|
537
|
-
}
|
|
489
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
490
|
+
guard let strongSelf = self else { return }
|
|
491
|
+
strongSelf.bannerContainerView?.removeFromSuperview()
|
|
492
|
+
strongSelf.bannerContainerView = nil
|
|
538
493
|
}
|
|
539
494
|
}
|
|
540
|
-
|
|
495
|
+
|
|
541
496
|
private func removeBannerMaskView() {
|
|
542
497
|
guard let _ = self.bannerMaskView else { return }
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
} else {
|
|
548
|
-
DispatchQueue.main.async { [weak self] in
|
|
549
|
-
guard let strongSelf = self else { return }
|
|
550
|
-
strongSelf.bannerMaskView?.removeFromSuperview()
|
|
551
|
-
strongSelf.bannerMaskView = nil
|
|
552
|
-
}
|
|
498
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
499
|
+
guard let strongSelf = self else { return }
|
|
500
|
+
strongSelf.bannerMaskView?.removeFromSuperview()
|
|
501
|
+
strongSelf.bannerMaskView = nil
|
|
553
502
|
}
|
|
554
503
|
}
|
|
555
504
|
// MARK: - Constraint References (only used for Auto Layout mode)
|
|
@@ -602,37 +551,35 @@ public class BSUBanner: NSObject, BannerViewDelegate {
|
|
|
602
551
|
// MARK: - BannerViewDelegate
|
|
603
552
|
|
|
604
553
|
public func onLoad(_ bannerView: BlueStackSDK.BannerView, _ preferredHeight: CGFloat) {
|
|
605
|
-
//
|
|
606
|
-
self
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
// Auto show on load
|
|
635
|
-
// showBannerAd()
|
|
554
|
+
// Delegate callback may arrive off the main thread; all work below touches UIKit.
|
|
555
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
556
|
+
guard let self = self else { return }
|
|
557
|
+
self.removeBannerView()
|
|
558
|
+
self.bannerView = bannerView
|
|
559
|
+
self.bannerContainerView?.addSubview(bannerView)
|
|
560
|
+
self.bannerView?.translatesAutoresizingMaskIntoConstraints = false
|
|
561
|
+
|
|
562
|
+
// Resolve the final container size:
|
|
563
|
+
// - height: always the SDK-reported preferredHeight
|
|
564
|
+
// - width: prefer bannerView.frame.width if non-zero, else originally requested width
|
|
565
|
+
let resolvedWidth = bannerView.frame.size.width
|
|
566
|
+
let target = CGSize(
|
|
567
|
+
width: resolvedWidth > 0 ? resolvedWidth : self.adFrame.width,
|
|
568
|
+
height: preferredHeight
|
|
569
|
+
)
|
|
570
|
+
if let containerView = self.bannerContainerView {
|
|
571
|
+
self.setupBannerViewConstraints(with: containerView)
|
|
572
|
+
containerView.layoutIfNeeded()
|
|
573
|
+
self.adjustContainerSize(to: target)
|
|
574
|
+
}
|
|
575
|
+
BSULogger.debug("Banner", message: "ad loaded \(Int(target.width))x\(Int(target.height))")
|
|
576
|
+
// Forward the final container size (preferredWidth, preferredHeight) to Unity.
|
|
577
|
+
self.onBannerDidLoadCallback?(
|
|
578
|
+
self.bannerClient,
|
|
579
|
+
Int32(target.width),
|
|
580
|
+
Int32(target.height)
|
|
581
|
+
)
|
|
582
|
+
}
|
|
636
583
|
}
|
|
637
584
|
|
|
638
585
|
public func onFailedToLoad(_ bannerView: BlueStackSDK.BannerView, _ error: any Error) {
|
|
@@ -666,15 +613,19 @@ public class BSUBanner: NSObject, BannerViewDelegate {
|
|
|
666
613
|
}
|
|
667
614
|
|
|
668
615
|
public func onResize(_ bannerView: BlueStackSDK.BannerView, _ size: CGSize) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
616
|
+
// Delegate callback may arrive off the main thread; adjustContainerSize touches UIKit.
|
|
617
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
618
|
+
guard let self = self else { return }
|
|
619
|
+
BSULogger.debug("Banner", message: "ad resized")
|
|
620
|
+
self.adjustContainerSize(to: size)
|
|
621
|
+
// Forward the final size (preferredWidth, preferredHeight) to Unity.
|
|
622
|
+
// adFrame is updated by adjustContainerSize and reflects the size actually applied.
|
|
623
|
+
self.onBannerResizeCallback?(
|
|
624
|
+
self.bannerClient,
|
|
625
|
+
Int32(self.adFrame.width),
|
|
626
|
+
Int32(self.adFrame.height)
|
|
627
|
+
)
|
|
628
|
+
}
|
|
678
629
|
}
|
|
679
630
|
|
|
680
631
|
/// Updates `adFrame` and Resizes `bannerContainerView` so it wraps `bannerView` exactly.
|
|
@@ -32,24 +32,24 @@ public class BSUInterstitialAd: NSObject {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
@objc public func loadAd() {
|
|
35
|
-
|
|
36
|
-
self
|
|
35
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
36
|
+
self?.interstitialAdFactory?.load()
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
@objc public func loadAd(withRequestOptions requestOptionsRef: BSUTypeRequestOptionsRef) {
|
|
41
41
|
let bsuOptions = Unmanaged<BSURequestOptions>.fromOpaque(requestOptionsRef).takeUnretainedValue()
|
|
42
42
|
let sdkOptions = bsuOptions.toSDKRequestOptions()
|
|
43
|
-
|
|
44
|
-
self
|
|
43
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
44
|
+
self?.interstitialAdFactory?.load(requestOptions: sdkOptions)
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
@objc public func show() {
|
|
49
49
|
if let interstitialAd = self.interstitialAdFactory {
|
|
50
50
|
if interstitialAd.isReady {
|
|
51
|
-
|
|
52
|
-
interstitialAd.show(fromRootViewController: self
|
|
51
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
52
|
+
interstitialAd.show(fromRootViewController: self?.interstitialAdFactory?.viewController)
|
|
53
53
|
}
|
|
54
54
|
} else {
|
|
55
55
|
BSULogger.warning("Interstitial", message: "cannot show: ad not ready")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//
|
|
2
|
+
// BSUMainThreadDispatcher.swift
|
|
3
|
+
// UnityFramework
|
|
4
|
+
//
|
|
5
|
+
// Encapsulates the "run on the main thread" guard used across the iOS bridge.
|
|
6
|
+
// WebView (WKWebView) and UIKit must only be touched on the main thread.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import Foundation
|
|
10
|
+
|
|
11
|
+
@objc(BSUMainThreadDispatcher)
|
|
12
|
+
public class BSUMainThreadDispatcher: NSObject {
|
|
13
|
+
/// Executes `work` on the main thread.
|
|
14
|
+
/// Runs synchronously when the caller is already on the main thread,
|
|
15
|
+
/// otherwise hops to the main queue async.
|
|
16
|
+
@objc public static func run(_ work: @escaping () -> Void) {
|
|
17
|
+
if Thread.isMainThread {
|
|
18
|
+
work()
|
|
19
|
+
} else {
|
|
20
|
+
DispatchQueue.main.async {
|
|
21
|
+
work()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
fileFormatVersion: 2
|
|
2
|
+
guid: 7bdea4bf7c6344c3f9bcb393fa36d210
|
|
3
|
+
PluginImporter:
|
|
4
|
+
externalObjects: {}
|
|
5
|
+
serializedVersion: 2
|
|
6
|
+
iconMap: {}
|
|
7
|
+
executionOrder: {}
|
|
8
|
+
defineConstraints: []
|
|
9
|
+
isPreloaded: 0
|
|
10
|
+
isOverridable: 1
|
|
11
|
+
isExplicitlyReferenced: 0
|
|
12
|
+
validateReferences: 1
|
|
13
|
+
platformData:
|
|
14
|
+
- first:
|
|
15
|
+
Any:
|
|
16
|
+
second:
|
|
17
|
+
enabled: 0
|
|
18
|
+
settings: {}
|
|
19
|
+
- first:
|
|
20
|
+
Editor: Editor
|
|
21
|
+
second:
|
|
22
|
+
enabled: 0
|
|
23
|
+
settings:
|
|
24
|
+
DefaultValueInitialized: true
|
|
25
|
+
- first:
|
|
26
|
+
iPhone: iOS
|
|
27
|
+
second:
|
|
28
|
+
enabled: 1
|
|
29
|
+
settings: {}
|
|
30
|
+
- first:
|
|
31
|
+
tvOS: tvOS
|
|
32
|
+
second:
|
|
33
|
+
enabled: 1
|
|
34
|
+
settings: {}
|
|
35
|
+
userData:
|
|
36
|
+
assetBundleName:
|
|
37
|
+
assetBundleVariant:
|
|
@@ -40,29 +40,37 @@ public class BSUNativeAd: NSObject, AdsAdapterNativeDelegate, AdClickDelegate, N
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
@objc public func loadAd() {
|
|
43
|
-
|
|
43
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
44
|
+
self?.nativeAdsFactory?.loadNative()
|
|
45
|
+
}
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
@objc public func loadAd(withRequestOptions requestOptionsRef: BSUTypeRequestOptionsRef) {
|
|
47
49
|
let bsuOptions = Unmanaged<BSURequestOptions>.fromOpaque(requestOptionsRef).takeUnretainedValue()
|
|
48
50
|
let sdkOptions = bsuOptions.toSDKRequestOptions()
|
|
49
|
-
|
|
51
|
+
|
|
50
52
|
// RequestOptions to Preference
|
|
51
53
|
var requestOptionsToPreferenceTransformer = RequestOptionsToPreferenceTransformer()
|
|
52
54
|
let preference = requestOptionsToPreferenceTransformer.transform(sdkOptions)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
|
|
56
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
57
|
+
self?.nativeAdsFactory?.loadNative(withPreferences: preference, withCover: false)
|
|
58
|
+
}
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
@objc public func recordImpression(_ args: [AnyHashable: Any]) {
|
|
58
|
-
|
|
62
|
+
// Drives the OMID/measurement session and WebView, so it must run on the main thread.
|
|
63
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
64
|
+
guard let self = self, let nativeAdsObject = self.nativeAdsObject else { return }
|
|
59
65
|
nativeAdsObject.setNativeAdImpressionListener(self)
|
|
60
66
|
nativeAdsObject.recordImpression(args)
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
@objc public func performClick(_ args: [AnyHashable: Any]) {
|
|
65
|
-
|
|
71
|
+
// Routes through the OMID session like recordImpression, so it must run on the main thread.
|
|
72
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
73
|
+
guard let nativeAdsObject = self?.nativeAdsObject else { return }
|
|
66
74
|
nativeAdsObject.performClick(args)
|
|
67
75
|
}
|
|
68
76
|
}
|
|
@@ -32,24 +32,24 @@ public class BSURewardedAd: NSObject {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
@objc public func loadAd() {
|
|
35
|
-
|
|
36
|
-
self
|
|
35
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
36
|
+
self?.rewardedAdFactory?.load()
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
@objc public func loadAd(withRequestOptions requestOptionsRef: BSUTypeRequestOptionsRef) {
|
|
41
41
|
let bsuOptions = Unmanaged<BSURequestOptions>.fromOpaque(requestOptionsRef).takeUnretainedValue()
|
|
42
42
|
let sdkOptions = bsuOptions.toSDKRequestOptions()
|
|
43
|
-
|
|
44
|
-
self
|
|
43
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
44
|
+
self?.rewardedAdFactory?.load(requestOptions: sdkOptions)
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
@objc public func show() {
|
|
49
49
|
if let rewardedAd = self.rewardedAdFactory {
|
|
50
50
|
if rewardedAd.isReady {
|
|
51
|
-
|
|
52
|
-
rewardedAd.show(fromRootViewController: self
|
|
51
|
+
BSUMainThreadDispatcher.run { [weak self] in
|
|
52
|
+
rewardedAd.show(fromRootViewController: self?.rewardedAdFactory?.viewController)
|
|
53
53
|
}
|
|
54
54
|
} else {
|
|
55
55
|
BSULogger.warning("Rewarded", message: "cannot show: ad not ready")
|
package/README.md
CHANGED
|
@@ -15,17 +15,21 @@ This SDK supports four types of Ads:
|
|
|
15
15
|
|
|
16
16
|
## Prerequisites
|
|
17
17
|
|
|
18
|
-
- Use Unity 2020.3 or higher
|
|
18
|
+
- Use Unity 2020.3 or higher (Unity 6 or higher recommended and tested)
|
|
19
19
|
- Android
|
|
20
|
-
- Minimum Android API level of 21 or higher
|
|
21
|
-
- Target Android API level
|
|
20
|
+
- Minimum Android API level of 21 or higher
|
|
21
|
+
- Target Android API level 34 or higher
|
|
22
|
+
- JDK 17 or higher
|
|
22
23
|
- iOS
|
|
23
|
-
- Xcode 15.3 or higher
|
|
24
|
-
- iOS
|
|
24
|
+
- Xcode 15.3 or higher
|
|
25
|
+
- iOS 13.0 or higher
|
|
25
26
|
- [CocoaPods](https://guides.cocoapods.org/using/getting-started.html)
|
|
26
27
|
|
|
27
|
-
:::
|
|
28
|
-
Android API level can be set in `Project Settings > Player > Android > Other Settings > Other Settings.`
|
|
28
|
+
:::note
|
|
29
|
+
- Android API level can be set in `Project Settings > Player > Android > Other Settings > Other Settings.`
|
|
30
|
+
- Set your AdMob App ID in `Azerion > BlueStack > Settings` when the Google Mobile Ads adapter is enabled
|
|
31
|
+
- Requires **External Dependency Manager for Unity (EDM4U)**, It resolves all native Android/iOS dependencies
|
|
32
|
+
- AndroidX enabled; enable the Custom Main Gradle Template and Custom Gradle Properties Template, then resolve via `External Dependency Manager > Android Resolver`
|
|
29
33
|
:::
|
|
30
34
|
|
|
31
35
|
## Import the package
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
namespace Azerion.BlueStack.API.Banner
|
|
2
2
|
{
|
|
3
3
|
/// <summary>
|
|
4
|
-
/// Anchoring mode for a banner ad
|
|
5
|
-
/// (with optional safe-area respect)
|
|
6
|
-
/// position supplied via <see cref="BannerAd.SetPosition(UnityEngine.Vector2)"/> or the
|
|
7
|
-
/// <c>Vector2</c> / anchor constructor.
|
|
4
|
+
/// Anchoring mode for a sticky banner ad: Top or Bottom snaps the banner to a screen edge
|
|
5
|
+
/// (with optional safe-area respect).
|
|
8
6
|
/// </summary>
|
|
9
7
|
public enum AdPosition
|
|
10
8
|
{
|
|
11
|
-
/// <summary>Banner is positioned at an explicit screen-space coordinate. Ignores the safe area.</summary>
|
|
12
|
-
Custom = -1,
|
|
13
|
-
|
|
14
9
|
/// <summary>Banner is anchored to the top of the screen.</summary>
|
|
15
10
|
Top = 0,
|
|
16
11
|
|
|
@@ -30,21 +30,30 @@ namespace Azerion.BlueStack.Platforms.Android
|
|
|
30
30
|
public void Create(string placementId)
|
|
31
31
|
{
|
|
32
32
|
_placementId = placementId;
|
|
33
|
-
|
|
33
|
+
AndroidJavaObjectFactory.Instance.RunOnUiThread(() =>
|
|
34
|
+
{
|
|
35
|
+
_unityNativeAd.Call("create", placementId);
|
|
36
|
+
});
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
public void Load()
|
|
37
40
|
{
|
|
38
41
|
BlueStackLogger.Debug("NativeAd", "Load requested for " + _placementId);
|
|
39
|
-
|
|
42
|
+
AndroidJavaObjectFactory.Instance.RunOnUiThread(() =>
|
|
43
|
+
{
|
|
44
|
+
_unityNativeAd.Call("load");
|
|
45
|
+
});
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
public void Load(RequestOptions options)
|
|
43
49
|
{
|
|
44
50
|
BlueStackLogger.Debug("NativeAd", "Load requested for " + _placementId);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
AndroidJavaObjectFactory.Instance.RunOnUiThread(() =>
|
|
52
|
+
{
|
|
53
|
+
using AndroidJavaObject unityOptions = RequestOptionsClient.CreateUnityRequestOptions(options);
|
|
54
|
+
using AndroidJavaObject sdkOptions = unityOptions.Call<AndroidJavaObject>("getRequestOptions");
|
|
55
|
+
_unityNativeAd.Call("load", sdkOptions);
|
|
56
|
+
});
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
public string GetTitle()
|
|
@@ -91,18 +100,28 @@ namespace Azerion.BlueStack.Platforms.Android
|
|
|
91
100
|
|
|
92
101
|
public void RecordImpression()
|
|
93
102
|
{
|
|
94
|
-
|
|
103
|
+
AndroidJavaObjectFactory.Instance.RunOnUiThread(() =>
|
|
104
|
+
{
|
|
105
|
+
_unityNativeAd.Call("recordImpression");
|
|
106
|
+
});
|
|
95
107
|
}
|
|
96
|
-
|
|
108
|
+
|
|
97
109
|
public void PerformClick()
|
|
98
110
|
{
|
|
99
|
-
|
|
111
|
+
AndroidJavaObjectFactory.Instance.RunOnUiThread(() =>
|
|
112
|
+
{
|
|
113
|
+
_unityNativeAd.Call("performClick");
|
|
114
|
+
});
|
|
100
115
|
}
|
|
101
116
|
|
|
102
117
|
public void Destroy()
|
|
103
118
|
{
|
|
104
119
|
BlueStackLogger.Debug("NativeAd", "Destroy requested");
|
|
105
|
-
|
|
120
|
+
AndroidJavaObjectFactory.Instance.RunOnUiThread(() =>
|
|
121
|
+
{
|
|
122
|
+
_unityNativeAd.Call("destroy");
|
|
123
|
+
_unityNativeAd.Dispose();
|
|
124
|
+
});
|
|
106
125
|
}
|
|
107
126
|
|
|
108
127
|
public void onNativeAdDidLoad()
|
|
@@ -130,7 +149,6 @@ namespace Azerion.BlueStack.Platforms.Android
|
|
|
130
149
|
public void onNativeAdClose()
|
|
131
150
|
{
|
|
132
151
|
OnNativeAdClosed?.Invoke(this, EventArgs.Empty);
|
|
133
|
-
_unityNativeAd.Dispose();
|
|
134
152
|
}
|
|
135
153
|
}
|
|
136
154
|
}
|
|
@@ -15,6 +15,7 @@ namespace Azerion.BlueStack.Platforms.UnityEditor
|
|
|
15
15
|
{
|
|
16
16
|
private string _placementId;
|
|
17
17
|
private AdPosition _adPosition;
|
|
18
|
+
private bool _isCustomPosition;
|
|
18
19
|
private AdSize _adSize;
|
|
19
20
|
private Vector2 _customPosition;
|
|
20
21
|
private Rect _maskBounds;
|
|
@@ -54,6 +55,7 @@ namespace Azerion.BlueStack.Platforms.UnityEditor
|
|
|
54
55
|
_placementId = placementId;
|
|
55
56
|
_adSize = adSize;
|
|
56
57
|
_adPosition = adPosition;
|
|
58
|
+
_isCustomPosition = false;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
public void CreateBannerAd(string placementId, AdSize adSize, Vector2 adPosition)
|
|
@@ -61,7 +63,7 @@ namespace Azerion.BlueStack.Platforms.UnityEditor
|
|
|
61
63
|
_placementId = placementId;
|
|
62
64
|
_adSize = adSize;
|
|
63
65
|
_customPosition = adPosition;
|
|
64
|
-
|
|
66
|
+
_isCustomPosition = true;
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
public void Load()
|
|
@@ -169,6 +171,7 @@ namespace Azerion.BlueStack.Platforms.UnityEditor
|
|
|
169
171
|
public void SetPosition(AdPosition adPosition)
|
|
170
172
|
{
|
|
171
173
|
_adPosition = adPosition;
|
|
174
|
+
_isCustomPosition = false;
|
|
172
175
|
if (dummyAd == null)
|
|
173
176
|
{
|
|
174
177
|
return;
|
|
@@ -179,7 +182,7 @@ namespace Azerion.BlueStack.Platforms.UnityEditor
|
|
|
179
182
|
public void SetPosition(Vector2 adPosition)
|
|
180
183
|
{
|
|
181
184
|
_customPosition = adPosition;
|
|
182
|
-
|
|
185
|
+
_isCustomPosition = true;
|
|
183
186
|
if (dummyAd == null)
|
|
184
187
|
{
|
|
185
188
|
return;
|
|
@@ -219,7 +222,7 @@ namespace Azerion.BlueStack.Platforms.UnityEditor
|
|
|
219
222
|
private void PositionBannerContainer()
|
|
220
223
|
{
|
|
221
224
|
if (_bannerContainer == null) return;
|
|
222
|
-
if (
|
|
225
|
+
if (_isCustomPosition)
|
|
223
226
|
{
|
|
224
227
|
AnchorBannerContainerToScreenPoint(_customPosition);
|
|
225
228
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "com.azerion.bluestack",
|
|
3
|
-
"version": "6.0.0-beta.
|
|
3
|
+
"version": "6.0.0-beta.5",
|
|
4
4
|
"displayName": "BlueStack",
|
|
5
5
|
"description": "BlueStack SDK has been designed to give developers options for showing Ads from different ad networks.",
|
|
6
6
|
"unity": "2020.3",
|