Skip to content

Commit

Permalink
[interactive_media_ads] Fixes bug where Android would show the last f…
Browse files Browse the repository at this point in the history
…rame of the previous Ad before playing the current one (#7835)

It seems that [VideoView](https://developer.android.com/reference/android/widget/VideoView) continues to show the last frame of the previous video until the new video starts playing. This causes the `VideoView` to momentarily show the last frame while the new video was being prepared. It looks like neither `videoView.setUri(null)` or `videoView.stopPlayback` would reset the state. 

The simplest solution I could find would be to create a new `VideoView` whenever an Ad stops playing.

Related links:

google/ExoPlayer#1942
https://stackoverflow.com/questions/25660994/clear-video-frame-from-surfaceview-on-video-complete
  • Loading branch information
bparrishMines authored Oct 10, 2024
1 parent f1a3da2 commit 791c6e3
Show file tree
Hide file tree
Showing 13 changed files with 137 additions and 10 deletions.
5 changes: 5 additions & 0 deletions packages/interactive_media_ads/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.2.2+10

* Fixes bug where Android would show the last frame of the previous Ad before playing the current
one.

## 0.2.2+9

* Adds internal wrapper for Android native `CompanionAd`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class AdsRequestProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) :
*
* This must match the version in pubspec.yaml.
*/
const val pluginVersion = "0.2.2+9"
const val pluginVersion = "0.2.2+10"
}

