Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
05f6d248
authored
Nov 23, 2020
by
andrewlewis
Committed by
kim-vde
Nov 24, 2020
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Add support for ad playlists with `ImaAdsLoader`
Issue: #3750 PiperOrigin-RevId: 343878310
parent
689e89e5
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
287 additions
and
90 deletions
extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java
extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java
extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java
extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java
View file @
05f6d248
...
@@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.ima;
...
@@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.ima;
import
static
com
.
google
.
android
.
exoplayer2
.
ext
.
ima
.
ImaUtil
.
BITRATE_UNSET
;
import
static
com
.
google
.
android
.
exoplayer2
.
ext
.
ima
.
ImaUtil
.
BITRATE_UNSET
;
import
static
com
.
google
.
android
.
exoplayer2
.
ext
.
ima
.
ImaUtil
.
TIMEOUT_UNSET
;
import
static
com
.
google
.
android
.
exoplayer2
.
ext
.
ima
.
ImaUtil
.
TIMEOUT_UNSET
;
import
static
com
.
google
.
android
.
exoplayer2
.
ext
.
ima
.
ImaUtil
.
getAdGroupTimesUsForCuePoints
;
import
static
com
.
google
.
android
.
exoplayer2
.
ext
.
ima
.
ImaUtil
.
getImaLooper
;
import
static
com
.
google
.
android
.
exoplayer2
.
ext
.
ima
.
ImaUtil
.
getImaLooper
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkArgument
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkState
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkState
;
import
static
java
.
lang
.
Math
.
max
;
import
static
java
.
lang
.
Math
.
max
;
...
@@ -280,11 +280,11 @@ import java.util.Map;
...
@@ -280,11 +280,11 @@ import java.util.Map;
}
}
}
}
/**
Starts using the ads loader for playback. */
/**
public
void
start
(
Player
player
,
AdViewProvider
adViewProvider
,
EventListener
eventListener
)
{
* Starts passing events from this instance (including any pending ad playback state) and
this
.
player
=
player
;
* registers obstructions.
player
.
addListener
(
this
);
*/
boolean
playWhenReady
=
player
.
getPlayWhenReady
();
public
void
start
(
AdViewProvider
adViewProvider
,
EventListener
eventListener
)
{
this
.
eventListener
=
eventListener
;
this
.
eventListener
=
eventListener
;
lastVolumePercent
=
0
;
lastVolumePercent
=
0
;
lastAdProgress
=
VideoProgressUpdate
.
VIDEO_TIME_NOT_READY
;
lastAdProgress
=
VideoProgressUpdate
.
VIDEO_TIME_NOT_READY
;
...
@@ -293,13 +293,9 @@ import java.util.Map;
...
@@ -293,13 +293,9 @@ import java.util.Map;
if
(!
AdPlaybackState
.
NONE
.
equals
(
adPlaybackState
))
{
if
(!
AdPlaybackState
.
NONE
.
equals
(
adPlaybackState
))
{
// Pass the ad playback state to the player, and resume ads if necessary.
// Pass the ad playback state to the player, and resume ads if necessary.
eventListener
.
onAdPlaybackState
(
adPlaybackState
);
eventListener
.
onAdPlaybackState
(
adPlaybackState
);
if
(
adsManager
!=
null
&&
imaPausedContent
&&
playWhenReady
)
{
adsManager
.
resume
();
}
}
else
if
(
adsManager
!=
null
)
{
}
else
if
(
adsManager
!=
null
)
{
adPlaybackState
=
adPlaybackState
=
new
AdPlaybackState
(
new
AdPlaybackState
(
adsId
,
getAdGroupTimesUsForCuePoints
(
adsManager
.
getAdCuePoints
()));
adsId
,
ImaUtil
.
getAdGroupTimesUsForCuePoints
(
adsManager
.
getAdCuePoints
()));
updateAdPlaybackState
();
updateAdPlaybackState
();
}
}
for
(
OverlayInfo
overlayInfo
:
adViewProvider
.
getAdOverlayInfos
())
{
for
(
OverlayInfo
overlayInfo
:
adViewProvider
.
getAdOverlayInfos
())
{
...
@@ -311,14 +307,36 @@ import java.util.Map;
...
@@ -311,14 +307,36 @@ import java.util.Map;
}
}
}
}
/** Stops using the ads loader for playback. */
/**
public
void
stop
()
{
* Populates the ad playback state with loaded cue points, if available. Any preroll will be
@Nullable
Player
player
=
this
.
player
;
* paused immediately while waiting for this instance to be {@link #activate(Player) activated}.
if
(
player
==
null
)
{
*/
return
;
public
void
maybePreloadAds
(
long
contentPositionMs
,
long
contentDurationMs
)
{
maybeInitializeAdsManager
(
contentPositionMs
,
contentDurationMs
);
}
/** Activates playback. */
public
void
activate
(
Player
player
)
{
this
.
player
=
player
;
player
.
addListener
(
this
);
boolean
playWhenReady
=
player
.
getPlayWhenReady
();
onTimelineChanged
(
player
.
getCurrentTimeline
(),
Player
.
TIMELINE_CHANGE_REASON_SOURCE_UPDATE
);
if
(!
AdPlaybackState
.
NONE
.
equals
(
adPlaybackState
)
&&
adsManager
!=
null
&&
imaPausedContent
&&
playWhenReady
)
{
adsManager
.
resume
();
}
}
if
(
adsManager
!=
null
&&
imaPausedContent
)
{
}
adsManager
.
pause
();
/** Deactivates playback. */
public
void
deactivate
()
{
Player
player
=
checkNotNull
(
this
.
player
);
if
(!
AdPlaybackState
.
NONE
.
equals
(
adPlaybackState
)
&&
imaPausedContent
)
{
if
(
adsManager
!=
null
)
{
adsManager
.
pause
();
}
adPlaybackState
=
adPlaybackState
=
adPlaybackState
.
withAdResumePositionUs
(
adPlaybackState
.
withAdResumePositionUs
(
playingAd
?
C
.
msToUs
(
player
.
getCurrentPosition
())
:
0
);
playingAd
?
C
.
msToUs
(
player
.
getCurrentPosition
())
:
0
);
...
@@ -326,10 +344,15 @@ import java.util.Map;
...
@@ -326,10 +344,15 @@ import java.util.Map;
lastVolumePercent
=
getPlayerVolumePercent
();
lastVolumePercent
=
getPlayerVolumePercent
();
lastAdProgress
=
getAdVideoProgressUpdate
();
lastAdProgress
=
getAdVideoProgressUpdate
();
lastContentProgress
=
getContentVideoProgressUpdate
();
lastContentProgress
=
getContentVideoProgressUpdate
();
adDisplayContainer
.
unregisterAllFriendlyObstructions
();
player
.
removeListener
(
this
);
player
.
removeListener
(
this
);
this
.
player
=
null
;
this
.
player
=
null
;
}
/** Stops passing of events from this instance and unregisters obstructions. */
public
void
stop
()
{
eventListener
=
null
;
eventListener
=
null
;
adDisplayContainer
.
unregisterAllFriendlyObstructions
();
}
}
/** Releases all resources used by the ad tag loader. */
/** Releases all resources used by the ad tag loader. */
...
@@ -392,7 +415,6 @@ import java.util.Map;
...
@@ -392,7 +415,6 @@ import java.util.Map;
// The player is being reset or contains no media.
// The player is being reset or contains no media.
return
;
return
;
}
}
checkArgument
(
timeline
.
getPeriodCount
()
==
1
);
this
.
timeline
=
timeline
;
this
.
timeline
=
timeline
;
Player
player
=
checkNotNull
(
this
.
player
);
Player
player
=
checkNotNull
(
this
.
player
);
long
contentDurationUs
=
timeline
.
getPeriod
(
player
.
getCurrentPeriodIndex
(),
period
).
durationUs
;
long
contentDurationUs
=
timeline
.
getPeriod
(
player
.
getCurrentPeriodIndex
(),
period
).
durationUs
;
...
@@ -592,14 +614,13 @@ import java.util.Map;
...
@@ -592,14 +614,13 @@ import java.util.Map;
}
}
private
VideoProgressUpdate
getContentVideoProgressUpdate
()
{
private
VideoProgressUpdate
getContentVideoProgressUpdate
()
{
if
(
player
==
null
)
{
return
lastContentProgress
;
}
boolean
hasContentDuration
=
contentDurationMs
!=
C
.
TIME_UNSET
;
boolean
hasContentDuration
=
contentDurationMs
!=
C
.
TIME_UNSET
;
long
contentPositionMs
;
long
contentPositionMs
;
if
(
pendingContentPositionMs
!=
C
.
TIME_UNSET
)
{
if
(
pendingContentPositionMs
!=
C
.
TIME_UNSET
)
{
sentPendingContentPositionMs
=
true
;
sentPendingContentPositionMs
=
true
;
contentPositionMs
=
pendingContentPositionMs
;
contentPositionMs
=
pendingContentPositionMs
;
}
else
if
(
player
==
null
)
{
return
lastContentProgress
;
}
else
if
(
fakeContentProgressElapsedRealtimeMs
!=
C
.
TIME_UNSET
)
{
}
else
if
(
fakeContentProgressElapsedRealtimeMs
!=
C
.
TIME_UNSET
)
{
long
elapsedSinceEndMs
=
SystemClock
.
elapsedRealtime
()
-
fakeContentProgressElapsedRealtimeMs
;
long
elapsedSinceEndMs
=
SystemClock
.
elapsedRealtime
()
-
fakeContentProgressElapsedRealtimeMs
;
contentPositionMs
=
fakeContentProgressOffsetMs
+
elapsedSinceEndMs
;
contentPositionMs
=
fakeContentProgressOffsetMs
+
elapsedSinceEndMs
;
...
@@ -923,7 +944,8 @@ import java.util.Map;
...
@@ -923,7 +944,8 @@ import java.util.Map;
adCallbacks
.
get
(
i
).
onResume
(
adMediaInfo
);
adCallbacks
.
get
(
i
).
onResume
(
adMediaInfo
);
}
}
}
}
if
(!
checkNotNull
(
player
).
getPlayWhenReady
())
{
if
(
player
==
null
||
!
player
.
getPlayWhenReady
())
{
// Either this loader hasn't been activated yet, or the player is paused now.
checkNotNull
(
adsManager
).
pause
();
checkNotNull
(
adsManager
).
pause
();
}
}
}
}
...
@@ -941,7 +963,14 @@ import java.util.Map;
...
@@ -941,7 +963,14 @@ import java.util.Map;
// to a different position, so drop the event. See also [Internal: b/159111848].
// to a different position, so drop the event. See also [Internal: b/159111848].
return
;
return
;
}
}
checkState
(
adMediaInfo
.
equals
(
imaAdMediaInfo
));
if
(
configuration
.
debugModeEnabled
&&
!
adMediaInfo
.
equals
(
imaAdMediaInfo
))
{
Log
.
w
(
TAG
,
"Unexpected pauseAd for "
+
getAdMediaInfoString
(
adMediaInfo
)
+
", expected "
+
getAdMediaInfoString
(
imaAdMediaInfo
));
}
imaAdState
=
IMA_AD_STATE_PAUSED
;
imaAdState
=
IMA_AD_STATE_PAUSED
;
for
(
int
i
=
0
;
i
<
adCallbacks
.
size
();
i
++)
{
for
(
int
i
=
0
;
i
<
adCallbacks
.
size
();
i
++)
{
adCallbacks
.
get
(
i
).
onPause
(
adMediaInfo
);
adCallbacks
.
get
(
i
).
onPause
(
adMediaInfo
);
...
@@ -1157,9 +1186,13 @@ import java.util.Map;
...
@@ -1157,9 +1186,13 @@ import java.util.Map;
throw
new
IllegalStateException
(
"Failed to find cue point"
);
throw
new
IllegalStateException
(
"Failed to find cue point"
);
}
}
private
String
getAdMediaInfoString
(
AdMediaInfo
adMediaInfo
)
{
private
String
getAdMediaInfoString
(
@Nullable
AdMediaInfo
adMediaInfo
)
{
@Nullable
AdInfo
adInfo
=
adInfoByAdMediaInfo
.
get
(
adMediaInfo
);
@Nullable
AdInfo
adInfo
=
adInfoByAdMediaInfo
.
get
(
adMediaInfo
);
return
"AdMediaInfo["
+
adMediaInfo
.
getUrl
()
+
(
adInfo
!=
null
?
", "
+
adInfo
:
""
)
+
"]"
;
return
"AdMediaInfo["
+
(
adMediaInfo
==
null
?
"null"
:
adMediaInfo
.
getUrl
())
+
", "
+
adInfo
+
"]"
;
}
}
private
static
long
getContentPeriodPositionMs
(
private
static
long
getContentPeriodPositionMs
(
...
@@ -1226,16 +1259,12 @@ import java.util.Map;
...
@@ -1226,16 +1259,12 @@ import java.util.Map;
if
(
configuration
.
applicationAdEventListener
!=
null
)
{
if
(
configuration
.
applicationAdEventListener
!=
null
)
{
adsManager
.
addAdEventListener
(
configuration
.
applicationAdEventListener
);
adsManager
.
addAdEventListener
(
configuration
.
applicationAdEventListener
);
}
}
if
(
player
!=
null
)
{
try
{
// If a player is attached already, start playback immediately.
adPlaybackState
=
try
{
new
AdPlaybackState
(
adsId
,
getAdGroupTimesUsForCuePoints
(
adsManager
.
getAdCuePoints
()));
adPlaybackState
=
updateAdPlaybackState
();
new
AdPlaybackState
(
}
catch
(
RuntimeException
e
)
{
adsId
,
ImaUtil
.
getAdGroupTimesUsForCuePoints
(
adsManager
.
getAdCuePoints
()));
maybeNotifyInternalError
(
"onAdsManagerLoaded"
,
e
);
updateAdPlaybackState
();
}
catch
(
RuntimeException
e
)
{
maybeNotifyInternalError
(
"onAdsManagerLoaded"
,
e
);
}
}
}
}
}
...
...
extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
View file @
05f6d248
...
@@ -44,6 +44,7 @@ import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer;
...
@@ -44,6 +44,7 @@ import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.ExoPlayerLibraryInfo
;
import
com.google.android.exoplayer2.ExoPlayerLibraryInfo
;
import
com.google.android.exoplayer2.Player
;
import
com.google.android.exoplayer2.Player
;
import
com.google.android.exoplayer2.Timeline
;
import
com.google.android.exoplayer2.source.MediaSourceFactory
;
import
com.google.android.exoplayer2.source.MediaSourceFactory
;
import
com.google.android.exoplayer2.source.ads.AdsLoader
;
import
com.google.android.exoplayer2.source.ads.AdsLoader
;
import
com.google.android.exoplayer2.source.ads.AdsMediaSource
;
import
com.google.android.exoplayer2.source.ads.AdsMediaSource
;
...
@@ -57,6 +58,7 @@ import java.util.ArrayList;
...
@@ -57,6 +58,7 @@ import java.util.ArrayList;
import
java.util.Arrays
;
import
java.util.Arrays
;
import
java.util.Collection
;
import
java.util.Collection
;
import
java.util.Collections
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.List
;
import
java.util.Locale
;
import
java.util.Locale
;
import
java.util.Set
;
import
java.util.Set
;
...
@@ -371,12 +373,16 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
...
@@ -371,12 +373,16 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
private
final
ImaUtil
.
Configuration
configuration
;
private
final
ImaUtil
.
Configuration
configuration
;
private
final
Context
context
;
private
final
Context
context
;
private
final
ImaUtil
.
ImaFactory
imaFactory
;
private
final
ImaUtil
.
ImaFactory
imaFactory
;
private
final
HashMap
<
Object
,
AdTagLoader
>
adTagLoaderByAdsId
;
private
final
HashMap
<
AdsMediaSource
,
AdTagLoader
>
adTagLoaderByAdsMediaSource
;
private
final
Timeline
.
Period
period
;
private
final
Timeline
.
Window
window
;
private
boolean
wasSetPlayerCalled
;
private
boolean
wasSetPlayerCalled
;
@Nullable
private
Player
nextPlayer
;
@Nullable
private
Player
nextPlayer
;
@Nullable
private
AdTagLoader
adTagLoader
;
private
List
<
String
>
supportedMimeTypes
;
private
List
<
String
>
supportedMimeTypes
;
@Nullable
private
Player
player
;
@Nullable
private
Player
player
;
@Nullable
private
AdTagLoader
currentAdTagLoader
;
private
ImaAdsLoader
(
private
ImaAdsLoader
(
Context
context
,
ImaUtil
.
Configuration
configuration
,
ImaUtil
.
ImaFactory
imaFactory
)
{
Context
context
,
ImaUtil
.
Configuration
configuration
,
ImaUtil
.
ImaFactory
imaFactory
)
{
...
@@ -384,6 +390,10 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
...
@@ -384,6 +390,10 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
this
.
configuration
=
configuration
;
this
.
configuration
=
configuration
;
this
.
imaFactory
=
imaFactory
;
this
.
imaFactory
=
imaFactory
;
supportedMimeTypes
=
ImmutableList
.
of
();
supportedMimeTypes
=
ImmutableList
.
of
();
adTagLoaderByAdsId
=
new
HashMap
<>();
adTagLoaderByAdsMediaSource
=
new
HashMap
<>();
period
=
new
Timeline
.
Period
();
window
=
new
Timeline
.
Window
();
}
}
/**
/**
...
@@ -394,7 +404,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
...
@@ -394,7 +404,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
@SuppressWarnings
(
"nullness:nullness.on.outer"
)
@SuppressWarnings
(
"nullness:nullness.on.outer"
)
@Nullable
@Nullable
public
com
.
google
.
ads
.
interactivemedia
.
v3
.
api
.
AdsLoader
getAdsLoader
()
{
public
com
.
google
.
ads
.
interactivemedia
.
v3
.
api
.
AdsLoader
getAdsLoader
()
{
return
adTagLoader
!=
null
?
a
dTagLoader
.
getAdsLoader
()
:
null
;
return
currentAdTagLoader
!=
null
?
currentA
dTagLoader
.
getAdsLoader
()
:
null
;
}
}
/**
/**
...
@@ -410,7 +420,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
...
@@ -410,7 +420,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
*/
*/
@Nullable
@Nullable
public
AdDisplayContainer
getAdDisplayContainer
()
{
public
AdDisplayContainer
getAdDisplayContainer
()
{
return
adTagLoader
!=
null
?
a
dTagLoader
.
getAdDisplayContainer
()
:
null
;
return
currentAdTagLoader
!=
null
?
currentA
dTagLoader
.
getAdDisplayContainer
()
:
null
;
}
}
/**
/**
...
@@ -427,8 +437,8 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
...
@@ -427,8 +437,8 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
* null} if playing audio-only ads.
* null} if playing audio-only ads.
*/
*/
public
void
requestAds
(
DataSpec
adTagDataSpec
,
Object
adsId
,
@Nullable
ViewGroup
adViewGroup
)
{
public
void
requestAds
(
DataSpec
adTagDataSpec
,
Object
adsId
,
@Nullable
ViewGroup
adViewGroup
)
{
if
(
adTagLoader
==
null
)
{
if
(
!
adTagLoaderByAdsId
.
containsKey
(
adsId
)
)
{
adTagLoader
=
AdTagLoader
adTagLoader
=
new
AdTagLoader
(
new
AdTagLoader
(
context
,
context
,
configuration
,
configuration
,
...
@@ -437,6 +447,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
...
@@ -437,6 +447,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
adTagDataSpec
,
adTagDataSpec
,
adsId
,
adsId
,
adViewGroup
);
adViewGroup
);
adTagLoaderByAdsId
.
put
(
adsId
,
adTagLoader
);
}
}
}
}
...
@@ -448,8 +459,8 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
...
@@ -448,8 +459,8 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
* IMA SDK provides the UI to skip ads in the ad view group passed via {@link AdViewProvider}.
* IMA SDK provides the UI to skip ads in the ad view group passed via {@link AdViewProvider}.
*/
*/
public
void
skipAd
()
{
public
void
skipAd
()
{
if
(
a
dTagLoader
!=
null
)
{
if
(
currentA
dTagLoader
!=
null
)
{
a
dTagLoader
.
skipAd
();
currentA
dTagLoader
.
skipAd
();
}
}
}
}
...
@@ -494,37 +505,67 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
...
@@ -494,37 +505,67 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
EventListener
eventListener
)
{
EventListener
eventListener
)
{
checkState
(
checkState
(
wasSetPlayerCalled
,
"Set player using adsLoader.setPlayer before preparing the player."
);
wasSetPlayerCalled
,
"Set player using adsLoader.setPlayer before preparing the player."
);
player
=
nextPlayer
;
if
(
adTagLoaderByAdsMediaSource
.
isEmpty
())
{
@Nullable
Player
player
=
this
.
player
;
player
=
nextPlayer
;
if
(
player
==
null
)
{
@Nullable
Player
player
=
this
.
player
;
return
;
if
(
player
==
null
)
{
return
;
}
player
.
addListener
(
this
);
}
}
@Nullable
AdTagLoader
adTagLoader
=
adTagLoaderByAdsId
.
get
(
adsId
);
if
(
adTagLoader
==
null
)
{
if
(
adTagLoader
==
null
)
{
requestAds
(
adTagDataSpec
,
adsId
,
adViewProvider
.
getAdViewGroup
());
requestAds
(
adTagDataSpec
,
adsId
,
adViewProvider
.
getAdViewGroup
());
adTagLoader
=
adTagLoaderByAdsId
.
get
(
adsId
);
}
}
checkNotNull
(
adTagLoader
).
start
(
player
,
adViewProvider
,
eventListener
);
adTagLoaderByAdsMediaSource
.
put
(
adsMediaSource
,
checkNotNull
(
adTagLoader
));
checkNotNull
(
adTagLoader
).
start
(
adViewProvider
,
eventListener
);
maybeUpdateCurrentAdTagLoader
();
}
}
@Override
@Override
public
void
stop
(
AdsMediaSource
adsMediaSource
)
{
public
void
stop
(
AdsMediaSource
adsMediaSource
)
{
if
(
player
!=
null
&&
adTagLoader
!=
null
)
{
@Nullable
AdTagLoader
removedAdTagLoader
=
adTagLoaderByAdsMediaSource
.
remove
(
adsMediaSource
);
adTagLoader
.
stop
();
maybeUpdateCurrentAdTagLoader
();
if
(
removedAdTagLoader
!=
null
)
{
removedAdTagLoader
.
stop
();
}
if
(
player
!=
null
&&
adTagLoaderByAdsMediaSource
.
isEmpty
())
{
player
.
removeListener
(
this
);
player
=
null
;
}
}
}
}
@Override
@Override
public
void
release
()
{
public
void
release
()
{
if
(
adTagLoader
!=
null
)
{
if
(
player
!=
null
)
{
player
.
removeListener
(
this
);
player
=
null
;
maybeUpdateCurrentAdTagLoader
();
}
nextPlayer
=
null
;
for
(
AdTagLoader
adTagLoader
:
adTagLoaderByAdsMediaSource
.
values
())
{
adTagLoader
.
release
();
}
adTagLoaderByAdsMediaSource
.
clear
();
for
(
AdTagLoader
adTagLoader
:
adTagLoaderByAdsId
.
values
())
{
adTagLoader
.
release
();
adTagLoader
.
release
();
}
}
adTagLoaderByAdsId
.
clear
();
}
}
@Override
@Override
public
void
handlePrepareComplete
(
public
void
handlePrepareComplete
(
AdsMediaSource
adsMediaSource
,
int
adGroupIndex
,
int
adIndexInAdGroup
)
{
AdsMediaSource
adsMediaSource
,
int
adGroupIndex
,
int
adIndexInAdGroup
)
{
if
(
adTagLoader
!
=
null
)
{
if
(
player
=
=
null
)
{
adTagLoader
.
handlePrepareComplete
(
adGroupIndex
,
adIndexInAdGroup
)
;
return
;
}
}
checkNotNull
(
adTagLoaderByAdsMediaSource
.
get
(
adsMediaSource
))
.
handlePrepareComplete
(
adGroupIndex
,
adIndexInAdGroup
);
}
}
@Override
@Override
...
@@ -533,9 +574,112 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
...
@@ -533,9 +574,112 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
int
adGroupIndex
,
int
adGroupIndex
,
int
adIndexInAdGroup
,
int
adIndexInAdGroup
,
IOException
exception
)
{
IOException
exception
)
{
if
(
adTagLoader
!=
null
)
{
if
(
player
==
null
)
{
adTagLoader
.
handlePrepareError
(
adGroupIndex
,
adIndexInAdGroup
,
exception
);
return
;
}
checkNotNull
(
adTagLoaderByAdsMediaSource
.
get
(
adsMediaSource
))
.
handlePrepareError
(
adGroupIndex
,
adIndexInAdGroup
,
exception
);
}
// Player.EventListener implementation.
@Override
public
void
onTimelineChanged
(
Timeline
timeline
,
@Player
.
TimelineChangeReason
int
reason
)
{
if
(
timeline
.
isEmpty
())
{
// The player is being reset or contains no media.
return
;
}
maybeUpdateCurrentAdTagLoader
();
maybePreloadNextPeriodAds
();
}
@Override
public
void
onPositionDiscontinuity
(
@Player
.
DiscontinuityReason
int
reason
)
{
maybeUpdateCurrentAdTagLoader
();
maybePreloadNextPeriodAds
();
}
@Override
public
void
onShuffleModeEnabledChanged
(
boolean
shuffleModeEnabled
)
{
maybePreloadNextPeriodAds
();
}
@Override
public
void
onRepeatModeChanged
(
@Player
.
RepeatMode
int
repeatMode
)
{
maybePreloadNextPeriodAds
();
}
// Internal methods.
private
void
maybeUpdateCurrentAdTagLoader
()
{
@Nullable
AdTagLoader
oldAdTagLoader
=
currentAdTagLoader
;
@Nullable
AdTagLoader
newAdTagLoader
=
getCurrentAdTagLoader
();
if
(!
Util
.
areEqual
(
oldAdTagLoader
,
newAdTagLoader
))
{
if
(
oldAdTagLoader
!=
null
)
{
oldAdTagLoader
.
deactivate
();
}
currentAdTagLoader
=
newAdTagLoader
;
if
(
newAdTagLoader
!=
null
)
{
newAdTagLoader
.
activate
(
checkNotNull
(
player
));
}
}
}
@Nullable
private
AdTagLoader
getCurrentAdTagLoader
()
{
@Nullable
Player
player
=
this
.
player
;
if
(
player
==
null
)
{
return
null
;
}
Timeline
timeline
=
player
.
getCurrentTimeline
();
if
(
timeline
.
isEmpty
())
{
return
null
;
}
int
periodIndex
=
player
.
getCurrentPeriodIndex
();
@Nullable
Object
adsId
=
timeline
.
getPeriod
(
periodIndex
,
period
).
getAdsId
();
if
(
adsId
==
null
)
{
return
null
;
}
@Nullable
AdTagLoader
adTagLoader
=
adTagLoaderByAdsId
.
get
(
adsId
);
if
(
adTagLoader
==
null
||
!
adTagLoaderByAdsMediaSource
.
containsValue
(
adTagLoader
))
{
return
null
;
}
return
adTagLoader
;
}
private
void
maybePreloadNextPeriodAds
()
{
@Nullable
Player
player
=
this
.
player
;
if
(
player
==
null
)
{
return
;
}
Timeline
timeline
=
player
.
getCurrentTimeline
();
if
(
timeline
.
isEmpty
())
{
return
;
}
int
nextPeriodIndex
=
timeline
.
getNextPeriodIndex
(
player
.
getCurrentPeriodIndex
(),
period
,
window
,
player
.
getRepeatMode
(),
player
.
getShuffleModeEnabled
());
if
(
nextPeriodIndex
==
C
.
INDEX_UNSET
)
{
return
;
}
timeline
.
getPeriod
(
nextPeriodIndex
,
period
);
@Nullable
Object
nextAdsId
=
period
.
getAdsId
();
if
(
nextAdsId
==
null
)
{
return
;
}
@Nullable
AdTagLoader
nextAdTagLoader
=
adTagLoaderByAdsId
.
get
(
nextAdsId
);
if
(
nextAdTagLoader
==
null
||
nextAdTagLoader
==
currentAdTagLoader
)
{
return
;
}
}
long
periodPositionUs
=
timeline
.
getPeriodPosition
(
window
,
period
,
period
.
windowIndex
,
/* windowPositionUs= */
C
.
TIME_UNSET
)
.
second
;
nextAdTagLoader
.
maybePreloadAds
(
C
.
usToMs
(
periodPositionUs
),
C
.
usToMs
(
period
.
durationUs
));
}
}
/**
/**
...
...
extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java
View file @
05f6d248
...
@@ -21,25 +21,30 @@ import com.google.android.exoplayer2.Player;
...
@@ -21,25 +21,30 @@ import com.google.android.exoplayer2.Player;
import
com.google.android.exoplayer2.Timeline
;
import
com.google.android.exoplayer2.Timeline
;
import
com.google.android.exoplayer2.testutil.StubExoPlayer
;
import
com.google.android.exoplayer2.testutil.StubExoPlayer
;
import
com.google.android.exoplayer2.trackselection.TrackSelectionArray
;
import
com.google.android.exoplayer2.trackselection.TrackSelectionArray
;
import
java.util.ArrayLis
t
;
import
com.google.android.exoplayer2.util.ListenerSe
t
;
/** A fake player for testing content/ad playback. */
/** A fake player for testing content/ad playback. */
/* package */
final
class
FakePlayer
extends
StubExoPlayer
{
/* package */
final
class
FakePlayer
extends
StubExoPlayer
{
private
final
ArrayList
<
Player
.
EventListener
>
listeners
;
private
final
ListenerSet
<
EventListener
,
Events
>
listeners
;
private
final
Timeline
.
Period
period
;
private
final
Timeline
.
Period
period
;
private
final
Timeline
timeline
;
private
Timeline
timeline
;
@Player
.
State
private
int
state
;
@Player
.
State
private
int
state
;
private
boolean
playWhenReady
;
private
boolean
playWhenReady
;
private
long
position
;
private
int
periodIndex
;
private
long
contentPosition
;
private
long
positionMs
;
private
long
contentPositionMs
;
private
boolean
isPlayingAd
;
private
boolean
isPlayingAd
;
private
int
adGroupIndex
;
private
int
adGroupIndex
;
private
int
adIndexInAdGroup
;
private
int
adIndexInAdGroup
;
public
FakePlayer
()
{
public
FakePlayer
()
{
listeners
=
new
ArrayList
<>();
listeners
=
new
ListenerSet
<>(
Looper
.
getMainLooper
(),
Player
.
Events
::
new
,
(
listener
,
eventFlags
)
->
listener
.
onEvents
(
/* player= */
this
,
eventFlags
));
period
=
new
Timeline
.
Period
();
period
=
new
Timeline
.
Period
();
state
=
Player
.
STATE_IDLE
;
state
=
Player
.
STATE_IDLE
;
playWhenReady
=
true
;
playWhenReady
=
true
;
...
@@ -48,26 +53,27 @@ import java.util.ArrayList;
...
@@ -48,26 +53,27 @@ import java.util.ArrayList;
/** Sets the timeline on this fake player, which notifies listeners with the changed timeline. */
/** Sets the timeline on this fake player, which notifies listeners with the changed timeline. */
public
void
updateTimeline
(
Timeline
timeline
,
@TimelineChangeReason
int
reason
)
{
public
void
updateTimeline
(
Timeline
timeline
,
@TimelineChangeReason
int
reason
)
{
for
(
Player
.
EventListener
listener
:
listeners
)
{
this
.
timeline
=
timeline
;
listener
.
onTimelineChanged
(
timeline
,
reason
);
listeners
.
sendEvent
(
}
Player
.
EVENT_TIMELINE_CHANGED
,
listener
->
listener
.
onTimelineChanged
(
timeline
,
reason
));
}
}
/**
/**
* Sets the state of this player as if it were playing content at the given {@code position}. If
* Sets the state of this player as if it were playing content at the given {@code position}. If
* an ad is currently playing, this will trigger a position discontinuity.
* an ad is currently playing, this will trigger a position discontinuity.
*/
*/
public
void
setPlayingContentPosition
(
long
position
)
{
public
void
setPlayingContentPosition
(
int
periodIndex
,
long
positionMs
)
{
boolean
notify
=
isPlayingAd
;
boolean
notify
=
isPlayingAd
;
isPlayingAd
=
false
;
isPlayingAd
=
false
;
adGroupIndex
=
C
.
INDEX_UNSET
;
adGroupIndex
=
C
.
INDEX_UNSET
;
adIndexInAdGroup
=
C
.
INDEX_UNSET
;
adIndexInAdGroup
=
C
.
INDEX_UNSET
;
this
.
position
=
position
;
this
.
periodIndex
=
periodIndex
;
contentPosition
=
position
;
this
.
positionMs
=
positionMs
;
contentPositionMs
=
positionMs
;
if
(
notify
)
{
if
(
notify
)
{
for
(
Player
.
EventListener
listener
:
listeners
)
{
listeners
.
sendEvent
(
listener
.
onPositionDiscontinuity
(
DISCONTINUITY_REASON_AD_INSERTION
);
Player
.
EVENT_POSITION_DISCONTINUITY
,
}
listener
->
listener
.
onPositionDiscontinuity
(
DISCONTINUITY_REASON_AD_INSERTION
));
}
}
}
}
...
@@ -77,17 +83,22 @@ import java.util.ArrayList;
...
@@ -77,17 +83,22 @@ import java.util.ArrayList;
* position discontinuity.
* position discontinuity.
*/
*/
public
void
setPlayingAdPosition
(
public
void
setPlayingAdPosition
(
int
adGroupIndex
,
int
adIndexInAdGroup
,
long
position
,
long
contentPosition
)
{
int
periodIndex
,
int
adGroupIndex
,
int
adIndexInAdGroup
,
long
positionMs
,
long
contentPositionMs
)
{
boolean
notify
=
!
isPlayingAd
||
this
.
adIndexInAdGroup
!=
adIndexInAdGroup
;
boolean
notify
=
!
isPlayingAd
||
this
.
adIndexInAdGroup
!=
adIndexInAdGroup
;
isPlayingAd
=
true
;
isPlayingAd
=
true
;
this
.
periodIndex
=
periodIndex
;
this
.
adGroupIndex
=
adGroupIndex
;
this
.
adGroupIndex
=
adGroupIndex
;
this
.
adIndexInAdGroup
=
adIndexInAdGroup
;
this
.
adIndexInAdGroup
=
adIndexInAdGroup
;
this
.
position
=
position
;
this
.
position
Ms
=
positionMs
;
this
.
contentPosition
=
contentPosition
;
this
.
contentPosition
Ms
=
contentPositionMs
;
if
(
notify
)
{
if
(
notify
)
{
for
(
Player
.
EventListener
listener
:
listeners
)
{
listeners
.
sendEvent
(
listener
.
onPositionDiscontinuity
(
DISCONTINUITY_REASON_AD_INSERTION
);
EVENT_POSITION_DISCONTINUITY
,
}
listener
->
listener
.
onPositionDiscontinuity
(
DISCONTINUITY_REASON_AD_INSERTION
));
}
}
}
}
...
@@ -99,16 +110,18 @@ import java.util.ArrayList;
...
@@ -99,16 +110,18 @@ import java.util.ArrayList;
this
.
state
=
state
;
this
.
state
=
state
;
this
.
playWhenReady
=
playWhenReady
;
this
.
playWhenReady
=
playWhenReady
;
if
(
playbackStateChanged
||
playWhenReadyChanged
)
{
if
(
playbackStateChanged
||
playWhenReadyChanged
)
{
for
(
Player
.
EventListener
listener
:
listeners
)
{
listeners
.
sendEvent
(
listener
.
onPlayerStateChanged
(
playWhenReady
,
state
);
Player
.
EVENT_PLAYBACK_STATE_CHANGED
,
if
(
playbackStateChanged
)
{
listener
->
{
listener
.
onPlaybackStateChanged
(
state
);
listener
.
onPlayerStateChanged
(
playWhenReady
,
state
);
}
if
(
playbackStateChanged
)
{
if
(
playWhenReadyChanged
)
{
listener
.
onPlaybackStateChanged
(
state
);
listener
.
onPlayWhenReadyChanged
(
}
playWhenReady
,
PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST
);
if
(
playWhenReadyChanged
)
{
}
listener
.
onPlayWhenReadyChanged
(
}
playWhenReady
,
PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST
);
}
});
}
}
}
}
...
@@ -146,6 +159,17 @@ import java.util.ArrayList;
...
@@ -146,6 +159,17 @@ import java.util.ArrayList;
}
}
@Override
@Override
@RepeatMode
public
int
getRepeatMode
()
{
return
REPEAT_MODE_OFF
;
}
@Override
public
boolean
getShuffleModeEnabled
()
{
return
false
;
}
@Override
public
int
getRendererCount
()
{
public
int
getRendererCount
()
{
return
0
;
return
0
;
}
}
...
@@ -162,7 +186,7 @@ import java.util.ArrayList;
...
@@ -162,7 +186,7 @@ import java.util.ArrayList;
@Override
@Override
public
int
getCurrentPeriodIndex
()
{
public
int
getCurrentPeriodIndex
()
{
return
0
;
return
periodIndex
;
}
}
@Override
@Override
...
@@ -186,7 +210,7 @@ import java.util.ArrayList;
...
@@ -186,7 +210,7 @@ import java.util.ArrayList;
@Override
@Override
public
long
getCurrentPosition
()
{
public
long
getCurrentPosition
()
{
return
position
;
return
position
Ms
;
}
}
@Override
@Override
...
@@ -206,6 +230,6 @@ import java.util.ArrayList;
...
@@ -206,6 +230,6 @@ import java.util.ArrayList;
@Override
@Override
public
long
getContentPosition
()
{
public
long
getContentPosition
()
{
return
contentPosition
;
return
contentPosition
Ms
;
}
}
}
}
extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java
View file @
05f6d248
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment