You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'm developing custom ad playing feature for an app and already implemented IMA SDK-like functionality (a pre-known set of ad breakpoints and playing ads on these breakpoints).
This working ok, but when we tested it on production (3000+ DAU) we faced on some technical difficulties in expanding and optimizing functionality:
the biggest problem is that the advertising points are rigidly fixed on the timeline, which creates many technical and ideological difficulties when, for example, the user rewinds the content forward and backward. (the user can easily skip over an advertising point and it has to be skipped (since, for example, it has not yet been loaded into the player and the user does not have to wait for it to load in real time) - the idea is that the internal logic of the application can load an ad in the background and insert it in real time at any place on the timeline immediately
the other problem is about "Interval midrolls" - the main idea is not to tie advertising to specific points on the timeline, but to track the time the user spends watching content (for example, 10 minutes, no matter where he rewinds) and at the right moment insert advertising immediately at the current position of the player
The main question - is it possible to implement in latest media3 lib in any way?
P.s no, we can't use 2 player instances in our app due to hard technicals limitations
P.p.s there are no public guides or manuals about whole build-in ads feature in media3
is it possible to implement in latest media3 lib in any way?
The existing AdsMediaSource allows to extend the number of ads and modify their positions as of Media3 1.6.0. Instead of writing it manually, our goal is to provide a simpler user layer that lets you insert ads manually at any time you like. There will likely be some restrictions still, for example that you can't insert ads between other existing ads, only behind other ads. But from what I understand, this would fit your use case.
we can't use 2 player instances in our app due to hard technicals limitations
This is also what we are aiming for as it better deals with limited resources, player state handling and smooth transitions.
there are no public guides or manuals about whole build-in ads feature in media3
There is a section at https://developer.android.com/media/media3/exoplayer/ad-insertion#client-using-third-party that matches your use case (your code is basically a "third-party ads SDK"). It's not partocularly detailed though as this is a quite advanced customization. Once we implemented the new layer described above, we'll likely add some more documentation for that.
Assigning to @marcbaechinger for further tracking of the feature request (feel free to expand on my explanations or de-duplicate if there is another issue for that already).
As Toni points out, a save way that can be achieved with existing API is appending ad after ad with increasing insertion positions. For instance after a seek back by the user, an ad can't be inserted behind the last inserted ad position.
"Interval midrolls"
I played around a bit with this use case to see what's possible with the restriction above. The restriction is still in place, but the idea is to have a single ad only and then remove that ad as soon it is played (or on demand). Then set the next ad to an arbitrary position in the VOD stream. There is always only a single midroll ad at the same time.
I assume VOD and that you've written an AdsLoader that works together with AdsMediaSource and maintains the ad playback state of the ads source.
Add a post roll ad at index 0, pos=C.TIME_END_OF_SOURCE. The postroll possibly never plays, but is required to make sure that the timeline never produces a MediaPeriodId.nextAdGroupIndex that is C.INDEX_UNSET. Once it is set to C.INDEX_UNSET the player will never check for ads again without a user seek and hence never hops on an ad we insert. So we always keep this post roll just for having the foot in the door.
Set your timer, then after some minutes, add an ad group with some ads like at currentPosition + 5_000 so there is 5 seconds to load the ad for the player. The ad group insertion index is adPlaybackState.adGroupCount - 1 to keep the post roll at the last index.
Let the ad play. When playback continues on the content, remove the ad group with adPlaybackState.withRemovedAdGrouCount(++removedAdGroupCount).
Continue with 2.
I did a hacky StaticAdsLoader to figure out whether this works. As Toni mentioned above since Media3 1.6, the AdsMediaSource allows the number of ad groups to be increased. The scenario from above uses this capability to grow in a creative way.
However, when testing I found that there is another bit that is missing on Media3 side. The problem sits here in AdPlaybackState where don't anticipate removed ad groups yet. This method is called by AdsMediaSource and it currently doesn't accept an update after removing an ad group. I made it work by changing that method to accept that update and correct to indices accordingly (see below).
Long story short: would such an approach of having the option of setting only the next ad but then being able to set it where ever you want fit your use case? If that's the case we can probably pursue this approach some further to either document it so people can build their own IntervalAdsLoader or bake it into an API of the library.
(Not sure yet when we get around doing this but if it's only about small fixes in the library, we are happy to enable you folks who are poking around with advanced use cases)
Please note that this code comes out of two hours of work or so. So it's not tested and not recommended to use this code. It's purpose is to illustrate the concept of 1) setting a pseudo post roll 2) setting only a single next ad on demand and 3) removing all ads except the post roll when an add is played (or manually on demand).
Please use this code as it would not exist, it requires a change in the library AdPlaybackState to work and hasn't been tested.
Our first solution which we created before - we have preroll (start pos = 0us), universal midroll (start pos = 1us) and postroll (start pos = C.TIME_END_OF_SOURCE). The main interesting thing here - universal midroll, which is "skipped" state on start.
During user timespent on video, our app core is decides when to load an ad (for example 5 min of wathcing video). In this moment we make midroll available + .withAvailableAdMediaItem() and ad starts loading immediately.
The disadvantage of this solution is that the ads are loaded in real time (user see loading bar) which is poor for UX
The next solution was to load ad media to context.cacheDir in background and when we want to show ad - make midroll available + withAvailableAdMediaItem(local cache dir uri). In that case we have an almost instant ad loading (but user see micro-loading bar anyway). The disadvantage is to load whole media file in cache, but as i understand right - default media loading is runtime-loading from web url without storing whole media file. Also the disadvantage is micro-loading bar. It always appears - even if we have load media file into ram memory, not to cache dir.
Adding group on runtime is cool new feature, but seems it not quite suitable for this case:
then after some minutes, add an ad group with some ads like at currentPosition + 5_000 so there is 5 seconds to load the ad for the player
The main problem is that user can seek back or seek forward (but ad still doesnt loaded) during this time and we should handle this case. In our solutions (with midroll on 1us) we try to workaround of this problem - we dont care about user seek on video because when we want to play ad "right now in any seek position" we just enable adgroup on 1us and it will show.
But it still has some disadvantages described above (at least micro loading bar)
Thanks for explaining your approach. Very interesting. This is very useful for us. I can see how your approach with the skipped state can work.
The disadvantage of this solution is that the ads are loaded in real time (user see loading bar) which is poor for UX
even if we have load media file into ram memory, not to cache dir.
The approach I was highlighting above ads a new ad group a bit ahead of the current position (like player.getCurrentPosition() + MARGIN_MS). This makes the ad period part of the timeline and the player automatically starts buffering the ad. If loading the ad is quick enough, this avoids a buffering state if the player has enough time to load the data within MARGIN_MS.
In the case of your ad already being cached on disk, I think that MARGIN_MS could be quite small. If already preloaded in a sample queue, I'd guess MARGIN_MS can be a couple of hundred milliseconds.
Ideally we can add an API to add preloading capabilities to AdsMediaSource as this would help to preload ad media to memory. You can probably achieve this already by using a custom MediaSource.Factory and then return a preloaded source when AdsMediSource calls createMediaSource(MediaItem) to request an ad media source.
Activity
tonihei commentedon Mar 28, 2025
The existing
AdsMediaSourceallows to extend the number of ads and modify their positions as of Media3 1.6.0. Instead of writing it manually, our goal is to provide a simpler user layer that lets you insert ads manually at any time you like. There will likely be some restrictions still, for example that you can't insert ads between other existing ads, only behind other ads. But from what I understand, this would fit your use case.This is also what we are aiming for as it better deals with limited resources, player state handling and smooth transitions.
There is a section at https://developer.android.com/media/media3/exoplayer/ad-insertion#client-using-third-party that matches your use case (your code is basically a "third-party ads SDK"). It's not partocularly detailed though as this is a quite advanced customization. Once we implemented the new layer described above, we'll likely add some more documentation for that.
Assigning to @marcbaechinger for further tracking of the feature request (feel free to expand on my explanations or de-duplicate if there is another issue for that already).
[-][ads] Interval midrolls and catch-up midrolls[/-][+]Manually add new ads to a playback[/+]marcbaechinger commentedon Apr 1, 2025
As Toni points out, a save way that can be achieved with existing API is appending ad after ad with increasing insertion positions. For instance after a seek back by the user, an ad can't be inserted behind the last inserted ad position.
I played around a bit with this use case to see what's possible with the restriction above. The restriction is still in place, but the idea is to have a single ad only and then remove that ad as soon it is played (or on demand). Then set the next ad to an arbitrary position in the VOD stream. There is always only a single midroll ad at the same time.
I assume VOD and that you've written an
AdsLoaderthat works together withAdsMediaSourceand maintains the ad playback state of the ads source.C.TIME_END_OF_SOURCE. The postroll possibly never plays, but is required to make sure that the timeline never produces aMediaPeriodId.nextAdGroupIndexthat isC.INDEX_UNSET. Once it is set toC.INDEX_UNSETthe player will never check for ads again without a user seek and hence never hops on an ad we insert. So we always keep this post roll just for having the foot in the door.currentPosition + 5_000so there is 5 seconds to load the ad for the player. The ad group insertion index isadPlaybackState.adGroupCount - 1to keep the post roll at the last index.adPlaybackState.withRemovedAdGrouCount(++removedAdGroupCount).I did a hacky
StaticAdsLoaderto figure out whether this works. As Toni mentioned above since Media3 1.6, theAdsMediaSourceallows the number of ad groups to be increased. The scenario from above uses this capability to grow in a creative way.However, when testing I found that there is another bit that is missing on Media3 side. The problem sits here in
AdPlaybackStatewhere don't anticipate removed ad groups yet. This method is called byAdsMediaSourceand it currently doesn't accept an update after removing an ad group. I made it work by changing that method to accept that update and correct to indices accordingly (see below).Long story short: would such an approach of having the option of setting only the next ad but then being able to set it where ever you want fit your use case? If that's the case we can probably pursue this approach some further to either document it so people can build their own
IntervalAdsLoaderor bake it into an API of the library.(Not sure yet when we get around doing this but if it's only about small fixes in the library, we are happy to enable you folks who are poking around with advanced use cases)
Please note that this code comes out of two hours of work or so. So it's not tested and not recommended to use this code. It's purpose is to illustrate the concept of 1) setting a pseudo post roll 2) setting only a single next ad on demand and 3) removing all ads except the post roll when an add is played (or manually on demand).
Please use this code as it would not exist, it requires a change in the library
AdPlaybackStateto work and hasn't been tested.At least this method needs to be changed on the Media3 library side to make the above ads loader work:
Make AdPlaybackState.withAdDurationsUs work with removed ad groups
amorenkov commentedon Apr 4, 2025
Thanks a lot, will try to implement in similar way. I will not close this question for now in case of any unforeseen questions on this topic
amorenkov commentedon Apr 4, 2025
Our first solution which we created before - we have preroll (start pos = 0us), universal midroll (start pos = 1us) and postroll (start pos = C.TIME_END_OF_SOURCE). The main interesting thing here - universal midroll, which is "skipped" state on start.
During user timespent on video, our app core is decides when to load an ad (for example 5 min of wathcing video). In this moment we make midroll available + .withAvailableAdMediaItem() and ad starts loading immediately.
The disadvantage of this solution is that the ads are loaded in real time (user see loading bar) which is poor for UX
The next solution was to load ad media to context.cacheDir in background and when we want to show ad - make midroll available + withAvailableAdMediaItem(local cache dir uri). In that case we have an almost instant ad loading (but user see micro-loading bar anyway). The disadvantage is to load whole media file in cache, but as i understand right - default media loading is runtime-loading from web url without storing whole media file. Also the disadvantage is micro-loading bar. It always appears - even if we have load media file into ram memory, not to cache dir.
Adding group on runtime is cool new feature, but seems it not quite suitable for this case:
The main problem is that user can seek back or seek forward (but ad still doesnt loaded) during this time and we should handle this case. In our solutions (with midroll on 1us) we try to workaround of this problem - we dont care about user seek on video because when we want to play ad "right now in any seek position" we just enable adgroup on 1us and it will show.
But it still has some disadvantages described above (at least micro loading bar)
marcbaechinger commentedon Apr 7, 2025
Thanks for explaining your approach. Very interesting. This is very useful for us. I can see how your approach with the skipped state can work.
The approach I was highlighting above ads a new ad group a bit ahead of the current position (like
player.getCurrentPosition() + MARGIN_MS). This makes the ad period part of the timeline and the player automatically starts buffering the ad. If loading the ad is quick enough, this avoids a buffering state if the player has enough time to load the data withinMARGIN_MS.In the case of your ad already being cached on disk, I think that
MARGIN_MScould be quite small. If already preloaded in a sample queue, I'd guessMARGIN_MScan be a couple of hundred milliseconds.Ideally we can add an API to add preloading capabilities to
AdsMediaSourceas this would help to preload ad media to memory. You can probably achieve this already by using a customMediaSource.Factoryand then return a preloaded source whenAdsMediSourcecallscreateMediaSource(MediaItem)to request an ad media source.Make AdPlaybackState.withAdDurationsUs work with removed ad groups
Make AdPlaybackState.withAdDurationsUs work with removed ad groups