Skip to content

[interactive_media_ads]: Live only play preroll when pass Vmap link contains preroll , midroll and postroll #173766

@abdalazeezalyosfi

Description

@abdalazeezalyosfi

What package does this bug report belong to?

interactive_media_ads

What target platforms are you seeing this bug on?

Android

Have you already upgraded your packages?

Yes

Dependency versions

pubspec.lock
[Paste file content here]

Steps to reproduce

play link live rather than video
set vmap link in ad url
the ads will only play preroll not all ads
just need to use link for streaming live (i cant provide our link becasue link will expired after 2 min )

Expected results

all ads listed in files should be played

Actual results

[log] Firebase Crashlytics Exception
[log] Null check operator used on a null value
[log] #0 AndroidAdDisplayContainer._startAdProgressTracking. (package:interactive_media_ads/src/android/android_ad_display_container.dart:189:34)

when set url its only play first ads

Code sample

Code sample
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'package:interactive_media_ads/interactive_media_ads.dart';
import 'package:video_player/video_player.dart';

/// Entry point for integration tests that require espresso.
@pragma('vm:entry-point')
void integrationTestMain() {
  enableFlutterDriverExtension();
  main();
}

void main() {
  runApp(const MaterialApp(home: AdExampleWidget()));
}

/// Example widget displaying an Ad before a video.
class AdExampleWidget extends StatefulWidget {
  /// Constructs an [AdExampleWidget].
  const AdExampleWidget({super.key});

  @override
  State<AdExampleWidget> createState() => _AdExampleWidgetState();
}