override fun setAdTagUrl(pigeon_instance: AdsRequest, adTagUrl: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2625,8 +2625,15 @@ abstract class PigeonApiFrameLayout(
abstract class PigeonApiViewGroup(
open val pigeonRegistrar: InteractiveMediaAdsLibraryPigeonProxyApiRegistrar
) {
/** Adds a child view. */
abstract fun addView(pigeon_instance: android.view.ViewGroup, view: android.view.View)

/**
* Called by a ViewGroup subclass to remove child views from itself, when it must first know its
* size on screen before it can calculate how many child views it will render.
*/
abstract fun removeView(pigeon_instance: android.view.ViewGroup, view: android.view.View)

companion object {
@Suppress("LocalVariableName")
fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiViewGroup?) {
Expand Down Expand Up @@ -2655,6 +2662,30 @@ abstract class PigeonApiViewGroup(
channel.setMessageHandler(null)
}
}
run {
val channel =
BasicMessageChannel<Any?>(
binaryMessenger,
"dev.flutter.pigeon.interactive_media_ads.ViewGroup.removeView",
codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val pigeon_instanceArg = args[0] as android.view.ViewGroup
val viewArg = args[1] as android.view.View
val wrapped: List<Any?> =
try {
api.removeView(pigeon_instanceArg, viewArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ class ViewGroupProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) :
override fun addView(pigeon_instance: ViewGroup, view: View) {
pigeon_instance.addView(view)
}

override fun removeView(pigeon_instance: ViewGroup, view: View) {
pigeon_instance.removeView(view)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,15 @@ class ViewGroupProxyApiTest {

verify(instance).addView(mockView)
}

@Test
fun removeView() {
val api = TestProxyApiRegistrar().getPigeonApiViewGroup()

val instance = mock<ViewGroup>()
val mockView = mock<View>()
api.removeView(instance, mockView)

verify(instance).removeView(mockView)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class AdsRequestProxyAPIDelegate: PigeonApiDelegateIMAAdsRequest {
/// The current version of the `interactive_media_ads` plugin.
///
/// This must match the version in pubspec.yaml.
static let pluginVersion = "0.2.2+9"
static let pluginVersion = "0.2.2+10"

func pigeonDefaultConstructor(
pigeonApi: PigeonApiIMAAdsRequest, adTagUrl: String, adDisplayContainer: IMAAdDisplayContainer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ base class AndroidAdDisplayContainer extends PlatformAdDisplayContainer {
_androidParams._imaProxy.newFrameLayout();

// Handles loading and displaying an ad.
late final ima.VideoView _videoView;
late ima.VideoView _videoView;

// After an ad is loaded in the `VideoView`, this is used to control
// playback.
Expand Down Expand Up @@ -285,7 +285,13 @@ base class AndroidAdDisplayContainer extends PlatformAdDisplayContainer {
if (container != null) {
// Clear and reset all state.
container._stopAdProgressTracking();
container._videoView.setVideoUri(null);

container._frameLayout.removeView(container._videoView);
container._videoView = _setUpVideoView(
WeakReference<AndroidAdDisplayContainer>(container),
);
container._frameLayout.addView(container._videoView);

container._clearMediaPlayer();
container._loadedAdMediaInfo = null;
container._adDuration = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2980,6 +2980,7 @@ class ViewGroup extends View {
}
}

/// Adds a child view.
Future<void> addView(View view) async {
final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec =
_pigeonVar_codecViewGroup;
Expand Down Expand Up @@ -3007,6 +3008,36 @@ class ViewGroup extends View {
}
}

/// Called by a ViewGroup subclass to remove child views from itself, when it
/// must first know its size on screen before it can calculate how many child
/// views it will render.
Future<void> removeView(View view) async {
final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec =
_pigeonVar_codecViewGroup;
final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger;
const String pigeonVar_channelName =
'dev.flutter.pigeon.interactive_media_ads.ViewGroup.removeView';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(<Object?>[this, view]) as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}

@override
ViewGroup pigeon_copy() {
return ViewGroup.pigeon_detached(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,13 @@ abstract class FrameLayout extends ViewGroup {
),
)
abstract class ViewGroup extends View {
/// Adds a child view.
void addView(View view);

/// Called by a ViewGroup subclass to remove child views from itself, when it
/// must first know its size on screen before it can calculate how many child
/// views it will render.
void removeView(View view);
}

/// Displays a video file.
Expand Down
2 changes: 1 addition & 1 deletion packages/interactive_media_ads/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: interactive_media_ads
description: A Flutter plugin for using the Interactive Media Ads SDKs on Android and iOS.
repository: https://github.com/flutter/packages/tree/main/packages/interactive_media_ads
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+interactive_media_ads%22
version: 0.2.2+9 # This must match the version in
version: 0.2.2+10 # This must match the version in
# `android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt` and
# `ios/interactive_media_ads/Sources/interactive_media_ads/AdsRequestProxyAPIDelegate.swift`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -624,21 +624,33 @@ void main() {
verify(mockVideoView.setVideoUri(videoUrl));
});

test('stop ad sets video uri to null', () async {
test('stop ad creates and sets a new VideoView', () async {
late final void Function(
ima.VideoAdPlayer,
ima.AdMediaInfo,
) stopAdCallback;

final MockVideoView mockVideoView = MockVideoView();
final MockFrameLayout mockFrameLayout = MockFrameLayout();
late final MockVideoView mockVideoView = MockVideoView();
late final MockVideoView mockVideoView2 = MockVideoView();
int newViewVideoCallCount = 0;
final InteractiveMediaAdsProxy imaProxy = InteractiveMediaAdsProxy(
newFrameLayout: () => MockFrameLayout(),
newFrameLayout: () => mockFrameLayout,
newVideoView: ({
dynamic onError,
dynamic onPrepared,
dynamic onCompletion,
}) {
return mockVideoView;
switch (newViewVideoCallCount) {
case 0:
newViewVideoCallCount++;
return mockVideoView;
case 1:
newViewVideoCallCount++;
return mockVideoView2;
default:
fail('newVideoView was called too many times');
}
},
createAdDisplayContainerImaSdkFactory: (_, __) async {
return MockAdDisplayContainer();
Expand Down Expand Up @@ -666,7 +678,8 @@ void main() {

stopAdCallback(MockVideoAdPlayer(), MockAdMediaInfo());

verify(mockVideoView.setVideoUri(null));
verify(mockFrameLayout.removeView(mockVideoView));
verify(mockFrameLayout.addView(mockVideoView2));
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,16 @@ class MockFrameLayout extends _i1.Mock implements _i2.FrameLayout {
returnValue: _i6.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(),
) as _i6.Future<void>);

@override
_i6.Future<void> removeView(_i2.View? view) => (super.noSuchMethod(
Invocation.method(
#removeView,
[view],
),
returnValue: _i6.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(),
) as _i6.Future<void>);
}

/// A class which mocks [MediaPlayer].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,16 @@ class MockFrameLayout extends _i1.Mock implements _i2.FrameLayout {
returnValue: _i6.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(),
) as _i6.Future<void>);

@override
_i6.Future<void> removeView(_i2.View? view) => (super.noSuchMethod(
Invocation.method(
#removeView,
[view],
),
returnValue: _i6.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(),
) as _i6.Future<void>);
}

/// A class which mocks [ImaSdkFactory].
Expand Down

0 comments on commit 791c6e3

Please sign in to comment.