class _AdExampleWidgetState extends State<AdExampleWidget>
    with WidgetsBindingObserver {
  // IMA sample tag for a pre-, mid-, and post-roll, single inline video ad. See more IMA sample
  // tags at https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/tags
  // static const String _adTagUrl =
  //     'https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpost&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator=';
  static const String _adTagUrl = true
      ? (true
          ? "https://feeds.kwikmotion.com/v1/VMAP-VAST.xml"
          : "https://pubads.g.doubleclick.net/gampad/ads?iu=/18294456/AlSumaria_App_VOD_Preroll&description_url=http%3A%2F%2Fwww.alsumaria.tv%2F&tfcd=0&npa=0&sz=640x480&gdfp_req=1&unviewed_position_start=1&output=vmap&env=vp&impl=s&correlator=")
      : 'https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpost&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator=';
  // The AdsLoader instance exposes the request ads method.
  late final AdsLoader _adsLoader;

  // AdsManager exposes methods to control ad playback and listen to ad events.
  AdsManager? _adsManager;

  // Last state received in `didChangeAppLifecycleState`.
  AppLifecycleState _lastLifecycleState = AppLifecycleState.resumed;

  // Whether the widget should be displaying the content video. The content
  // player is hidden while Ads are playing.
  bool _shouldShowContentVideo = false;

  // Controls the content video player.
  late final VideoPlayerController _contentVideoController;

  // Periodically updates the SDK of the current playback progress of the
  // content video.
  Timer? _contentProgressTimer;

  // Provides the SDK with the current playback progress of the content video.
  // This is required to support mid-roll ads.
  final ContentProgressProvider _contentProgressProvider =
      ContentProgressProvider();

  late final CompanionAdSlot companionAd = CompanionAdSlot(
    size: CompanionAdSlotSize.fixed(width: 300, height: 250),
    onClicked: () => debugPrint('Companion Ad Clicked'),
  );

  late final AdDisplayContainer _adDisplayContainer = AdDisplayContainer(
    companionSlots: <CompanionAdSlot>[companionAd],
    onContainerAdded: (AdDisplayContainer container) {
      _adsLoader = AdsLoader(
        container: container,
        onAdsLoaded: (OnAdsLoadedData data) {
          final AdsManager manager = data.manager;
          _adsManager = data.manager;

          manager.setAdsManagerDelegate(AdsManagerDelegate(
            onAdEvent: (AdEvent event) {
              debugPrint('OnAdEvent: ${event.type} => ${event.adData}');
              switch (event.type) {
                case AdEventType.loaded:
                  manager.start();
                case AdEventType.contentPauseRequested:
                  _pauseContent();
                case AdEventType.contentResumeRequested:
                  _resumeContent();
                case AdEventType.allAdsCompleted:
                  manager.destroy();
                  _adsManager = null;
                case AdEventType.clicked:
                case AdEventType.complete:
                case _:
              }
            },
            onAdErrorEvent: (AdErrorEvent event) {
              debugPrint('AdErrorEvent: ${event.error.message}');
              _resumeContent();
            },
          ));

          manager.init(
              settings: AdsRenderingSettings(
                  loadVideoTimeout: Duration(minutes: 1),
                  mimeTypes: ["video/mp4"],
                  enablePreloading: true));
        },
        onAdsLoadError: (AdsLoadErrorData data) {
          debugPrint('OnAdsLoadError: ${data.error.message}');
          _resumeContent();
        },
      );

      // Ads can't be requested until the `AdDisplayContainer` has been added to
      // the native View hierarchy.
      _requestAds(container);
    },
  );

  @override
  void initState() {
    super.initState();
    // Adds this instance as an observer for `AppLifecycleState` changes.
    WidgetsBinding.instance.addObserver(this);

    _contentVideoController = VideoPlayerController.networkUrl(
      Uri.parse(
        'https://storage.googleapis.com/gvabox/media/samples/stock.mp4',
      ),
    )
      ..addListener(() {
        if (_contentVideoController.value.isCompleted) {
          _adsLoader.contentComplete();
        }
        setState(() {});
      })
      ..initialize().then((_) {
        // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
        setState(() {});
      });
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        if (!_shouldShowContentVideo) {
          _adsManager?.resume();
        }
      case AppLifecycleState.inactive:
        // Pausing the Ad video player on Android can only be done in this state
        // because it corresponds to `Activity.onPause`. This state is also
        // triggered before resume, so this will only pause the Ad if the app is
        // in the process of being sent to the background.
        if (!_shouldShowContentVideo &&
            _lastLifecycleState == AppLifecycleState.resumed) {
          _adsManager?.pause();
        }
      case AppLifecycleState.hidden:
      case AppLifecycleState.paused:
      case AppLifecycleState.detached:
    }
    _lastLifecycleState = state;
  }

  Future<void> _requestAds(AdDisplayContainer container) {
    return _adsLoader.requestAds(AdsRequest(
      adWillAutoPlay: true,
      adTagUrl: _adTagUrl,
      contentProgressProvider: _contentProgressProvider,
    ));
  }

  Future<void> _resumeContent() async {
    setState(() {
      _shouldShowContentVideo = true;
    });

    if (_adsManager != null) {
      _contentProgressTimer = Timer.periodic(
        const Duration(milliseconds: 200),
        (Timer timer) async {
          if (_contentVideoController.value.isInitialized) {
            final Duration? progress = await _contentVideoController.position;
            if (progress != null) {
              await _contentProgressProvider.setProgress(
                progress: progress,
                duration: _contentVideoController.value.duration,
              );
            }
          }
        },
      );
    }

    await _contentVideoController.play();
  }

  Future<void> _pauseContent() {
    setState(() {
      _shouldShowContentVideo = false;
    });
    _contentProgressTimer?.cancel();
    _contentProgressTimer = null;
    return _contentVideoController.pause();
  }

  @override
  void dispose() {
    super.dispose();
    _contentProgressTimer?.cancel();
    _contentVideoController.dispose();
    _adsManager?.destroy();
    WidgetsBinding.instance.removeObserver(this);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          spacing: 100,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            SizedBox(
              width: 300,
              child: !_contentVideoController.value.isInitialized
                  ? Container()
                  : AspectRatio(
                      aspectRatio: _contentVideoController.value.aspectRatio,
                      child: Stack(
                        children: <Widget>[
                          // The display container must be on screen before any Ads can be
                          // loaded and can't be removed between ads. This handles clicks for
                          // ads.
                          _adDisplayContainer,
                          if (_shouldShowContentVideo)
                            VideoPlayer(_contentVideoController)
                        ],
                      ),
                    ),
            ),
            ColoredBox(
              color: Colors.green,
              child: SizedBox(
                width: 300,
                height: 250,
                child: companionAd.buildWidget(context),
              ),
            ),
          ],
        ),
      ),
      floatingActionButton:
          _contentVideoController.value.isInitialized && _shouldShowContentVideo
              ? FloatingActionButton(
                  onPressed: () {
                    setState(() {
                      _contentVideoController.value.isPlaying
                          ? _contentVideoController.pause()
                          : _contentVideoController.play();
                    });
                  },
                  child: Icon(
                    _contentVideoController.value.isPlaying
                        ? Icons.pause
                        : Icons.play_arrow,
                  ),
                )
              : null,
    );
  }
}

Screenshots or Videos

Screenshots / Video demonstration

[Upload media here]

Logs

Logs
I/flutter (18273): OnAdEvent: AdEventType.adProgress => {}
I/flutter (18273): OnAdEvent: AdEventType.skipped => {}
I/flutter (18273): OnAdEvent: AdEventType.contentResumeRequested => {}
[log] VIDEO STATE is :Instance of 'VideoPlayerLoaded'
I/mali_config(18273): @get_buffer_dataspace_setting: update dataspace from GE (0x00000000 -> 0x10010000)
[log] VIDEO STATE is :Instance of 'VideoPlayerPlaying'
I/Choreographer(18273): Skipped 33 frames!  The application may be doing too much work on its main thread.
I/gralloc4(18273): @set_metadata: update dataspace from GM (0x00000000 -> 0x08010000)
[log] Firebase Crashlytics Exception
[log] Null check operator used on a null value
[log] #0      AndroidAdDisplayContainer._startAdProgressTracking.<anonymous closure> (package:interactive_media_ads/src/android/android_ad_display_container.dart:189:34)
<asynchronous suspension>

V/MediaPlayer-JNI(18273): pause
V/MediaPlayerNative(18273): pause

Flutter Doctor output

Doctor output
Flutter (Channel stable, 3.32.8, on Microsoft Windows [Version 10.0.26100.4652], locale en-US)
    ! Warning: `flutter` on your path resolves to C:\Users\Ab-Aziz\fvm\versions\3.32.8\bin\flutter, which is not inside your current Flutter SDK checkout at
      C:\Users\Ab-Aziz\fvm\default. Consider adding C:\Users\Ab-Aziz\fvm\default\bin to the front of your path.
    ! Warning: `dart` on your path resolves to C:\Users\Ab-Aziz\fvm\versions\3.32.8\bin\dart, which is not inside your current Flutter SDK checkout at
      C:\Users\Ab-Aziz\fvm\default. Consider adding C:\Users\Ab-Aziz\fvm\default\bin to the front of your path.
[√] Windows Version (11 Pro 64-bit, 24H2, 2009)
[√] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[√] Chrome - develop for the web
[X] Visual Studio - develop Windows apps
    X Visual Studio not installed; this is necessary to develop Windows apps.
      Download at https://visualstudio.microsoft.com/downloads/.
      Please install the "Desktop development with C++" workload, including all of its default components
[√] Android Studio (version 2024.3)
[√] VS Code (version 1.99.3)
[√] Connected device (4 available)
[√] Network resources

Metadata

Metadata

Assignees

No one assigned

    Labels

    in triagePresently being triaged by the triage teamwaiting for customer responseThe Flutter team cannot make further progress on this issue until the original reporter responds

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions